17#define M_PI 3.14159265358979323846
23 if (!skeleton_)
throw Exception(
"SpineAnim: skeleton is null");
27 if (owner_) owner_->unregisterSpineAnim(
this);
32 pageTextures_.clear();
33 if (atlas_) pageTextures_.resize(
static_cast<size_t>(atlas_->
getPageCount()),
nullptr);
37 if (!atlas_)
throw Exception(
"SpineAnim.setPageTexture: atlas is null");
38 if (pageIndex < 0 || pageIndex >= atlas_->
getPageCount())
39 throw Exception(
"SpineAnim.setPageTexture: page index %d out of range", pageIndex);
40 if (pageTextures_.size() <
static_cast<size_t>(atlas_->
getPageCount()))
41 pageTextures_.resize(
static_cast<size_t>(atlas_->
getPageCount()),
nullptr);
42 pageTextures_[
static_cast<size_t>(pageIndex)] = texture;
46 if (!atlas_)
throw Exception(
"SpineAnim.setPageTextureByName: atlas is null");
53 throw Exception(
"SpineAnim.setPageTextureByName: unknown page '%s'", pageName.c_str());
57 if (pageIndex < 0 || pageIndex >=
static_cast<int>(pageTextures_.size()))
return nullptr;
58 return pageTextures_[
static_cast<size_t>(pageIndex)];
62 if (!skeleton_ || !skeleton_->
getData())
return false;
64 if (
idx < 0)
return false;
66 animName_ = animationName;
85 if (playing_) paused_ =
true;
89 if (playing_ && paused_) paused_ =
false;
93 if (speed < 0.f)
throw Exception(
"SpineAnim.setSpeed: speed must be >= 0");
98 if (seconds < 0.f)
throw Exception(
"SpineAnim.setTime: time must be >= 0");
100 if (playing_)
apply();
104 if (animIndex_ < 0 || !skeleton_ || !skeleton_->getData())
return 0.f;
125float SpineAnim::sampleFloat(
const std::vector<SpineSkeletonData::FloatKey> &keys,
float time,
127 if (keys.empty())
return fallback;
128 if (
time <= keys.front().time)
return keys.front().value;
129 if (
time >= keys.back().time)
return keys.back().value;
130 for (
size_t i = 0; i + 1 < keys.size(); ++i) {
131 const auto &
a = keys[i];
132 const auto &
b = keys[i + 1];
133 if (
time >
b.time)
continue;
134 if (
a.stepped ||
b.time <=
a.time)
return a.value;
135 float t = (
time -
a.time) / (
b.time -
a.time);
136 return a.value + (
b.value -
a.value) *
t;
138 return keys.back().value;
141void SpineAnim::sampleTranslate(
const std::vector<SpineSkeletonData::TranslateKey> &keys,
142 float time,
float &
x,
float &
y) {
143 if (keys.empty())
return;
144 if (
time <= keys.front().time) {
149 if (
time >= keys.back().time) {
154 for (
size_t i = 0; i + 1 < keys.size(); ++i) {
155 const auto &
a = keys[i];
156 const auto &
b = keys[i + 1];
157 if (
time >
b.time)
continue;
158 if (
a.stepped ||
b.time <=
a.time) {
163 float t = (
time -
a.time) / (
b.time -
a.time);
164 x =
a.x + (
b.x -
a.x) *
t;
165 y =
a.y + (
b.y -
a.y) *
t;
172void SpineAnim::sampleScale(
const std::vector<SpineSkeletonData::ScaleKey> &keys,
float time,
173 float &
x,
float &
y) {
174 if (keys.empty())
return;
175 if (
time <= keys.front().time) {
180 if (
time >= keys.back().time) {
185 for (
size_t i = 0; i + 1 < keys.size(); ++i) {
186 const auto &
a = keys[i];
187 const auto &
b = keys[i + 1];
188 if (
time >
b.time)
continue;
189 if (
a.stepped ||
b.time <=
a.time) {
194 float t = (
time -
a.time) / (
b.time -
a.time);
195 x =
a.x + (
b.x -
a.x) *
t;
196 y =
a.y + (
b.y -
a.y) *
t;
203std::string SpineAnim::sampleAttachment(
const std::vector<SpineSkeletonData::AttachmentKey> &keys,
204 float time,
const std::string &fallback) {
205 if (keys.empty())
return fallback;
206 std::string cur = fallback;
207 for (
const auto &k : keys) {
208 if (k.time >
time)
break;
215 if (!skeleton_ || !skeleton_->
getData())
return;
218 if (animIndex_ >= 0) {
221 const float dur = anim.duration;
222 if (loop_ && dur > 0.f) {
223 t = std::fmod(
t, dur);
224 if (
t < 0.f)
t += dur;
225 }
else if (!loop_ && dur > 0.f &&
t > dur) {
229 for (
const auto &bt : anim.bones) {
242 const auto &setup = skeleton_->
getData()->
bone(bt.boneIndex);
243 if (!bt.translate.empty()) {
244 float tx = 0.f, ty = 0.f;
245 sampleTranslate(bt.translate,
t, tx, ty);
249 if (!bt.rotate.empty()) {
250 float angle = sampleFloat(bt.rotate,
t, 0.f);
251 r = setup.rotation +
angle;
253 if (!bt.scale.empty()) {
254 float kx = 1.f, ky = 1.f;
255 sampleScale(bt.scale,
t, kx, ky);
256 sx = setup.scaleX * kx;
257 sy = setup.scaleY * ky;
266 for (
const auto &st : anim.slots) {
268 std::string
name = sampleAttachment(st.attachment,
t, setupName);
277bool SpineAnim::updateUnchecked(
float dt) {
278 if (dt < 0.f)
throw Exception(
"SpineAnim.update: dt must be >= 0");
279 if (!playing_ || paused_)
return playing_ || paused_;
281 time_ += dt * speed_;
283 if (!loop_ && dur > 0.f && time_ >= dur) {
297 updateUnchecked(std::move(seconds).takeValue());
298 lastTick_ =
step.tick;
306 step.ignore(
"legacy SpineAnim update");
307 return playing_ || paused_;
309 auto result =
advance(std::move(
step).takeValue());
310 result.ignore(
"legacy SpineAnim update");
311 return playing_ || paused_;
316 std::vector<graphics::DrawItem2D> items;
321void SpineAnim::rebuildDrawSlots() {
323 if (!skeleton_)
return;
325 for (
int si = 0; si < skeleton_->
getSlotCount(); ++si) {
329 const auto &
bone = skeleton_->bones_[
static_cast<size_t>(boneIndex)];
332 ds.regionName = att->path.empty() ? att->name : att->path;
335 int regionIndex = -1;
336 float regionW = att->width, regionH = att->height;
337 float origW = att->width, origH = att->height;
338 bool rotated =
false;
340 regionIndex = atlas_->
findRegion(ds.regionName);
341 ds.region = regionIndex;
342 if (regionIndex >= 0) {
345 ds.rotated = rotated;
346 regionW =
static_cast<float>(atlas_->
getRegionWidth(regionIndex));
354 tw = tex->getWidth();
355 th = tex->getHeight();
357 if (tw > 0 && th > 0)
358 atlas_->
getRegionUV(regionIndex, tw, th, ds.u0, ds.v0, ds.u1, ds.v1);
361 if (origW <= 0.f) origW = att->width;
362 if (origH <= 0.f) origH = att->height;
369 const float regionScaleX = (origW > 0.f) ? (att->width / origW) * att->scaleX : att->scaleX;
370 const float regionScaleY = (origH > 0.f) ? (att->height / origH) * att->scaleY : att->scaleY;
372 const float localX = -att->width * 0.5f * att->scaleX;
373 const float localY = -att->height * 0.5f * att->scaleY;
374 const float localX2 = localX + regionW * regionScaleX;
375 const float localY2 = localY + regionH * regionScaleY;
377 const float ar = att->rotation *
static_cast<float>(
M_PI / 180.0);
378 const float ac = std::cos(ar);
379 const float as = std::sin(ar);
382 auto toScreen = [&](
float lx,
float ly) -> std::pair<float, float> {
383 const float rx = lx *
ac - ly * as + att->x;
384 const float ry = lx * as + ly *
ac + att->y;
387 return {x_ +
wx * scaleX_, y_ + (flipY_ ? -
wy :
wy) * scaleY_};
390 const auto bl = toScreen(localX, localY);
391 const auto br = toScreen(localX2, localY);
392 const auto tl = toScreen(localX, localY2);
393 const auto tr = toScreen(localX2, localY2);
401 ds.x = (bl.first + br.first + tl.first +
tr.first) * 0.25f;
402 ds.y = (bl.second + br.second + tl.second +
tr.second) * 0.25f;
403 const float dx =
tr.first - tl.first;
404 const float dy =
tr.second - tl.second;
405 ds.w = std::sqrt(
dx *
dx +
dy *
dy);
406 const float hx = bl.first - tl.first;
407 const float hy = bl.second - tl.second;
408 ds.h = std::sqrt(hx * hx + hy * hy);
409 ds.rotation = (ds.w > 1e-6f) ? std::atan2(
dy,
dx) *
static_cast<float>(180.0 /
M_PI) : 0.f;
413 drawSlots_.push_back(ds);
419 for (
const DrawSlot &ds : drawSlots_) {
422 item.
x = ds.x - ds.w * 0.5f;
423 item.
y = ds.y - ds.h * 0.5f;
426 item.
depthY = ds.y + ds.h * 0.5f;
427 item.
order = ds.order;
430 item.
color = {r_, g_, b_, a_};
446 std::swap(item.
u0, item.
u1);
448 std::swap(item.
v0, item.
v1);
454void SpineAnim::checkDrawSlot(
int index)
const {
456 throw Exception(
"SpineAnim: draw slot index %d out of range (count=%d)",
index,
463 checkDrawSlot(
index);
464 return drawSlots_[
static_cast<size_t>(
index)].
x;
467 checkDrawSlot(
index);
468 return drawSlots_[
static_cast<size_t>(
index)].
y;
471 checkDrawSlot(
index);
472 return drawSlots_[
static_cast<size_t>(
index)].
w;
475 checkDrawSlot(
index);
476 return drawSlots_[
static_cast<size_t>(
index)].
h;
479 checkDrawSlot(
index);
483 checkDrawSlot(
index);
484 return drawSlots_[
static_cast<size_t>(
index)].page;
487 checkDrawSlot(
index);
488 return drawSlots_[
static_cast<size_t>(
index)].regionName;
std::array< float, 4 > rotation
EVENGINE_API_FOUNDATION public API.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
void apply()
Apply current animation time to skeleton and update world transforms.
void setPageTexture(int pageIndex, graphics::Texture *texture)
Bind a GPU texture to an atlas page (by index or page image name).
graphics::Texture * getPageTexture(int pageIndex) const
Returns the page texture.
float getDrawSlotHeight(int index) const
Returns the draw slot height.
void setSpeed(float speed)
Sets the speed.
void setPageTextureByName(const std::string &pageName, graphics::Texture *texture)
Sets the page texture by name.
float getDrawSlotX(int index) const
Returns the draw slot x.
bool update(float dt)
Legacy seconds facade; explicitly forwards to advance().
void setAtlas(SpineAtlas *atlas)
Sets the atlas.
void setScale(float sx, float sy)
Sets the scale.
eve::Result< void > advance(const eve::SimulationStep &step)
Advance playback by one scheduler-owned deterministic step.
int getDrawSlotCount() const
Query posed draw slots without textures (for unit tests). Returns number of visible region attachment...
float getAnimationDuration() const
Returns the animation duration.
float getDrawSlotRotation(int index) const
Returns the draw slot rotation.
float getDrawSlotY(int index) const
Returns the draw slot y.
void collectDrawItems(std::vector< graphics::DrawItem2D > &out)
Append region attachment quads into the shared 2D draw queue.
void setPosition(float x, float y)
Sets the position.
bool play(const std::string &animationName)
Play.
float getDrawSlotWidth(int index) const
Returns the draw slot width.
SpineAnim(SpineSkeleton *skeleton)
Spine anim.
std::string getDrawSlotRegion(int index) const
Returns the draw slot region.
void setColor(float r, float g, float b, float a=1.f)
Sets the color.
void setTime(float seconds)
Sets the time.
void draw(graphics::Graphics *gfx)
Draw the current pose into an existing frame without clearing or presenting it.
int getDrawSlotPage(int index) const
Returns the draw slot page.
Esoteric Spine .atlas text parser (region rectangles + page metadata). Does not load image pixels — b...
int getPageHeight(int pageIndex) const
Returns the page height.
int getRegionOriginalWidth(int index) const
Returns the region original width.
std::string getPageName(int pageIndex) const
Returns the page name.
int getPageWidth(int pageIndex) const
Returns the page width.
void getRegionUV(int index, int texW, int texH, float &u0, float &v0, float &u1, float &v1) const
Normalized UVs for a texture of texW×texH (usually page size).
int getRegionOriginalHeight(int index) const
Returns the region original height.
bool getRegionRotate(int index) const
Returns the region rotate.
int getPageCount() const
Returns the page count.
int getRegionPage(int index) const
Returns the region page.
int getRegionWidth(int index) const
Returns the region width.
int getRegionHeight(int index) const
Returns the region height.
int findRegion(const std::string &name) const
Finds region.
float getAnimationDuration(int index) const
Returns the animation duration.
const AnimationData & animation(int i) const
int findAnimation(const std::string &name) const
Finds animation.
const SlotData & slot(int i) const
const BoneData & bone(int i) const
Runtime Spine skeleton pose (local + world bone transforms, slot attachments). Script type: SpineSkel...
void setBoneLocalScaleY(int index, float sy)
Sets the bone local scale y.
float getBoneLocalScaleY(int index) const
Returns the bone local scale y.
void setBoneLocalScaleX(int index, float sx)
Sets the bone local scale x.
float getBoneLocalScaleX(int index) const
Returns the bone local scale x.
void setToSetupPose()
Sets the to setup pose.
float getBoneLocalX(int index) const
Returns the bone local x.
const SpineSkeletonData::RegionAttachment * getSlotRegion(int slotIndex) const
Returns the slot region.
int getSlotCount() const
Returns the slot count.
float getBoneLocalRotation(int index) const
Returns the bone local rotation.
void setBoneLocalRotation(int index, float degrees)
Sets the bone local rotation.
void setBoneLocalX(int index, float x)
Sets the bone local x.
void updateWorldTransform()
Updates world transform.
SpineSkeletonData * getData() const
Returns the data.
void setSlotAttachmentName(int slotIndex, const std::string &name)
Sets the slot attachment name.
float getBoneLocalY(int index) const
Returns the bone local y.
void setBoneLocalY(int index, float y)
Sets the bone local y.
static void drawItems(Graphics &gfx, std::vector< DrawItem2D > &items, bool present)
Sort and draw items. If present=true, calls gfx.present() at the end. Map::render uses present=false ...
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
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.
One deterministic fixed-step emitted by SimulationClock.
Shared 2D draw queue item (sprites + tiles).
bool rotatedUV
Atlas-packed rotated region: corner UVs are remapped (rotated 90°).
float rotation
Degrees, clockwise, around the rectangle center (screen Y-down).