载入中...
搜索中...
未找到
AnimationMotionBindings.cpp
浏览该文件的文档.
1#include <array>
2#include <cstdint>
3#include <functional>
4#include <simplesquirrel/simplesquirrel.hpp>
5#include <stdexcept>
6#include <string>
7#include <vector>
8
16#include "filesystem/FileData.h"
18
19namespace eve::animation {
20namespace {
21
22constexpr const char* kSource = "animation.bindings";
23
24
25} // namespace
26
27// A provider read hands over a freshly allocated FileData that this call owns.
29
30void exposeMotionMatcherBindings(ssq::Table& table) {
31 const script::BindContext bind{table.getHandle(), kSource};
34 auto db = table.addClass<MotionDatabase>(
35 "MotionDatabase", std::function<MotionDatabase*()>([]() -> MotionDatabase* { return nullptr; }), true);
36 db.addFunc("addFeatureBone", &MotionDatabase::addFeatureBone);
37 db.addFunc("addFeatureBoneByName", &MotionDatabase::addFeatureBoneByName);
38 db.addFunc("setRootBone", &MotionDatabase::setRootBone);
39 db.addFunc("getRootBone", &MotionDatabase::getRootBone);
40 db.addFunc("setRootBoneByName", &MotionDatabase::setRootBoneByName);
41 db.addFunc("addClip", &MotionDatabase::addClip);
42 db.addFunc("getClipCount", &MotionDatabase::getClipCount);
43 db.addFunc("bake", &MotionDatabase::bake);
44 db.addFunc("isBaked", &MotionDatabase::isBaked);
45 db.addFunc("getFrameCount", &MotionDatabase::getFrameCount);
46 db.addFunc("getFeatureSize", &MotionDatabase::getFeatureSize);
47 db.addFunc("getFrameTime", &MotionDatabase::getFrameTime);
48 db.addFunc("getFrameClipIndex", &MotionDatabase::getFrameClipIndex);
49 db.addFunc("getFeatureBoneCount", &MotionDatabase::getFeatureBoneCount);
50 db.addFunc("getFeatureBone", &MotionDatabase::getFeatureBone);
51 db.addFunc("setLocomotionFeatures", [bind](MotionDatabase* self, int left, int right, int pelvis) {
52 return script::projectResult(bind.vm(), self->setLocomotionFeatures(left, right, pelvis));
53 });
54 db.addFunc("hasLocomotionFeatures", &MotionDatabase::hasLocomotionFeatures);
55 db.addFunc("hasFeatureLayout", &MotionDatabase::hasFeatureLayout);
56 db.addFunc("loadFeatureCurves", [bind](MotionDatabase* self, std::string path, ssq::Array sources) {
57 try {
58 if (sources.size() != static_cast<std::size_t>(self->getClipCount()))
59 throw std::runtime_error("one curve source per database clip is required");
60 std::vector<std::string> names;
61 for (std::size_t i = 0; i < sources.size(); ++i) names.push_back(sources.get<std::string>(i));
62 auto* fs = eve::ModuleManager::getInstance<eve::filesystem::Filesystem>("Filesystem");
63 if (!fs) throw std::runtime_error("filesystem unavailable");
64 auto* raw = fs->read(path);
65 if (!raw) throw std::runtime_error("feature curve file unavailable");
66 Owned<eve::filesystem::FileData> data(raw);
68 bind.vm(),
69 self->setFeatureCurves({static_cast<const std::byte*>(data->getData()), data->getSize()}, names));
70 } catch (const std::exception& error) {
71 return bind.failInvalid(error.what());
72 }
73 });
74 db.addFunc("setFeatureLayout", [bind](MotionDatabase* self, int rate, ssq::Array entries, float lengthScale) {
75 try {
76 if (entries.size() == 0 || entries.size() > 256)
77 throw std::runtime_error("feature layout requires 1..256 channels");
80 layout.normalizationLengthScale = lengthScale;
81 for (std::size_t i = 0; i < entries.size(); ++i) {
82 auto item = entries.get<ssq::Array>(i);
83 if (item.size() != 12 && item.size() != 13)
84 throw std::runtime_error("feature channel requires twelve fields plus an optional curve name");
86 const auto kind = item.get<std::string>(0), source = item.get<std::string>(1),
87 query = item.get<std::string>(2);
88 if (kind == "position")
90 else if (kind == "velocity")
92 else if (kind == "heading")
94 else if (kind == "curve")
96 else
97 throw std::runtime_error("unsupported feature operation");
98 if (source == "pose")
100 else if (source == "trajectory")
102 else
103 throw std::runtime_error("unsupported feature source");
104 if (query == "USE_CHARACTER_POSE")
106 else if (query == "USE_CONTINUING_POSE")
108 else
109 throw std::runtime_error("unsupported feature query policy");
110 c.bone = item.get<int>(3);
111 c.origin = item.get<int>(4);
112 const int axes = item.get<int>(5);
113 if (axes < 1 || axes > 7) throw std::runtime_error("feature axes must be a nonempty XYZ mask");
114 c.axes = static_cast<std::uint8_t>(axes);
115 c.headingAxis = item.get<int>(6);
116 c.sampleTime = item.get<float>(7);
117 c.weight = item.get<float>(8);
118 c.characterSpaceVelocity = item.get<bool>(9);
119 c.normalizeVelocity = item.get<bool>(10);
120 c.normalizationGroup = item.get<std::string>(11);
121 if (item.size() == 13) c.curve = item.get<std::string>(12);
122 layout.channels.push_back(std::move(c));
123 }
124 return script::projectResult(bind.vm(), self->setFeatureLayout(layout));
125 } catch (const std::exception& error) {
126 return bind.failInvalid(error.what());
127 }
128 });
129 db.addFunc("setFeatureNormalizationRanges", [bind](MotionDatabase* self, ssq::Array entries) {
130 try {
131 if (entries.size() > 100000) throw std::runtime_error("too many normalization ranges");
132 std::vector<MotionNormalizationRange> ranges;
133 for (std::size_t i = 0; i < entries.size(); ++i) {
134 auto item = entries.get<ssq::Array>(i);
135 if (item.size() != 3) throw std::runtime_error("normalization range requires clip,start,end");
136 ranges.push_back({item.get<int>(0), item.get<float>(1), item.get<float>(2)});
137 }
138 return script::projectResult(bind.vm(), self->setFeatureNormalizationRanges(ranges),
139 [](int count) { return Value(count); });
140 } catch (const std::exception& error) {
141 return bind.failInvalid(error.what());
142 }
143 });
144 auto mm = table.addClass<MotionMatcher>(
145 "MotionMatcher", std::function<MotionMatcher*()>([]() -> MotionMatcher* { return nullptr; }), true);
146 mm.addFunc("setDesiredVelocity", &MotionMatcher::setDesiredVelocity);
147 mm.addFunc("getDesiredVelocityX", &MotionMatcher::getDesiredVelocityX);
148 mm.addFunc("getDesiredVelocityZ", &MotionMatcher::getDesiredVelocityZ);
149 mm.addFunc("setDesiredYaw", &MotionMatcher::setDesiredYaw);
150 mm.addFunc("getDesiredYaw", &MotionMatcher::getDesiredYaw);
151 mm.addFunc("setSearchInterval", &MotionMatcher::setSearchInterval);
152 mm.addFunc("getSearchInterval", &MotionMatcher::getSearchInterval);
153 mm.addFunc("setBlendTime", &MotionMatcher::setBlendTime);
154 mm.addFunc("getBlendTime", &MotionMatcher::getBlendTime);
155 mm.addFunc("setPlayRateRange", [bind](MotionMatcher* self, float minimum, float maximum) {
156 return script::projectResult(bind.vm(), self->setPlayRateRange(minimum, maximum));
157 });
158 mm.addFunc("getPlayRateMinimum", &MotionMatcher::getPlayRateMinimum);
159 mm.addFunc("getPlayRateMaximum", &MotionMatcher::getPlayRateMaximum);
160 mm.addFunc("getPlayRate", &MotionMatcher::getPlayRate);
161 mm.addFunc("setTrajectoryWeight", &MotionMatcher::setTrajectoryWeight);
162 mm.addFunc("getTrajectoryWeight", &MotionMatcher::getTrajectoryWeight);
163 mm.addFunc("setPoseWeight", &MotionMatcher::setPoseWeight);
164 mm.addFunc("getPoseWeight", &MotionMatcher::getPoseWeight);
165 mm.addFunc("setVelocityWeight", &MotionMatcher::setVelocityWeight);
166 mm.addFunc("getVelocityWeight", &MotionMatcher::getVelocityWeight);
167 mm.addFunc("setIgnoreRadius", &MotionMatcher::setIgnoreRadius);
168 mm.addFunc("getIgnoreRadius", &MotionMatcher::getIgnoreRadius);
169 mm.addFunc("setPoseReselectHistory", [bind](MotionMatcher* self, float seconds) {
170 return script::projectResult(bind.vm(), self->setPoseReselectHistory(seconds));
171 });
172 mm.addFunc("getPoseReselectHistory", &MotionMatcher::getPoseReselectHistory);
173 mm.addFunc("getMatchedFrame", &MotionMatcher::getMatchedFrame);
174 mm.addFunc("getMatchedClipIndex", &MotionMatcher::getMatchedClipIndex);
175 mm.addFunc("getMatchedTime", &MotionMatcher::getMatchedTime);
176 mm.addFunc("getLastSearchCost", &MotionMatcher::getLastSearchCost);
177 mm.addFunc("getPose", &MotionMatcher::getPose);
178 mm.addFunc("search", &MotionMatcher::search);
179 mm.addFunc("update", &MotionMatcher::update);
180 mm.addFunc("setFeatureQuery", [bind](MotionMatcher* self, AnimPose* current, AnimPose* previous, float dt,
181 ssq::Array entries, ssq::Array curveEntries) {
182 try {
183 if (!current || !previous || entries.size() > 256)
184 throw std::runtime_error("feature query requires two poses and bounded trajectory samples");
185 std::vector<MotionFeatureTrajectorySample> samples;
186 for (std::size_t i = 0; i < entries.size(); ++i) {
187 auto item = entries.get<ssq::Array>(i);
188 if (item.size() != 8) throw std::runtime_error("feature sample requires time,x,y,z,vx,vy,vz,yaw");
189 samples.push_back({item.get<float>(0), item.get<float>(1), item.get<float>(2), item.get<float>(3),
190 item.get<float>(4), item.get<float>(5), item.get<float>(6), item.get<float>(7)});
191 }
192 if (curveEntries.size() > 256) throw std::runtime_error("curve query requires bounded samples");
193 std::vector<MotionFeatureCurveSample> curves;
194 for (std::size_t i = 0; i < curveEntries.size(); ++i) {
195 auto item = curveEntries.get<ssq::Array>(i);
196 if (item.size() != 3) throw std::runtime_error("curve sample requires name,time,value");
197 curves.push_back({item.get<std::string>(0), item.get<float>(1), item.get<float>(2)});
198 }
199 return script::projectResult(bind.vm(), self->setFeatureQuery(*current, *previous, dt, samples, curves));
200 } catch (const std::exception& error) {
201 return bind.failInvalid(error.what());
202 }
203 });
204 mm.addFunc("setLocomotionQuery", [bind](MotionMatcher* self, AnimPose* current, AnimPose* previous, float dt,
205 ssq::Array entries) {
206 try {
207 if (!current || !previous || entries.size() != 5)
208 throw std::runtime_error("locomotion query requires two poses and five trajectory samples");
209 std::array<MotionLocomotionSample, 5> samples;
210 for (std::size_t i = 0; i < 5; ++i) {
211 auto item = entries.get<ssq::Array>(i);
212 if (item.size() != 7) throw std::runtime_error("locomotion sample requires x,y,z,vx,vy,vz,yaw");
213 samples[i] = {item.get<float>(0), item.get<float>(1), item.get<float>(2), item.get<float>(3),
214 item.get<float>(4), item.get<float>(5), item.get<float>(6)};
215 }
216 return script::projectResult(bind.vm(), self->setLocomotionQuery(*current, *previous, dt, samples));
217 } catch (const std::exception& error) {
218 return bind.failInvalid(error.what());
219 }
220 });
221 mm.addFunc("setCandidateRanges", [bind](MotionMatcher* self, ssq::Array entries) {
222 try {
223 if (entries.size() > 100000) throw std::runtime_error("too many candidate ranges");
224 std::vector<MotionSearchRange> ranges;
225 for (std::size_t i = 0; i < entries.size(); ++i) {
226 auto item = entries.get<ssq::Array>(i);
227 if (item.size() != 9)
228 throw std::runtime_error(
229 "candidate range requires "
230 "clip,start,end,bias,disableReselection,transitionBlocks,continuingBias,costOverrides,"
231 "continuingCostOverrides");
232 MotionSearchRange range{item.get<int>(0), item.get<float>(1), item.get<float>(2), item.get<float>(3),
233 item.get<bool>(4)};
234 auto blocks = item.get<ssq::Array>(5);
235 if (blocks.size() > 100000) throw std::runtime_error("too many transition blocks");
236 for (std::size_t j = 0; j < blocks.size(); ++j) {
237 auto block = blocks.get<ssq::Array>(j);
238 if (block.size() != 2) throw std::runtime_error("transition block requires start,end");
239 range.transitionBlocks.push_back({block.get<float>(0), block.get<float>(1)});
240 }
241 range.continuingCostBias = item.get<float>(6);
242 for (std::size_t field : {std::size_t{7}, std::size_t{8}}) {
243 auto values = item.get<ssq::Array>(field);
244 if (values.size() > 100000) throw std::runtime_error("too many cost override intervals");
245 auto& destination = field == 7 ? range.costOverrides : range.continuingCostOverrides;
246 for (std::size_t j = 0; j < values.size(); ++j) {
247 auto value = values.get<ssq::Array>(j);
248 if (value.size() != 3) throw std::runtime_error("cost override requires start,end,bias");
249 destination.push_back({value.get<float>(0), value.get<float>(1), value.get<float>(2)});
250 }
251 }
252 ranges.push_back(std::move(range));
253 }
254 return script::projectResult(bind.vm(), self->setCandidateRanges(ranges),
255 [](int count) { return Value(count); });
256 } catch (const std::exception& error) {
257 return bind.failInvalid(error.what());
258 }
259 });
260 mm.addFunc("setQueryPose", [](MotionMatcher* self, AnimPose* pose) {
261 if (!pose) throw std::runtime_error("query pose is required");
262 auto configured = self->setQueryPose(*pose);
263 if (!configured) throw std::runtime_error(configured.status().describe());
264 });
265 mm.addFunc("setTrajectory", [bind](MotionMatcher* self, ssq::Array entries) {
266 try {
267 if (entries.size() != 3) throw std::runtime_error("trajectory requires three samples");
268 std::array<MotionTrajectorySample, 3> samples;
269 for (std::size_t i = 0; i < 3; ++i) {
270 auto item = entries.get<ssq::Array>(i);
271 if (item.size() != 3) throw std::runtime_error("trajectory sample requires x,z,yaw");
272 samples[i] = {item.get<float>(0), item.get<float>(1), item.get<float>(2)};
273 }
274 return script::projectResult(bind.vm(), self->setTrajectory(samples));
275 } catch (const std::exception& error) {
276 return bind.failInvalid(error.what());
277 }
278 });
279}
280} // namespace eve::animation
double value
eve::EntitySpatialPose pose
std::unordered_map< std::string, QuestRuntime > entries
std::map< std::string, Var > values
std::string layout
float maximum[3]
float minimum[3]
HexVec3 left
HexVec3 right
std::int32_t c
TokenKind kind
Range range
graphics::Canvas * previous
std::string error
Definition Package.cpp:60
std::string path
Definition PlayHost.cpp:110
double current
std::uint32_t count
Per-translation-unit VM + diagnostic-source helper for Squirrel bindings.
The single Squirrel projection for common Result, Status and Value.
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
const UnitySourceAsset & source
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:17
Baked motion-matching feature database from one or more AnimClips. Feature layout per frame: [0....
void setRootBoneByName(const std::string &name)
Sets the root bone by name.
eve::Result< int > setFeatureNormalizationRanges(std::span< const MotionNormalizationRange > ranges)
Atomically select weighted source ranges used to normalize a variable feature layout.
bool hasFeatureLayout() const
Whether this database uses an explicitly configured variable layout.
void addFeatureBone(int boneIndex)
Include bone world position in pose features (by index).
float getFrameTime(int frameIndex) const
Returns the frame time.
bool isBaked() const
True when baked.
int getFeatureBoneCount() const
Returns the feature bone count.
eve::Result< void > setFeatureLayout(const MotionFeatureLayout &layout)
Atomically configure an owning variable feature layout before bake.
int getFeatureSize() const
Returns the feature size.
int getRootBone() const
Returns the root bone.
void addFeatureBoneByName(const std::string &name)
Adds feature bone by name.
void bake()
Bake all clips into searchable frames. Call after addClip / feature bones.
bool hasLocomotionFeatures() const
Whether this database uses the 30-dimensional locomotion layout.
int getFeatureBone(int index) const
Returns the feature bone.
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.
eve::Result< void > setLocomotionFeatures(int leftFoot, int rightFoot, int pelvis)
Configure the locomotion layout before baking or creating matchers.
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.
int getFrameClipIndex(int frameIndex) const
Returns the frame clip index.
int getClipCount() const
Returns the clip count.
Runtime motion matching player: builds a query feature from current pose + desired trajectory,...
void search()
Force an immediate search (also called periodically from update).
void update(float dt)
Legacy seconds facade; explicitly forwards to advance().
void setBlendTime(float seconds)
Sets the blend time.
float getMatchedTime() const
Returns the matched time.
void setDesiredYaw(float yaw)
Desired facing yaw (radians, Y-up).
int getMatchedFrame() const
Returns the matched frame.
float getDesiredVelocityX() const
Returns the desired velocity x.
void setDesiredVelocity(float x, float z)
Desired planar velocity in character/world XZ (units/sec).
void setVelocityWeight(float w)
Sets the velocity weight.
void setPoseWeight(float w)
Sets the pose weight.
void setTrajectoryWeight(float w)
Sets the trajectory weight.
AnimPose * getPose()
Returns the pose.
float getSearchInterval() const
Returns the search interval.
void setIgnoreRadius(int frames)
Keep continuous playback within this frame radius of the live playhead, including loop seams.
float getDesiredYaw() const
Returns the desired yaw.
float getVelocityWeight() const
Returns the velocity weight.
int getIgnoreRadius() const
Returns the ignore radius.
float getBlendTime() const
Returns the blend time.
int getMatchedClipIndex() const
Returns the matched clip index.
float getPlayRateMaximum() const
Returns the play rate maximum.
float getLastSearchCost() const
Returns the last search cost.
float getPoseWeight() const
Returns the pose weight.
void setSearchInterval(float seconds)
Sets the search interval.
float getTrajectoryWeight() const
Returns the trajectory weight.
float getDesiredVelocityZ() const
Returns the desired velocity z.
float getPlayRate() const
Returns the play rate.
Pins the active VM and a stable diagnostic source for one expose() TU.
void exposeAnimInertializerBindings(ssq::Table &table)
Register the owning AnimInertializer Squirrel surface.
void exposeOrientationWarpingBindings(ssq::Table &table)
Register the owning OrientationWarping Squirrel surface.
void exposeMotionMatcherBindings(ssq::Table &table)
Register the synchronous MotionMatcher Squirrel surface.
std::unique_ptr< T > Owned
C++ owning storage used by an object factory.
ssq::Table projectResult(HSQUIRRELVM vm, Result< void > &&result)
Consume and project a void native Result using the common schema.
Owning configuration for one ordered motion feature channel.
Owning ordered feature layout; copied atomically by a database.
Owning candidate interval in seconds in a database clip; endpoints inclusive.
std::vector< MotionTransitionBlock > transitionBlocks
Owned transition blocks, unioned across all ranges of this clip; continuation remains eligible.