载入中...
搜索中...
未找到
OrientationWarping.cpp
浏览该文件的文档.
2
6
7#include "common/Diagnostic.h"
8
9#include <algorithm>
10#include <cmath>
11#include <vector>
12
13namespace eve::animation {
14namespace {
15constexpr int kMaxSpineBones = 32;
16constexpr int kMaxIkBones = 16;
17
20}
21
22float wrapPi(float radians) { return std::remainder(radians, 6.28318530718f); }
23
24float planarSpeed(float x, float z) { return std::sqrt(x * x + z * z); }
25
26float signedPlanarAngle(float fromX, float fromZ, float toX, float toZ) {
27 return wrapPi(std::atan2(toX, toZ) - std::atan2(fromX, fromZ));
28}
29
30float interpTo(float current, float target, float dt, float speed) {
31 const float delta = wrapPi(target - current);
32 if (speed <= 0.f) return target;
33 const float step = std::clamp(dt * speed, 0.f, 1.f);
34 return wrapPi(current + delta * step);
35}
36
37void setWorldRotation(AnimPose& pose, const AnimSkeleton& skeleton, int bone, float qx, float qy, float qz, float qw) {
38 const int parent = skeleton.getParent(bone);
39 float lx, ly, lz, lw;
40 if (parent < 0) {
41 lx = qx;
42 ly = qy;
43 lz = qz;
44 lw = qw;
45 } else {
46 const auto& parentWorld = pose.world(parent);
47 multiplyQuat(-parentWorld.qx, -parentWorld.qy, -parentWorld.qz, parentWorld.qw, qx, qy, qz, qw, lx, ly, lz, lw);
48 }
49 pose.setLocalRotation(bone, lx, ly, lz, lw);
50}
51
52void addWorldYaw(AnimPose& pose, const AnimSkeleton& skeleton, int bone, float yaw) {
53 pose.computeWorld(&skeleton);
54 const auto& world = pose.world(bone);
55 float qx, qy, qz, qw;
56 const float half = yaw * 0.5f;
57 multiplyQuat(0.f, std::sin(half), 0.f, std::cos(half), world.qx, world.qy, world.qz, world.qw, qx, qy, qz, qw);
58 setWorldRotation(pose, skeleton, bone, qx, qy, qz, qw);
59}
60
61bool validBoneList(const AnimSkeleton& skeleton, int root, std::span<const int> bones, int maxCount) {
62 if (bones.size() > static_cast<std::size_t>(maxCount)) return false;
63 for (std::size_t i = 0; i < bones.size(); ++i) {
64 if (bones[i] < 0 || bones[i] >= skeleton.getBoneCount() || bones[i] == root) return false;
65 for (std::size_t j = 0; j < i; ++j)
66 if (bones[i] == bones[j]) return false;
67 }
68 return true;
69}
70} // namespace
71
74 int rootBone = -1;
75 std::vector<int> spineBones;
76 std::vector<int> ikBones;
77 float distributedAlpha = 0.5f;
78 float angleThreshold = 2.35619449019f; // 135 degrees
79 float interpSpeed = 8.f;
80 float minSpeed = 0.1f;
81 float appliedAngle = 0.f;
82 float targetAngle = 0.f;
83 bool enabled = true;
84};
85
86OrientationWarping::OrientationWarping() : impl_(std::make_unique<Impl>()) {}
88
89eve::Result<void> OrientationWarping::configure(AnimSkeleton& skeleton, int rootBone, std::span<const int> spineBones,
90 std::span<const int> ikBones) {
91 if (rootBone < 0 || rootBone >= skeleton.getBoneCount()) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "root bone must belong to the skeleton",
92 "orientationWarping", {}, "animation"));
93 if (!validBoneList(skeleton, rootBone, spineBones, kMaxSpineBones))
94 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "spine bones must be unique valid indices distinct from the root",
95 "orientationWarping", {}, "animation"));
96 if (!validBoneList(skeleton, rootBone, ikBones, kMaxIkBones))
97 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "ik bones must be unique valid indices distinct from the root",
98 "orientationWarping", {}, "animation"));
99 for (int spine : spineBones)
100 for (int ik : ikBones)
101 if (spine == ik) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "a bone cannot be both spine and ik",
102 "orientationWarping", {}, "animation"));
103 impl_->skeleton = &skeleton;
104 impl_->rootBone = rootBone;
105 impl_->spineBones.assign(spineBones.begin(), spineBones.end());
106 impl_->ikBones.assign(ikBones.begin(), ikBones.end());
107 impl_->appliedAngle = 0.f;
108 impl_->targetAngle = 0.f;
110}
111
113 if (!std::isfinite(alpha) || alpha < 0.f || alpha > 1.f) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "distributed alpha must be finite in [0,1]",
114 "orientationWarping", {}, "animation"));
115 impl_->distributedAlpha = alpha;
117}
118
119float OrientationWarping::getDistributedAlpha() const { return impl_->distributedAlpha; }
120
122 if (!std::isfinite(radians) || radians <= 0.f || radians > 3.14159265359f + 1e-6f)
123 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "angle threshold must be finite in (0, pi]",
124 "orientationWarping", {}, "animation"));
125 impl_->angleThreshold = radians;
127}
128
129float OrientationWarping::getAngleThreshold() const { return impl_->angleThreshold; }
130
132 if (!std::isfinite(speed) || speed < 0.f || speed > 100.f)
133 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "rotation interp speed must be finite in [0,100]",
134 "orientationWarping", {}, "animation"));
135 impl_->interpSpeed = speed;
137}
138
139float OrientationWarping::getRotationInterpSpeed() const { return impl_->interpSpeed; }
140
142 if (!std::isfinite(metresPerSecond) || metresPerSecond < 0.f || metresPerSecond > 100.f)
143 return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "minimum root-motion speed must be finite in [0,100]",
144 "orientationWarping", {}, "animation"));
145 impl_->minSpeed = metresPerSecond;
147}
148
149float OrientationWarping::getMinRootMotionSpeed() const { return impl_->minSpeed; }
150
151void OrientationWarping::setEnabled(bool enabled) { impl_->enabled = enabled; }
152bool OrientationWarping::isEnabled() const { return impl_->enabled; }
153
155 impl_->appliedAngle = 0.f;
156 impl_->targetAngle = 0.f;
157}
158
159eve::Result<void> OrientationWarping::apply(AnimPose& pose, float locomotionX, float locomotionZ, float animatedX,
160 float animatedZ, float dt) {
161 if (!impl_->skeleton) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "configure a skeleton before applying orientation warp",
162 "orientationWarping", {}, "animation"));
163 if (pose.getBoneCount() != impl_->skeleton->getBoneCount()) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "pose bone count must match the skeleton",
164 "orientationWarping", {}, "animation"));
165 if (!std::isfinite(dt) || dt < 0.f || dt > 1.f) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "dt must be finite in [0,1]",
166 "orientationWarping", {}, "animation"));
167 for (float value : {locomotionX, locomotionZ, animatedX, animatedZ})
168 if (!std::isfinite(value)) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "locomotion and animated velocities must be finite",
169 "orientationWarping", {}, "animation"));
170 for (int i = 0; i < pose.getBoneCount(); ++i) {
171 const auto& t = pose.local(i);
172 for (float value : {t.px, t.py, t.pz, t.qx, t.qy, t.qz, t.qw, t.sx, t.sy, t.sz})
173 if (!std::isfinite(value)) return eve::Result<void>::failure(eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "pose locals must be finite",
174 "orientationWarping", {}, "animation"));
175 }
176
177 float target = 0.f;
178 if (planarSpeed(locomotionX, locomotionZ) >= impl_->minSpeed &&
179 planarSpeed(animatedX, animatedZ) >= impl_->minSpeed) {
180 const float angle = signedPlanarAngle(animatedX, animatedZ, locomotionX, locomotionZ);
181 if (std::fabs(angle) <= impl_->angleThreshold) target = angle;
182 }
183 const float applied = interpTo(impl_->appliedAngle, target, dt, impl_->interpSpeed);
184 if (!impl_->enabled) {
185 impl_->targetAngle = target;
186 impl_->appliedAngle = applied;
188 }
189 if (std::fabs(applied) <= 1e-6f) {
190 impl_->targetAngle = target;
191 impl_->appliedAngle = 0.f;
193 }
194
195 AnimPose next;
196 next.copyFrom(&pose);
197 next.computeWorld(impl_->skeleton);
198 struct SavedRotation {
199 int bone = 0;
200 float qx = 0.f, qy = 0.f, qz = 0.f, qw = 1.f;
201 };
202 std::vector<SavedRotation> ikWorld;
203 ikWorld.reserve(impl_->ikBones.size());
204 for (int bone : impl_->ikBones) {
205 const auto& world = next.world(bone);
206 ikWorld.push_back({bone, world.qx, world.qy, world.qz, world.qw});
207 }
208
209 const float spineShare = impl_->spineBones.empty() ? 0.f : impl_->distributedAlpha;
210 addWorldYaw(next, *impl_->skeleton, impl_->rootBone, applied * (1.f - spineShare));
211 if (!impl_->spineBones.empty()) {
212 const float perBone = applied * spineShare / static_cast<float>(impl_->spineBones.size());
213 for (int bone : impl_->spineBones) addWorldYaw(next, *impl_->skeleton, bone, perBone);
214 }
215 next.computeWorld(impl_->skeleton);
216 for (const auto& saved : ikWorld)
217 setWorldRotation(next, *impl_->skeleton, saved.bone, saved.qx, saved.qy, saved.qz, saved.qw);
218 next.computeWorld(impl_->skeleton);
219
220 pose.copyFrom(&next);
221 impl_->targetAngle = target;
222 impl_->appliedAngle = applied;
224}
225
226float OrientationWarping::getAppliedAngle() const { return impl_->appliedAngle; }
227float OrientationWarping::getTargetAngle() const { return impl_->targetAngle; }
228AnimSkeleton* OrientationWarping::getSkeleton() const { return impl_->skeleton; }
229int OrientationWarping::getRootBone() const { return impl_->rootBone; }
230int OrientationWarping::getSpineBoneCount() const { return static_cast<int>(impl_->spineBones.size()); }
231int OrientationWarping::getIkBoneCount() const { return static_cast<int>(impl_->ikBones.size()); }
232} // namespace eve::animation
LogicalId target
double value
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
int root
Definition AnimSmr.cpp:119
eve::EntitySpatialPose pose
Stable, structured diagnostics shared by engine modules.
DiagnosticCode code
std::vector< Bone > bones
std::uint32_t bone
std::int32_t parent
World3D * world
float t
double current
float step
Definition TreeMesh.cpp:314
float qy
float qx
float qw
float angle
float qz
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
Evaluated local (and optional world) pose for an AnimSkeleton. Script type: AnimPose.
Definition AnimPose.h:17
3D bone hierarchy + bind-pose local TRS for skeletal animation. Independent of ik::Skeleton3D (FABRIK...
int getBoneCount() const
Returns the bone count.
int getSpineBoneCount() const
Returns the spine bone count.
eve::Result< void > setDistributedAlpha(float alpha)
Set how much of the warp is distributed onto the spine chain.
~OrientationWarping()
Release owned interpolation state; the borrowed skeleton is not accessed.
AnimSkeleton * getSkeleton() const
Borrowed skeleton from the last successful configure, or null. @ownership Borrowed; ownership remains...
void reset()
Clear smoothed yaw so the next apply starts from zero.
eve::Result< void > apply(AnimPose &pose, float locomotionX, float locomotionZ, float animatedX, float animatedZ, float dt)
Warp a local pose so animated planar velocity aligns with locomotion velocity.
float getAppliedAngle() const
Smoothed yaw last applied to a pose, radians, positive from +Z toward +X.
eve::Result< void > setAngleThreshold(float radians)
Set the inclusive planar-angle magnitude that disables warping.
eve::Result< void > setMinRootMotionSpeed(float metresPerSecond)
Set the planar speed below which either direction is treated as stationary.
eve::Result< void > setRotationInterpSpeed(float speed)
Set how quickly the applied yaw approaches the target, matching Unreal FInterpTo.
int getIkBoneCount() const
Returns the ik bone count.
void setEnabled(bool enabled)
Enable or disable pose mutation. Disabled apply calls still recompute the target.
bool isEnabled() const
True when enabled.
eve::Result< void > configure(AnimSkeleton &skeleton, int rootBone, std::span< const int > spineBones={}, std::span< const int > ikBones={})
Atomically bind a skeleton and bone lists used by later apply calls.
OrientationWarping()
Construct an unconfigured warper with no borrowed skeleton.
float getTargetAngle() const
Unsmoothed target yaw from the last successful apply, radians.
int getRootBone() const
Returns the root bone.
void multiplyQuat(float ax, float ay, float az, float aw, float bx, float by, float bz, float bw, float &ox, float &oy, float &oz, float &ow)
Hamilton product a * b for unit quaternions (xyzw).
Definition AnimMath.h:94
StatusCode
Stable outcome category for an operation.
Definition Status.h:27
bool enabled