15 if (!skeleton_)
throw Exception(
"MotionDatabase: skeleton is null");
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);
25 for (
int b : featureBones_) {
26 if (
b == boneIndex)
return;
28 featureBones_.push_back(boneIndex);
34 if (
id < 0)
throw Exception(
"MotionDatabase.addFeatureBoneByName: unknown '%s'",
name.c_str());
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);
43 rootBone_ = boneIndex;
49 if (
id < 0)
throw Exception(
"MotionDatabase.setRootBoneByName: unknown '%s'",
name.c_str());
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);
62 throw Exception(
"MotionDatabase.getClip: invalid index %d", clipIndex);
64 return clips_[
static_cast<size_t>(clipIndex)];
67void MotionDatabase::computeFeatureSize() {
69 featureSize_ = schema_ ? schema_->dimension : (locomotionFeatures_ ? 30 : 2 + 6 + 2 +
static_cast<int>(featureBones_.size()) * 3);
72float MotionDatabase::yawFromQuat(
float ,
float y,
float ,
float w) {
74 return std::atan2(2.f * (
w *
y), 1.f - 2.f * (
y *
y));
79 throw Exception(
"MotionDatabase.getFeatureBone: invalid index %d",
index);
81 return featureBones_[
static_cast<size_t>(
index)];
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 {
87 extractSchemaFeature(
clip,
time, out, rootX, rootZ, rootYaw, velX, velZ);
90 if (locomotionFeatures_) {
91 extractLocomotionFeature(
clip,
time, out, rootX, rootZ, rootYaw, velX, velZ);
94 out.assign(
static_cast<size_t>(featureSize_), 0.f);
98 auto worldBone = [&](
auto&& self,
int bone,
float sampleTime) -> TransformTRS {
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);
107 const int root = rootBone_;
115 float cycleX = 0.f, cycleZ = 0.f;
118 const auto start = worldAt(
root, 0.f,
false);
123 auto cyclesAt = [&](
float t) {
return clip->getLoop() &&
duration > 1e-8f ? std::floor(
t /
duration) : 0.f; };
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;
134 float cs = std::cos(rootYaw);
135 float sn = std::sin(rootYaw);
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;
144 out[0] = velX * cs - velZ * sn;
145 out[1] = velX * sn + velZ * cs;
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]);
151 const float cycles = cyclesAt(
time + horizons[
h]);
152 toLocal(fut.px + cycles * cycleX, fut.pz + cycles * cycleZ, lx, lz);
154 out[2 +
h * 2 + 1] = lz;
158 const auto fut = worldAt(
root,
time + 1.0f);
159 const float fyaw = yawFromQuat(fut.qx, fut.qy, fut.qz, fut.qw);
167 for (
int bone : featureBones_) {
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;
180 if (schema_)
for (
const auto& channel : schema_->layout.channels) {
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");
186 if (clips_.empty())
throw Exception(
"MotionDatabase.bake: no clips");
187 if (featureBones_.empty() && !schema_) {
192 computeFeatureSize();
194 std::vector<std::vector<Frame>> clipFrames(clips_.size());
197 const int ci =
static_cast<int>(
index);
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();
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));
212 const int schemaLast = schema_ ?
static_cast<int>(std::floor(dur *
rate)) : 0;
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;
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));
225 std::size_t total = 0;
226 for (
const auto&
output : clipFrames) total +=
output.size();
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));
235 clipFrameOffsets_.push_back(
static_cast<int>(frames_.size()));
237 normalizeSchemaFeatures();
241 if (locomotionFeatures_) {
242 normalizeLocomotionFeatures();
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;
258 for (
float& invStd : featureInvStd_) invStd = 1.f / std::sqrt(invStd * invCount + 1e-6f);
264 if (
static_cast<int>(feature.size()) != featureSize_ ||
static_cast<int>(featureMean_.size()) != featureSize_)
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)];
271void MotionDatabase::requireBaked()
const {
272 if (!baked_)
throw Exception(
"MotionDatabase: not baked; call bake() first");
280 return frames_[
static_cast<size_t>(
index)];
289 if (!out || outCount < featureSize_) {
290 throw Exception(
"MotionDatabase.getFeature: buffer too small");
292 for (
int i = 0; i < featureSize_; ++i) out[i] =
f.feature[
static_cast<size_t>(i)];
EVENGINE_API_FOUNDATION public API.
Keyframed skeletal animation clip (local TRS tracks per bone). Script type: AnimClip.
void resize(int boneCount)
Resize.
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.
float clampf(float v, float lo, float hi)
Clampf.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).