载入中...
搜索中...
未找到
MotionDatabaseSchema.cpp
浏览该文件的文档.
1#include <bit>
2#include <cmath>
3#include <cstdint>
4#include <map>
5#include <tuple>
10#include "common/Exception.h"
11
12namespace eve::animation {
14bool MotionDatabase::hasFeatureLayout() const { return schema_ != nullptr; }
15
16float MotionDatabase::schemaTrajectorySpeed(std::span<const float> feature) const {
17 if (!schema_ || feature.size() != static_cast<std::size_t>(schema_->dimension)) return 0.f;
18 float total = 0.f;
19 for (std::size_t i = 0; i < schema_->layout.channels.size(); ++i) {
20 const auto& channel = schema_->layout.channels[i];
21 if (channel.source != MotionFeatureSource::Trajectory || channel.kind != MotionFeatureKind::Velocity ||
22 channel.normalizeVelocity)
23 continue;
24 const int count = std::popcount(static_cast<unsigned>(channel.axes));
25 float squared = 0.f;
26 for (int component = 0; component < count; ++component) {
27 const float value = feature[schema_->offsets[i] + static_cast<std::size_t>(component)];
28 squared += value * value;
29 }
30 total += std::sqrt(squared);
31 }
32 return total;
33}
34
36 auto fail = [](const char* message) {
38 eve::DiagnosticCode::InvalidArgument, message, "featureLayout", {}, "animation"));
39 };
40 if (baked_ || layout.sampleRate < 1 || layout.sampleRate > 240 || layout.channels.empty() || layout.channels.size() > 256 ||
41 !std::isfinite(layout.normalizationLengthScale) || layout.normalizationLengthScale <= 0.f || layout.normalizationLengthScale > 1000000.f)
42 return fail("feature layout requires an unbaked database, 1..240 Hz and 1..256 channels");
43 auto next = std::make_unique<detail::MotionSchemaState>();
44 next->layout = layout;
45 double totalWeight = 0;
46 for (const auto& c : layout.channels) {
48 c.query > MotionFeatureQuery::Continuing || c.axes == 0 || c.axes > 7 ||
49 c.headingAxis < 0 || c.headingAxis > 2 || !std::isfinite(c.weight) || c.weight < 0.f ||
50 c.weight > 1000000.f || !std::isfinite(c.sampleTime) || std::abs(c.sampleTime) > 10.f ||
51 c.normalizationGroup.size() > 4096)
52 return fail("invalid feature channel operation, axes, weight, time or normalization group");
53 if (c.kind == MotionFeatureKind::Curve && (c.source != MotionFeatureSource::Pose || c.axes != 1 ||
54 c.curve.empty() || c.curve.size() > 4096 || c.curve.find('\0') != std::string::npos))
55 return fail("curve channels require a named scalar pose source");
56 if (c.kind != MotionFeatureKind::Curve && !c.curve.empty())
57 return fail("vector channels cannot specify a scalar curve");
58 if (c.kind != MotionFeatureKind::Curve && c.source == MotionFeatureSource::Pose && (c.sampleTime != 0.f || c.bone < 0 || c.origin < 0 ||
59 c.bone >= skeleton_->getBoneCount() || c.origin >= skeleton_->getBoneCount()))
60 return fail("pose channels require valid bones and zero sample offset");
62 (c.kind == MotionFeatureKind::Velocity && c.characterSpaceVelocity)))
63 return fail("trajectory channels require character queries and world-space velocity");
64 const int count = std::popcount(static_cast<unsigned>(c.axes));
65 if (c.normalizeVelocity && c.kind != MotionFeatureKind::Velocity)
66 return fail("velocity normalization requires a velocity channel");
67 next->offsets.push_back(next->dimension);
68 next->dimension += count;
69 totalWeight += c.weight * count;
70 const int group = c.source == MotionFeatureSource::Pose ? 0 : (c.kind == MotionFeatureKind::Velocity ? 1 : 2);
71 next->weightSums[group] += c.weight * count;
72 for (int i = 0; i < count; ++i) {
73 next->weights.push_back(c.weight); next->sources.push_back(c.source);
74 next->kinds.push_back(c.kind); next->queries.push_back(c.query);
75 }
76 }
77 if (next->dimension > 1024 || totalWeight <= 0 || !std::isfinite(totalWeight))
78 return fail("feature layout requires bounded dimensions and positive finite total weight");
79 schema_ = std::move(next);
80 locomotionFeatures_ = false;
81 featureBones_.clear();
82 computeFeatureSize();
84}
85
87 std::span<const std::string> sources) {
88 auto fail = [](const char* message) {
90 message, "featureCurves", {}, "animation"));
91 };
92 if (!schema_ || baked_ || clips_.empty() || sources.size() != clips_.size())
93 return fail("feature curves require a configured unbaked database and one source per clip");
94 auto library = std::make_unique<AnimCurveLibrary>();
95 auto loaded = library->load(bytes);
96 if (!loaded.ok()) return fail("invalid feature curve binary");
97 std::map<const AnimClip*, std::string> names;
98 for (std::size_t i = 0; i < sources.size(); ++i) {
99 if (!library->contains(sources[i])) return fail("feature curve source is missing");
100 auto [it, inserted] = names.emplace(clips_[i], sources[i]);
101 if (!inserted && it->second != sources[i]) return fail("one clip cannot have conflicting curve sources");
102 }
103 schema_->curves = std::move(library);
104 schema_->curveSources = std::move(names);
106}
107
108eve::Result<int> MotionDatabase::setFeatureNormalizationRanges(std::span<const MotionNormalizationRange> ranges) {
109 auto fail = [](const char* message) {
111 eve::DiagnosticCode::InvalidArgument, message, "featureNormalizationRanges", {}, "animation"));
112 };
113 if (!schema_ || baked_ || clips_.empty() || ranges.empty() || ranges.size() > 100000)
114 return fail("normalization ranges require a configured unbaked variable layout, clips and bounded nonempty input");
115 std::uint64_t sampleCount = 0;
116 const float rate = static_cast<float>(schema_->layout.sampleRate);
117 for (const auto& range : ranges) {
118 if (range.clipIndex < 0 || range.clipIndex >= static_cast<int>(clips_.size()) ||
119 !std::isfinite(range.start) || !std::isfinite(range.end) || range.start < 0.f ||
120 range.end < range.start || range.end > clips_[range.clipIndex]->getDuration() + .001f)
121 return fail("invalid normalization clip or time interval");
122 const auto* clip = clips_[range.clipIndex];
123 int last = static_cast<int>(std::floor(clip->getDuration() * rate));
124 if (clip->getLoop() && static_cast<float>(last) / rate >= clip->getDuration() - 1e-5f) --last;
125 const int first = std::max(0, static_cast<int>(std::ceil((range.start - 1e-5f) * rate)));
126 const int end = std::min(last, static_cast<int>(std::floor((range.end + 1e-5f) * rate)));
127 if (first > end) return fail("normalization interval contains no layout-rate sample");
128 sampleCount += static_cast<std::uint64_t>(end - first + 1);
129 if (sampleCount > 100000000) return fail("normalization sample count exceeds the configured bound");
130 }
131 normalizationRanges_.assign(ranges.begin(), ranges.end());
132 return eve::Result<int>::success(static_cast<int>(sampleCount),
134}
135
136namespace {
137TransformTRS inverseRigid(TransformTRS value) {
138 value.qx = -value.qx; value.qy = -value.qy; value.qz = -value.qz;
139 const auto p = detail::schemaRotate({-value.px, -value.py, -value.pz}, value);
140 value.px = p[0]; value.py = p[1]; value.pz = p[2];
141 value.sx = value.sy = value.sz = 1.f;
142 return value;
143}
144TransformTRS rigidPower(TransformTRS delta, float count) {
145 if (count < 0.f) { delta = inverseRigid(delta); count = -count; }
146 auto whole = static_cast<unsigned>(std::floor(count));
147 const float fraction = count - whole;
148 TransformTRS result, power = delta;
149 while (whole) {
150 if (whole & 1) result = detail::mulTRS(result, power);
151 power = detail::mulTRS(power, power); whole >>= 1;
152 }
153 TransformTRS partial;
154 partial.px = delta.px * fraction; partial.py = delta.py * fraction; partial.pz = delta.pz * fraction;
155 slerpQuat(0.f, 0.f, 0.f, 1.f, delta.qx, delta.qy, delta.qz, delta.qw, fraction,
156 partial.qx, partial.qy, partial.qz, partial.qw);
157 return detail::mulTRS(result, partial);
158}
159} // namespace
164 TransformTRS raw(int bone, float time) const {
165 const auto local = clip.sampleBone(bone, time, skeleton.bindLocal(bone));
166 const int parent = skeleton.getParent(bone);
167 return parent < 0 ? local : detail::mulTRS(raw(parent, time), local);
168 }
169 TransformTRS root(float time) const {
170 const float duration = clip.getDuration();
171 if (duration <= 0.f) return raw(rootBone, 0.f);
172 if (clip.getLoop()) {
173 const float cycles = std::floor(time / duration);
174 const auto start = raw(rootBone, 0.f), end = raw(rootBone, duration);
175 const auto delta = detail::mulTRS(inverseRigid(start), end);
176 const auto local = detail::mulTRS(inverseRigid(start), raw(rootBone, clip.wrapTime(time)));
177 return detail::mulTRS(detail::mulTRS(start, rigidPower(delta, cycles)), local);
178 }
179 if (time >= 0.f && time <= duration) return raw(rootBone, time);
180 const float step = std::min(1.f / 30.f, duration);
181 const auto a = raw(rootBone, time < 0.f ? 0.f : duration - step);
182 const auto b = raw(rootBone, time < 0.f ? step : duration);
183 const auto delta = detail::mulTRS(inverseRigid(a), b);
184 return detail::mulTRS(time < 0.f ? a : b, rigidPower(delta, (time < 0.f ? time : time - duration) / step));
185 }
186 TransformTRS bone(int index, float time) const {
187 const float sample = clip.getLoop() ? clip.wrapTime(time) : clampf(time, 0.f, clip.getDuration());
188 return detail::mulTRS(root(time), detail::mulTRS(inverseRigid(raw(rootBone, sample)), raw(index, sample)));
189 }
190};
191
192void MotionDatabase::extractSchemaFeature(AnimClip* clip, float time, std::vector<float>& out, float& rootX,
193 float& rootZ, float& rootYaw, float& velX, float& velZ) const {
194 constexpr float dt = 1.f / 60.f;
195 const SchemaSampler sampler{*skeleton_, *clip, rootBone_};
196 const auto root = sampler.root(time), previousRoot = sampler.root(time - dt);
197 rootX = root.px; rootZ = root.pz;
198 rootYaw = std::atan2(2.f * (root.qw * root.qy + root.qx * root.qz), 1.f - 2.f * (root.qx * root.qx + root.qy * root.qy));
199 velX = (root.px - previousRoot.px) / dt; velZ = (root.pz - previousRoot.pz) / dt;
200 out.assign(schema_->dimension, 0.f);
201 for (std::size_t i = 0; i < schema_->layout.channels.size(); ++i) {
202 const auto& c = schema_->layout.channels[i];
203 std::array<float, 3> value{};
204 if (c.kind == MotionFeatureKind::Curve) {
205 auto sampled = schema_->curves->sample(schema_->curveSources.at(clip), c.curve,
206 time + c.sampleTime, clip->getLoop());
207 if (!sampled.ok()) throw std::runtime_error("configured motion curve cannot be sampled");
208 value[0] = sampled.value().value_or(0.f);
209 } else if (c.source == MotionFeatureSource::Trajectory) {
210 const auto sample = sampler.root(time + c.sampleTime);
212 else if (c.kind == MotionFeatureKind::Heading) value = detail::schemaHeading(sample, root, c.headingAxis);
213 else {
214 value = detail::schemaDifference(sample, sampler.root(time + c.sampleTime - dt), root);
215 for (float& v : value) v /= dt;
216 }
217 } else {
218 const auto bone = sampler.bone(c.bone, time), origin = sampler.bone(c.origin, time);
220 else if (c.kind == MotionFeatureKind::Heading) value = detail::schemaHeading(bone, root, c.headingAxis);
221 else if (c.characterSpaceVelocity) {
223 const auto past = detail::schemaDifference(sampler.bone(c.bone, time - dt), sampler.bone(c.origin, time - dt), previousRoot);
224 for (int axis = 0; axis < 3; ++axis) value[axis] = (value[axis] - past[axis]) / dt;
225 } else {
226 value = detail::schemaDifference(bone, sampler.bone(c.bone, time - dt), root);
227 for (float& v : value) v /= dt;
228 }
229 }
230 detail::schemaEncode(std::span(out).subspan(schema_->offsets[i]), c, value, schema_->layout.normalizationLengthScale);
231 }
232}
233
234void MotionDatabase::normalizeSchemaFeatures() {
235 featureMean_.assign(schema_->dimension, 0.f); featureInvStd_.assign(schema_->dimension, 1.f);
236 using Key = std::tuple<MotionFeatureKind, int, std::string, int>;
237 std::map<Key, std::vector<int>> groups;
238 for (std::size_t i = 0; i < schema_->layout.channels.size(); ++i) {
239 const auto& c = schema_->layout.channels[i];
240 groups[{c.kind, std::popcount(static_cast<unsigned>(c.axes)), c.normalizationGroup,
241 c.normalizationGroup.empty() ? static_cast<int>(i) : -1}].push_back(static_cast<int>(i));
242 }
243 std::vector<const Frame*> samples;
244 if (normalizationRanges_.empty()) {
245 samples.reserve(frames_.size());
246 for (const auto& frame : frames_) samples.push_back(&frame);
247 } else {
248 std::vector<std::vector<const Frame*>> perClip(clips_.size());
249 for (const auto& frame : frames_) perClip[frame.clipIndex].push_back(&frame);
250 for (const auto& range : normalizationRanges_)
251 for (const auto* frame : perClip[range.clipIndex])
252 if (frame->time + 1e-5f >= range.start && frame->time - 1e-5f <= range.end)
253 samples.push_back(frame);
254 }
255 if (samples.empty()) throw Exception("MotionDatabase: normalization ranges contain no baked samples");
256 for (const auto& [key, channels] : groups) {
257 const int count = std::get<1>(key);
258 const double size = static_cast<double>(samples.size()) * channels.size();
259 std::array<double, 3> mean{};
260 for (const auto* frame : samples) for (int channel : channels)
261 for (int axis = 0; axis < count; ++axis) mean[axis] += frame->feature[schema_->offsets[channel] + axis];
262 for (double& v : mean) v /= size;
263 double deviation = 0;
264 for (const auto* frame : samples) for (int channel : channels) {
265 double square = 0;
266 for (int axis = 0; axis < count; ++axis) {
267 const double delta = frame->feature[schema_->offsets[channel] + axis] - mean[axis]; square += delta * delta;
268 }
269 deviation += std::sqrt(square);
270 }
271 deviation /= size;
272 const auto& first = schema_->layout.channels[channels[0]];
273 const bool unit = first.kind == MotionFeatureKind::Heading || first.kind == MotionFeatureKind::Curve || first.normalizeVelocity;
274 const float scale = schema_->layout.normalizationLengthScale;
275 const float inverse = static_cast<float>(deviation > (unit ? .1 : .001 * scale) ? 1.0 / deviation : (unit ? 1.0 : 100.0 / scale));
276 for (int channel : channels) for (int axis = 0; axis < count; ++axis) {
277 featureMean_[schema_->offsets[channel] + axis] = static_cast<float>(mean[axis]);
278 featureInvStd_[schema_->offsets[channel] + axis] = inverse;
279 }
280 }
281 for (auto& frame : frames_) normalizeFeature(frame.feature);
282}
283} // namespace eve::animation
double value
Duration start
float duration
std::map< std::string, std::vector< Key >, std::less<> > channels
int root
Definition AnimSmr.cpp:119
building::EdgeCurveGroup group
glm::vec4 p[6]
std::string layout
std::string message
std::uint32_t key
vk::UniqueSampler sampler
glm::vec4 clip
float v
std::int32_t c
std::int32_t first
std::string local
Range range
std::array< float, 3 > scale
std::uint32_t bone
std::int32_t parent
std::uint64_t bytes
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
uint32_t groups
Definition OnnxGpgpu.cpp:39
V3 origin
Definition RoadBake.cpp:138
float power
Definition RockMesh.cpp:23
std::uint32_t count
TacticalUnit * unit
float step
Definition TreeMesh.cpp:314
float size
Definition TreeMesh.cpp:156
uint32_t index
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
Keyframed skeletal animation clip (local TRS tracks per bone). Script type: AnimClip.
Definition AnimClip.h:145
float getDuration() const
Returns the duration.
Definition AnimClip.h:163
bool getLoop() const
Returns the loop.
Definition AnimClip.h:168
float wrapTime(float time) const
Wrap or clamp time according to loop flag.
Definition AnimClip.cpp:471
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.
eve::Result< int > setFeatureNormalizationRanges(std::span< const MotionNormalizationRange > ranges)
Atomically select weighted source ranges used to normalize a variable feature layout.
void normalizeFeature(std::vector< float > &feature) const
Normalize a query with statistics computed by bake().
bool hasFeatureLayout() const
Whether this database uses an explicitly configured variable layout.
eve::Result< void > setFeatureLayout(const MotionFeatureLayout &layout)
Atomically configure an owning variable feature layout before bake.
eve::Result< void > setFeatureCurves(std::span< const std::byte > bytes, std::span< const std::string > sources)
Atomically copy scalar source curves for the configured layout.
~MotionDatabase()
Motion database.
std::array< float, 3 > schemaDifference(const TransformTRS &a, const TransformTRS &b, const TransformTRS &root)
TransformTRS mulTRS(const TransformTRS &parent, const TransformTRS &local)
Mul trs.
std::array< float, 3 > schemaHeading(const TransformTRS &bone, const TransformTRS &root, int axis)
std::array< float, 3 > schemaRotate(const std::array< float, 3 > &p, const TransformTRS &q, bool inverse=false)
void schemaEncode(std::span< float > output, const MotionFeatureChannel &channel, std::array< float, 3 > value, float lengthScale)
Schema encode.
void slerpQuat(float ax, float ay, float az, float aw, float bx, float by, float bz, float bw, float t, float &ox, float &oy, float &oz, float &ow)
Slerp quat.
Definition AnimMath.h:43
float clampf(float v, float lo, float hi)
Clampf.
Definition AnimMath.h:34
double sample(const Heightmap &map, double u, double v)
Sample.
int axis(int64_t a, size_t rank)
Axis.
TransformTRS bone(int index, float time) const
TransformTRS raw(int bone, float time) const
Owning ordered feature layout; copied atomically by a database.
Local TRS used by skeletal animation (quaternion xyzw).
Definition AnimMath.h:9