载入中...
搜索中...
未找到
SpriteAnim.cpp
浏览该文件的文档.
2
7#include "common/Exception.h"
8#include "graphics/Quad.h"
10
11#include <cmath>
12#include <algorithm>
13
14namespace eve::animation {
15
16SpriteAnim::SpriteAnim() = default;
17
19 if (owner_) owner_->unregisterSpriteAnim(this);
20}
21
22void SpriteAnim::setSheet(SpriteSheet *sheet) { sheet_ = sheet; }
23
25 if (!clip) throw Exception("SpriteAnim.play: clip is null");
26 clip_ = clip;
27 time_ = 0.f;
28 playing_ = true;
29 paused_ = false;
30 finished_ = false;
31 loopCount_ = 0;
32 pendingLoops_ = 0;
33 pendingComplete_ = false;
34 speedCurveTime_ = 0.f;
35 refreshFrame();
36 syncBoundQuad();
37}
38
40 play(clip);
41 if (speed_ >= 0.f) speed_ = speed_ == 0.f ? -1.f : -speed_;
42 time_ = clip_->getDuration();
43 if (time_ > 0.f) time_ = std::nextafter(time_, 0.f);
44 refreshFrame();
45 syncBoundQuad();
46}
47
49 playing_ = false;
50 paused_ = false;
51 finished_ = false;
52 time_ = 0.f;
53 clipFrame_ = -1;
54}
55
57 if (playing_) paused_ = true;
58}
59
61 if (playing_ && paused_) paused_ = false;
62}
63
64void SpriteAnim::setSpeed(float speed) {
65 speed_ = speed;
66}
67
68void SpriteAnim::addSpeedCurveKey(float seconds, float multiplier) {
69 if (seconds < 0.f) throw Exception("SpriteAnim.addSpeedCurveKey: time must be >= 0");
70 speedCurve_.push_back({seconds, multiplier});
71 std::stable_sort(speedCurve_.begin(), speedCurve_.end(),
72 [](const SpeedKey &a, const SpeedKey &b) { return a.time < b.time; });
73}
74
76 speedCurve_.clear();
77 speedCurveTime_ = 0.f;
78}
79
80void SpriteAnim::resetSpeedCurve() { speedCurveTime_ = 0.f; }
81
82float SpriteAnim::sampleSpeedCurve(float seconds) const {
83 if (speedCurve_.empty()) return 1.f;
84 if (speedCurve_.size() == 1 || seconds <= speedCurve_.front().time)
85 return speedCurve_.front().value;
86
87 const float end = speedCurve_.back().time;
88 float t = seconds;
89 if (speedCurveLoop_ && end > 0.f) t = std::fmod(t, end);
90 else if (t >= end) return speedCurve_.back().value;
91
92 for (size_t i = 1; i < speedCurve_.size(); ++i) {
93 if (t <= speedCurve_[i].time) {
94 const SpeedKey &a = speedCurve_[i - 1];
95 const SpeedKey &b = speedCurve_[i];
96 const float span = b.time - a.time;
97 if (span <= 0.f) return b.value;
98 float u = (t - a.time) / span;
99 if (speedCurveInterpolation_ == "smooth") u = u * u * (3.f - 2.f * u);
100 else if (speedCurveInterpolation_ == "cubic")
101 u = u * u * u * (u * (u * 6.f - 15.f) + 10.f);
102 return a.value + (b.value - a.value) * u;
103 }
104 }
105 return speedCurve_.back().value;
106}
107
108float SpriteAnim::getSpeedCurveValue() const { return sampleSpeedCurve(speedCurveTime_); }
109
110void SpriteAnim::setSpeedCurveInterpolation(const std::string &mode) {
111 if (mode != "linear" && mode != "smooth" && mode != "cubic")
112 throw Exception("SpriteAnim.setSpeedCurveInterpolation: expected linear|smooth|cubic");
113 speedCurveInterpolation_ = mode;
114}
115
116void SpriteAnim::setFrame(int frame) {
117 if (!clip_ || frame < 0 || frame >= clip_->getFrameCount())
118 throw Exception("SpriteAnim.setFrame: frame out of range");
119 float t = 0.f;
120 for (int i = 0; i < frame; ++i) t += clip_->getFrameDuration(i);
121 setTime(t);
122}
123
124void SpriteAnim::step(int frames) {
125 if (!clip_ || clip_->getFrameCount() == 0) return;
126 int target = clipFrame_ + frames;
127 if (getLoop()) {
128 const int count = clip_->getFrameCount();
129 target = ((target % count) + count) % count;
130 } else target = std::max(0, std::min(target, clip_->getFrameCount() - 1));
132}
133
135void SpriteAnim::queue(SpriteClip *clip) { if (!clip) throw Exception("SpriteAnim.queue: clip is null"); queuedClip_ = clip; }
136std::string SpriteAnim::consumeEvent() { std::string value = pendingEvent_; pendingEvent_.clear(); return value; }
137
139 const bool value = pendingComplete_;
140 pendingComplete_ = false;
141 return value;
142}
143
145 const int value = pendingLoops_;
146 pendingLoops_ = 0;
147 return value;
148}
149
150void SpriteAnim::setTime(float seconds) {
151 if (seconds < 0.f) throw Exception("SpriteAnim.setTime: time must be >= 0");
152 time_ = seconds;
153 refreshFrame();
154 if (clipFrame_ >= 0) {
155 const std::string event = clip_->getEvent(clipFrame_);
156 if (!event.empty()) pendingEvent_ = event;
157 }
158 syncBoundQuad();
159}
160
162 loopOverride_ = true;
163 loopValue_ = loop;
164}
165
167 return loopOverride_ ? loopValue_ : (clip_ && clip_->getLoop());
168}
169
171 if (!clip_ || clipFrame_ < 0) return -1;
172 return clip_->getSheetFrame(clipFrame_);
173}
174
176 boundQuad_ = quad;
177 syncBoundQuad();
178}
179
180void SpriteAnim::unbindQuad() { boundQuad_ = nullptr; }
181void SpriteAnim::bindSprite(graphics::Renderable2D *sprite) { boundSprite_ = sprite; syncBoundQuad(); }
182
184 if (!quad) throw Exception("SpriteAnim.applyToQuad: quad is null");
185 if (!sheet_) throw Exception("SpriteAnim.applyToQuad: sheet is null");
186 int sf = getSheetFrame();
187 if (sf < 0) throw Exception("SpriteAnim.applyToQuad: no current frame");
188 sheet_->applyToQuad(quad, sf);
189}
190
191void SpriteAnim::refreshFrame() {
192 if (!clip_ || clip_->getFrameCount() == 0) {
193 clipFrame_ = -1;
194 return;
195 }
196
197 const float dur = clip_->getDuration();
198 if (dur <= 0.f) {
199 clipFrame_ = 0;
200 return;
201 }
202
203 const bool loop = getLoop();
204 float local = time_;
205 if (loop) {
206 local = std::fmod(local, dur);
207 if (local < 0.f) local += dur;
208 } else if (local >= dur) {
209 clipFrame_ = clip_->getFrameCount() - 1;
210 return;
211 } else if (local < 0.f) {
212 local = 0.f;
213 }
214
215 float acc = 0.f;
216 for (int i = 0; i < clip_->getFrameCount(); ++i) {
217 acc += clip_->getFrameDuration(i);
218 if (local < acc || i == clip_->getFrameCount() - 1) {
219 clipFrame_ = i;
220 return;
221 }
222 }
223 clipFrame_ = clip_->getFrameCount() - 1;
224}
225
226void SpriteAnim::syncBoundQuad() {
227 if (!boundQuad_ || !sheet_) return;
228 int sf = getSheetFrame();
229 if (sf < 0) return;
230 sheet_->applyToQuad(boundQuad_, sf);
231 if (boundSprite_) {
232 boundSprite_->setFrameLayout(sheet_->getFrameSourceWidth(sf), sheet_->getFrameSourceHeight(sf),
233 sheet_->getFrameWidth(sf), sheet_->getFrameHeight(sf),
234 sheet_->getFrameOffsetX(sf), sheet_->getFrameOffsetY(sf));
235 }
236}
237
238bool SpriteAnim::updateUnchecked(float dt) {
239 if (dt < 0.f) throw Exception("SpriteAnim.update: dt must be >= 0");
240 if (!playing_ || paused_ || !clip_) return playing_ || paused_;
241
242 const float previous = time_;
243 const int previousFrame = clipFrame_;
244 const float curveValue = sampleSpeedCurve(speedCurveTime_ + dt * 0.5f);
245 speedCurveTime_ += dt;
246 time_ += dt * speed_ * curveValue;
247 const float dur = clip_->getDuration();
248 const bool loop = getLoop();
249
250 if (dur <= 0.f) {
251 refreshFrame();
252 syncBoundQuad();
253 return true;
254 }
255
256 if (loop) {
257 const int beforeCycle = static_cast<int>(std::floor(previous / dur));
258 const int afterCycle = static_cast<int>(std::floor(time_ / dur));
259 const int crossed = std::abs(afterCycle - beforeCycle);
260 if (crossed > 0) {
261 loopCount_ += crossed;
262 pendingLoops_ += crossed;
263 }
264 } else if ((speed_ >= 0.f && time_ >= dur) || (speed_ < 0.f && time_ <= 0.f)) {
265 time_ = speed_ < 0.f ? 0.f : dur;
266 finished_ = true;
267 playing_ = false;
268 pendingComplete_ = true;
269 if (queuedClip_) {
270 SpriteClip *next = queuedClip_;
271 queuedClip_ = nullptr;
272 loopOverride_ = false;
273 play(next);
274 return true;
275 }
276 refreshFrame();
277 loopOverride_ = false;
278 syncBoundQuad();
279 return false;
280 }
281
282 refreshFrame();
283 if (clipFrame_ != previousFrame && clipFrame_ >= 0) {
284 const std::string event = clip_->getEvent(clipFrame_);
285 if (!event.empty()) pendingEvent_ = event;
286 }
287 syncBoundQuad();
288 return true;
289}
290
292 auto seconds = detail::secondsForStep(step, hasLastTick_, lastTick_, "SpriteAnim");
293 if (!seconds) return eve::Result<void>::failure(seconds.status());
294 updateUnchecked(std::move(seconds).takeValue());
295 lastTick_ = step.tick;
296 hasLastTick_ = true;
298}
299
300bool SpriteAnim::update(float dt) {
301 auto step = detail::legacyStep(dt, hasLastTick_, lastTick_, "SpriteAnim");
302 if (!step) {
303 step.ignore("legacy SpriteAnim update");
304 return playing_ || paused_;
305 }
306 auto result = advance(std::move(step).takeValue());
307 result.ignore("legacy SpriteAnim update");
308 return playing_ || paused_;
309}
310
311} // namespace eve::animation
LogicalId target
double value
glm::vec4 clip
float u
Definition Grass.cpp:233
std::string local
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
graphics::Canvas * previous
float t
std::uint32_t count
float step
Definition TreeMesh.cpp:314
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
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
void bindQuad(graphics::Quad *quad)
Keep this Quad's viewport updated each update/play.
bool update(float dt)
Legacy seconds facade; explicitly forwards to advance().
bool consumeCompleted()
Consume and clear the one-shot completion event.
void setLoop(bool loop)
Sets the loop.
void setTime(float seconds)
Sets the time.
void resetSpeedCurve()
Restart speed-curve sampling from zero without changing clip time.
void setSpeedCurveInterpolation(const std::string &mode)
Set speed-curve interpolation: linear, smooth, or cubic.
void play(SpriteClip *clip)
Play.
void clearSpeedCurve()
Remove all speed-curve keys and restore constant-speed playback.
void setSpeed(float speed)
Sets the speed.
void step(int frames)
Step.
void setSheet(SpriteSheet *sheet)
Optional sheet used by applyToQuad / bindQuad.
void queue(SpriteClip *clip)
Queue.
void addSpeedCurveKey(float seconds, float multiplier)
Add a piecewise-linear speed multiplier key at curve time in seconds.
float getSpeedCurveValue() const
Return the current sampled curve multiplier.
void unbindQuad()
Unbinds quad.
void bindSprite(graphics::Renderable2D *sprite)
Bind a Sprite2D so trimmed frame layout is synchronized automatically.
void applyToQuad(graphics::Quad *quad) const
Write current sheet frame into quad (requires sheet).
bool getLoop() const
Returns the loop.
std::string consumeEvent()
Consume event.
void playReverse(SpriteClip *clip)
Start at the clip end and play backward (speed becomes negative).
eve::Result< void > advance(const eve::SimulationStep &step)
Advance playback. Returns true while still active (playing or paused). Auto-applies to boundQuad when...
void playOnce(SpriteClip *clip)
Play once.
int consumeLooped()
Consume and clear pending loop events, returning their count.
int getSheetFrame() const
Sheet frame index for the current cell, or -1.
void setFrame(int clipFrame)
Sets the frame.
Named 2D frame sequence referencing SpriteSheet frame indices. Script type: SpriteClip.
Definition SpriteClip.h:17
int getSheetFrame(int index) const
Returns the sheet frame.
float getDuration() const
Returns the duration.
int getFrameCount() const
Returns the frame count.
Definition SpriteClip.h:58
std::string getEvent(int clipFrame) const
Return event name for a frame, or empty when none.
bool getLoop() const
Returns the loop.
Definition SpriteClip.h:35
float getFrameDuration(int index) const
Returns the frame duration.
Sprite-sheet / texture-atlas frame table (pixel rects).
Definition SpriteSheet.h:23
int getFrameOffsetX(int index) const
Returns the frame offset x.
int getFrameSourceWidth(int index) const
Returns the frame source width.
int getFrameWidth(int index) const
Returns the frame width.
int getFrameHeight(int index) const
Returns the frame height.
int getFrameOffsetY(int index) const
Returns the frame offset y.
void applyToQuad(graphics::Quad *quad, int frameIndex) const
Write pixel viewport into an existing Quad (does not allocate).
int getFrameSourceHeight(int index) const
Returns the frame source height.
Pixel-space sub-rectangle of a Texture (atlas cell / sprite frame). UV conversion uses the texture's ...
Definition Quad.h:11
Default renderable entity for declarative 2D sprites / solid quads.
void setFrameLayout(int sourceW, int sourceH, int trimW, int trimH, int offsetX, int offsetY)
Apply trimmed-frame layout while preserving the original canvas origin.
eve::Result< eve::SimulationStep > legacyStep(float seconds, bool hasLastTick, eve::SimulationTick lastTick, const char *owner)
Convert a legacy seconds call to the next local scheduler step.
eve::Result< float > secondsForStep(const eve::SimulationStep &step, bool hasLastTick, eve::SimulationTick lastTick, const char *owner)
Validate a scheduler step before an animation object mutates state.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
One deterministic fixed-step emitted by SimulationClock.
Definition Time.h:158