载入中...
搜索中...
未找到
MotionDatabase.cpp
浏览该文件的文档.
7
8#include "common/Exception.h"
9
10#include <cmath>
11
12namespace eve::animation {
13
14MotionDatabase::MotionDatabase(AnimSkeleton* skeleton) : skeleton_(skeleton) {
15 if (!skeleton_) throw Exception("MotionDatabase: skeleton is null");
16 scratchPose_.resize(skeleton_->getBoneCount());
17 // Default: use every non-root bone if none specified before bake.
18}
19
20void MotionDatabase::addFeatureBone(int boneIndex) {
21 if (locomotionFeatures_ || schema_) throw Exception("MotionDatabase: configured layout owns its feature bones");
22 if (boneIndex < 0 || boneIndex >= skeleton_->getBoneCount()) {
23 throw Exception("MotionDatabase.addFeatureBone: invalid bone %d", boneIndex);
24 }
25 for (int b : featureBones_) {
26 if (b == boneIndex) return;
27 }
28 featureBones_.push_back(boneIndex);
29 baked_ = false;
30}
31
33 const int id = skeleton_->findBone(name);
34 if (id < 0) throw Exception("MotionDatabase.addFeatureBoneByName: unknown '%s'", name.c_str());
36}
37
38void MotionDatabase::setRootBone(int boneIndex) {
39 if (schema_ && baked_) throw Exception("MotionDatabase: baked feature layout is immutable");
40 if (boneIndex < 0 || boneIndex >= skeleton_->getBoneCount()) {
41 throw Exception("MotionDatabase.setRootBone: invalid bone %d", boneIndex);
42 }
43 rootBone_ = boneIndex;
44 baked_ = false;
45}
46
47void MotionDatabase::setRootBoneByName(const std::string& name) {
48 const int id = skeleton_->findBone(name);
49 if (id < 0) throw Exception("MotionDatabase.setRootBoneByName: unknown '%s'", name.c_str());
50 setRootBone(id);
51}
52
54 if (schema_ && baked_) throw Exception("MotionDatabase: baked feature layout is immutable");
55 if (!clip) throw Exception("MotionDatabase.addClip: clip is null");
56 clips_.push_back(clip);
57 baked_ = false;
58}
59
60AnimClip* MotionDatabase::getClip(int clipIndex) const {
61 if (clipIndex < 0 || clipIndex >= getClipCount()) {
62 throw Exception("MotionDatabase.getClip: invalid index %d", clipIndex);
63 }
64 return clips_[static_cast<size_t>(clipIndex)];
65}
66
67void MotionDatabase::computeFeatureSize() {
68 // vel(2) + trajPos*3(6) + trajFacing(2) + bones*3
69 featureSize_ = schema_ ? schema_->dimension : (locomotionFeatures_ ? 30 : 2 + 6 + 2 + static_cast<int>(featureBones_.size()) * 3);
70}
71
72float MotionDatabase::yawFromQuat(float /*x*/, float y, float /*z*/, float w) {
73 // Yaw from quaternion (Y-up), assuming mostly planar rotation.
74 return std::atan2(2.f * (w * y), 1.f - 2.f * (y * y));
75}
76
78 if (index < 0 || index >= getFeatureBoneCount()) {
79 throw Exception("MotionDatabase.getFeatureBone: invalid index %d", index);
80 }
81 return featureBones_[static_cast<size_t>(index)];
82}
83
84void MotionDatabase::extractFeature(AnimClip* clip, float time, float dtSample, std::vector<float>& out, float& rootX,
85 float& rootZ, float& rootYaw, float& velX, float& velZ) const {
86 if (schema_) {
87 extractSchemaFeature(clip, time, out, rootX, rootZ, rootYaw, velX, velZ);
88 return;
89 }
90 if (locomotionFeatures_) {
91 extractLocomotionFeature(clip, time, out, rootX, rootZ, rootYaw, velX, velZ);
92 return;
93 }
94 out.assign(static_cast<size_t>(featureSize_), 0.f);
95 // Trajectory horizons need only the root's ancestor chain; pose features
96 // need only their own chains. Sampling every unrelated finger/face bone at
97 // all six horizons makes full-corpus baking unnecessarily expensive.
98 auto worldBone = [&](auto&& self, int bone, float sampleTime) -> TransformTRS {
99 const auto local = clip->sampleBone(bone, sampleTime, skeleton_->bindLocal(bone));
100 const int parent = skeleton_->getParent(bone);
101 return parent < 0 ? local : detail::mulTRS(self(self, parent, sampleTime), local);
102 };
103 auto worldAt = [&](int bone, float sampleTime, bool wrap = true) {
104 const float t = clampf(wrap ? clip->wrapTime(sampleTime) : sampleTime, 0.f, clip->getDuration());
105 return worldBone(worldBone, bone, t);
106 };
107 const int root = rootBone_;
108 const auto current = worldAt(root, time);
109 rootX = current.px;
110 rootZ = current.pz;
111 rootYaw = yawFromQuat(current.qx, current.qy, current.qz, current.qw);
112
113 // Sampling a looping pose wraps its root back to the origin. Trajectories
114 // must instead accumulate the displacement of every completed cycle.
115 float cycleX = 0.f, cycleZ = 0.f;
116 const float duration = clip->getDuration();
117 if (clip->getLoop() && duration > 1e-8f) {
118 const auto start = worldAt(root, 0.f, false);
119 const auto end = worldAt(root, duration, false);
120 cycleX = end.px - start.px;
121 cycleZ = end.pz - start.pz;
122 }
123 auto cyclesAt = [&](float t) { return clip->getLoop() && duration > 1e-8f ? std::floor(t / duration) : 0.f; };
124
125 // Velocity from nearby sample.
126 const float t1 = time + std::max(dtSample, 1e-3f);
127 const auto next = worldAt(root, t1);
128 const float nX = next.px + cyclesAt(t1) * cycleX;
129 const float nZ = next.pz + cyclesAt(t1) * cycleZ;
130 const float dtt = std::max(dtSample, 1e-3f);
131 velX = (nX - rootX) / dtt;
132 velZ = (nZ - rootZ) / dtt;
133
134 float cs = std::cos(rootYaw);
135 float sn = std::sin(rootYaw);
136 // Character-space: rotate world xz by -yaw
137 auto toLocal = [&](float wx, float wz, float& lx, float& lz) {
138 const float dx = wx - rootX;
139 const float dz = wz - rootZ;
140 lx = dx * cs - dz * sn;
141 lz = dx * sn + dz * cs;
142 };
143
144 out[0] = velX * cs - velZ * sn;
145 out[1] = velX * sn + velZ * cs;
146
147 const float horizons[3] = {0.33f, 0.66f, 1.0f};
148 for (int h = 0; h < 3; ++h) {
149 const auto fut = worldAt(root, time + horizons[h]);
150 float lx, lz;
151 const float cycles = cyclesAt(time + horizons[h]);
152 toLocal(fut.px + cycles * cycleX, fut.pz + cycles * cycleZ, lx, lz);
153 out[2 + h * 2] = lx;
154 out[2 + h * 2 + 1] = lz;
155 }
156
157 {
158 const auto fut = worldAt(root, time + 1.0f);
159 const float fyaw = yawFromQuat(fut.qx, fut.qy, fut.qz, fut.qw);
160 float fx, fz;
161 yawToForward(fyaw - rootYaw, fx, fz);
162 out[8] = fx;
163 out[9] = fz;
164 }
165
166 int base = 10;
167 for (int bone : featureBones_) {
168 float lx, lz;
169 const auto feature = worldAt(bone, time);
170 const float wy = feature.py;
171 toLocal(feature.px, feature.pz, lx, lz);
172 out[static_cast<size_t>(base)] = lx;
173 out[static_cast<size_t>(base + 1)] = wy;
174 out[static_cast<size_t>(base + 2)] = lz;
175 base += 3;
176 }
177}
178
180 if (schema_) for (const auto& channel : schema_->layout.channels) {
181 if (channel.kind != MotionFeatureKind::Curve) continue;
182 if (!schema_->curves) throw Exception("MotionDatabase: scalar curve data must be configured before bake");
183 for (const auto* clip : clips_) if (!schema_->curveSources.contains(clip))
184 throw Exception("MotionDatabase: added clip has no scalar curve source");
185 }
186 if (clips_.empty()) throw Exception("MotionDatabase.bake: no clips");
187 if (featureBones_.empty() && !schema_) {
188 // Default: all bones except root.
189 for (int i = 1; i < skeleton_->getBoneCount(); ++i) addFeatureBone(i);
190 if (featureBones_.empty() && skeleton_->getBoneCount() > 0) addFeatureBone(0);
191 }
192 computeFeatureSize();
193 baked_ = false;
194 std::vector<std::vector<Frame>> clipFrames(clips_.size());
195
196 detail::parallelAnimationItems(clips_.size(), clips_.size() < 16 ? 1 : 0, [&](std::size_t index) {
197 const int ci = static_cast<int>(index);
198 auto& output = clipFrames[index];
199 AnimClip* clip = clips_[static_cast<size_t>(ci)];
200 const float rate = schema_ ? static_cast<float>(schema_->layout.sampleRate) : (clip->getSampleRate() > 0.f ? clip->getSampleRate() : 30.f);
201 const float dt = 1.f / rate;
202 const float dur = clip->getDuration();
203 if (dur <= 0.f) {
204 Frame f;
205 f.clipIndex = ci;
206 f.time = 0.f;
207 extractFeature(clip, 0.f, dt, f.feature, f.rootX, f.rootZ, f.rootYaw, f.velX, f.velZ);
208 if (schema_) f.trajectorySpeed = schemaTrajectorySpeed(f.feature);
209 output.push_back(std::move(f));
210 return;
211 }
212 const int schemaLast = schema_ ? static_cast<int>(std::floor(dur * rate)) : 0;
213 float t = 0.f;
214 for (int sample = 0; ; ++sample, t = schema_ ? sample / rate : t + dt) {
215 if (schema_ ? (sample > schemaLast || (clip->getLoop() && t >= dur - 1e-5f)) : t >= dur - 1e-5f) break;
216 Frame f;
217 f.clipIndex = ci;
218 f.time = t;
219 extractFeature(clip, t, dt, f.feature, f.rootX, f.rootZ, f.rootYaw, f.velX, f.velZ);
220 if (schema_) f.trajectorySpeed = schemaTrajectorySpeed(f.feature);
221 output.push_back(std::move(f));
222 }
223 });
224 // Preserve frame IDs and floating-point reduction order exactly.
225 std::size_t total = 0;
226 for (const auto& output : clipFrames) total += output.size();
227 frames_.clear();
228 frames_.reserve(total);
229 clipFrameOffsets_.clear();
230 clipFrameOffsets_.reserve(clipFrames.size() + 1);
231 for (auto& output : clipFrames) {
232 clipFrameOffsets_.push_back(static_cast<int>(frames_.size()));
233 for (auto& frame : output) frames_.push_back(std::move(frame));
234 }
235 clipFrameOffsets_.push_back(static_cast<int>(frames_.size()));
236 if (schema_) {
237 normalizeSchemaFeatures();
238 baked_ = true;
239 return;
240 }
241 if (locomotionFeatures_) {
242 normalizeLocomotionFeatures();
243 baked_ = true;
244 return;
245 }
246 featureMean_.assign(static_cast<size_t>(featureSize_), 0.f);
247 featureInvStd_.assign(static_cast<size_t>(featureSize_), 0.f);
248 for (const Frame& frame : frames_)
249 for (int i = 0; i < featureSize_; ++i)
250 featureMean_[static_cast<size_t>(i)] += frame.feature[static_cast<size_t>(i)];
251 const float invCount = 1.f / static_cast<float>(frames_.size());
252 for (float& mean : featureMean_) mean *= invCount;
253 for (const Frame& frame : frames_)
254 for (int i = 0; i < featureSize_; ++i) {
255 const float d = frame.feature[static_cast<size_t>(i)] - featureMean_[static_cast<size_t>(i)];
256 featureInvStd_[static_cast<size_t>(i)] += d * d;
257 }
258 for (float& invStd : featureInvStd_) invStd = 1.f / std::sqrt(invStd * invCount + 1e-6f);
259 for (Frame& frame : frames_) normalizeFeature(frame.feature);
260 baked_ = true;
261}
262
263void MotionDatabase::normalizeFeature(std::vector<float>& feature) const {
264 if (static_cast<int>(feature.size()) != featureSize_ || static_cast<int>(featureMean_.size()) != featureSize_)
265 return;
266 for (int i = 0; i < featureSize_; ++i)
267 feature[static_cast<size_t>(i)] = (feature[static_cast<size_t>(i)] - featureMean_[static_cast<size_t>(i)]) *
268 featureInvStd_[static_cast<size_t>(i)];
269}
270
271void MotionDatabase::requireBaked() const {
272 if (!baked_) throw Exception("MotionDatabase: not baked; call bake() first");
273}
274
276 requireBaked();
277 if (index < 0 || index >= getFrameCount()) {
278 throw Exception("MotionDatabase: invalid frame %d", index);
279 }
280 return frames_[static_cast<size_t>(index)];
281}
282
283float MotionDatabase::getFrameTime(int frameIndex) const { return frameAt(frameIndex).time; }
284
285int MotionDatabase::getFrameClipIndex(int frameIndex) const { return frameAt(frameIndex).clipIndex; }
286
287void MotionDatabase::getFeature(int frameIndex, float* out, int outCount) const {
288 const Frame& f = frameAt(frameIndex);
289 if (!out || outCount < featureSize_) {
290 throw Exception("MotionDatabase.getFeature: buffer too small");
291 }
292 for (int i = 0; i < featureSize_; ++i) out[i] = f.feature[static_cast<size_t>(i)];
293}
294
295} // namespace eve::animation
Duration start
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float duration
int root
Definition AnimSmr.cpp:119
std::string output
glm::vec4 clip
int h
std::string local
std::uint32_t bone
std::int32_t parent
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
float f
float d
float t
double current
float dz
float dx
uint32_t index
float wz
float wx
float wy
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Keyframed skeletal animation clip (local TRS tracks per bone). Script type: AnimClip.
Definition AnimClip.h:145
void resize(int boneCount)
Resize.
Definition AnimPose.cpp:78
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.
const TransformTRS & bindLocal(int boneIndex) const
Binds local.
int findBone(const std::string &name) const
Finds bone.
void setRootBoneByName(const std::string &name)
Sets the root bone by name.
void normalizeFeature(std::vector< float > &feature) const
Normalize a query with statistics computed by bake().
void getFeature(int frameIndex, float *out, int outCount) const
Copy feature vector into out[0..featureSize).
void addFeatureBone(int boneIndex)
Include bone world position in pose features (by index).
float getFrameTime(int frameIndex) const
Returns the frame time.
int getFeatureBoneCount() const
Returns the feature bone count.
AnimClip * getClip(int clipIndex) const
Returns the clip.
const Frame & frameAt(int index) const
void addFeatureBoneByName(const std::string &name)
Adds feature bone by name.
void bake()
Bake all clips into searchable frames. Call after addClip / feature bones.
int getFeatureBone(int index) const
Returns the feature bone.
int getFrameCount() const
Returns the frame count.
void setRootBone(int boneIndex)
Bone used for trajectory / velocity features (default 0). Mixamo clips typically want hips (mixamorig...
void addClip(AnimClip *clip)
Adds clip.
MotionDatabase(AnimSkeleton *skeleton)
Motion database.
int getFrameClipIndex(int frameIndex) const
Returns the frame clip index.
int getClipCount() const
Returns the clip count.
TransformTRS mulTRS(const TransformTRS &parent, const TransformTRS &local)
Mul trs.
int parallelAnimationItems(std::size_t count, int requested, Body &&body)
Parallel animation items.
void yawToForward(float yaw, float &fx, float &fz)
Rotate unit +Z by yaw (radians) around Y — used for planar facing.
Definition AnimMath.h:144
float clampf(float v, float lo, float hi)
Clampf.
Definition AnimMath.h:34
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76