[cpp] 4.3 porting WIP

This commit is contained in:
Mario Zechner 2025-07-05 04:04:10 +02:00
parent 7bd2f2ebd6
commit 751ee5972c
5 changed files with 93 additions and 43 deletions

View File

@ -401,28 +401,35 @@ namespace spine {
~AnimationState();
/// Increments the track entry times, setting queued animations as current if needed
/// @param delta delta time
/// Increments each track entry TrackEntry::getTrackTime(), setting queued animations as current if needed.
void update(float delta);
/// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the
/// animation state can be applied to multiple skeletons to pose them identically.
/// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple
/// skeletons to pose them identically.
/// @return True if any animations were applied.
bool apply(Skeleton &skeleton);
/// Removes all animations from all tracks, leaving skeletons in their previous pose.
/// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose,
/// rather than leaving them in their previous pose.
/// Removes all animations from all tracks, leaving skeletons in their current pose.
///
/// It may be desired to use AnimationState::setEmptyAnimations(float) to mix the skeletons back to the setup pose,
/// rather than leaving them in their current pose.
void clearTracks();
/// Removes all animations from the tracks, leaving skeletons in their previous pose.
/// It may be desired to use AnimationState.setEmptyAnimations(float) to mix the skeletons back to the setup pose,
/// rather than leaving them in their previous pose.
/// Removes all animations from the track, leaving skeletons in their current pose.
///
/// It may be desired to use AnimationState::setEmptyAnimation(int, float) to mix the skeletons back to the setup pose,
/// rather than leaving them in their current pose.
void clearTrack(size_t trackIndex);
/// Sets an animation by name. setAnimation(int, Animation, bool)
/// Sets an animation by name.
///
/// See setAnimation(int, Animation, bool).
TrackEntry *setAnimation(size_t trackIndex, const String &animationName, bool loop);
/// Sets the current animation for a track, discarding any queued animations.
///
/// If the formerly current track entry is for the same animation and was never applied to a skeleton, it is replaced (not mixed
/// from).
/// @param loop If true, the animation will repeat.
/// If false, it will not, instead its last frame is applied if played beyond its duration.
/// In either case TrackEntry.TrackEnd determines when the track is cleared.
@ -432,11 +439,12 @@ namespace spine {
TrackEntry *setAnimation(size_t trackIndex, Animation *animation, bool loop);
/// Queues an animation by name.
/// addAnimation(int, Animation, bool, float)
///
/// See addAnimation(int, Animation, bool, float).
TrackEntry *addAnimation(size_t trackIndex, const String &animationName, bool loop, float delay);
/// Adds an animation to be played delay seconds after the current or last queued animation
/// for a track. If the track is empty, it is equivalent to calling setAnimation.
/// for a track. If the track has no entries, this is equivalent to calling setAnimation.
/// @param delay
/// Seconds to begin this animation after the start of the previous animation. May be <= 0 to use the animation
/// duration of the previous track minus any mix duration plus the negative delay.
@ -445,37 +453,67 @@ namespace spine {
/// after AnimationState.Dispose
TrackEntry *addAnimation(size_t trackIndex, Animation *animation, bool loop, float delay);
/// Sets an empty animation for a track, discarding any queued animations, and mixes to it over the specified mix duration.
/// Sets an empty animation for a track, discarding any queued animations, and sets the track entry's
/// TrackEntry::getMixDuration(). An empty animation has no timelines and serves as a placeholder for mixing in or out.
///
/// Mixing out is done by setting an empty animation with a mix duration using either setEmptyAnimation(int, float),
/// setEmptyAnimations(float), or addEmptyAnimation(int, float, float). Mixing to an empty animation causes
/// the previous animation to be applied less and less over the mix duration. Properties keyed in the previous animation
/// transition to the value from lower tracks or to the setup pose value if no lower tracks key the property. A mix duration of
/// 0 still mixes out over one frame.
///
/// Mixing in is done by first setting an empty animation, then adding an animation using
/// addAnimation(int, Animation, bool, float) with the desired delay (an empty animation has a duration of 0) and on
/// the returned track entry, set the TrackEntry::setMixDuration(float). Mixing from an empty animation causes the new
/// animation to be applied more and more over the mix duration. Properties keyed in the new animation transition from the value
/// from lower tracks or from the setup pose value if no lower tracks key the property to the value keyed in the new animation.
///
/// See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
/// Runtimes Guide.
TrackEntry *setEmptyAnimation(size_t trackIndex, float mixDuration);
/// Adds an empty animation to be played after the current or last queued animation for a track, and mixes to it over the
/// specified mix duration.
/// @return
/// A track entry to allow further customization of animation playback. References to the track entry must not be kept after AnimationState.Dispose.
///
/// @param trackIndex Track number.
/// @param mixDuration Mix duration.
/// @param delay Seconds to begin this animation after the start of the previous animation. May be &lt;= 0 to use the animation
/// duration of the previous track minus any mix duration plus the negative delay.
/// Adds an empty animation to be played after the current or last queued animation for a track, and sets the track entry's
/// TrackEntry::getMixDuration(). If the track has no entries, it is equivalent to calling
/// setEmptyAnimation(int, float).
///
/// See setEmptyAnimation(int, float) and
/// <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
/// Runtimes Guide.
/// @param delay If > 0, sets TrackEntry::getDelay(). If <= 0, the delay set is the duration of the previous track entry
/// minus any mix duration plus the specified <code>delay</code> (ie the mix ends at (<code>delay</code> = 0) or
/// before (<code>delay</code> < 0) the previous track entry duration). If the previous entry is looping, its next
/// loop completion is used instead of its duration.
/// @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
/// after the AnimationStateListener::dispose(TrackEntry) event occurs.
TrackEntry *addEmptyAnimation(size_t trackIndex, float mixDuration, float delay);
/// Sets an empty animation for every track, discarding any queued animations, and mixes to it over the specified mix duration.
///
/// See <a href='https://esotericsoftware.com/spine-applying-animations/#Empty-animations'>Empty animations</a> in the Spine
/// Runtimes Guide.
void setEmptyAnimations(float mixDuration);
/// @return The track entry for the animation currently playing on the track, or NULL if no animation is currently playing.
TrackEntry *getCurrent(size_t trackIndex);
/// The AnimationStateData to look up mix durations.
AnimationStateData *getData();
/// A list of tracks that have animations, which may contain NULLs.
/// The list of tracks that have had animations, which may contain null entries for tracks that currently have no animation.
Vector<TrackEntry *> &getTracks();
/// Multiplier for the delta time when the animation state is updated, causing time for all animations and mixes to play slower
/// or faster. Defaults to 1.
///
/// See TrackEntry TrackEntry::getTimeScale() for affecting a single animation.
float getTimeScale();
void setTimeScale(float inValue);
/// Adds a listener to receive events for all track entries.
void setListener(AnimationStateListener listener);
/// Adds a listener to receive events for all track entries.
void setListener(AnimationStateListenerObject *listener);
void disableQueue();
@ -519,12 +557,18 @@ namespace spine {
static Animation *getEmptyAnimation();
/// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest
/// the first time the mixing was applied.
static void
applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleton &skeleton, float time, float alpha, MixBlend pose,
Vector<float> &timelinesRotation, size_t i, bool firstFrame);
/// Applies the attachment timeline and sets Slot::attachmentState.
/// @param attachments False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline
/// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent
/// timelines see any deform.
void applyAttachmentTimeline(AttachmentTimeline *attachmentTimeline, Skeleton &skeleton, float animationTime,
MixBlend pose, bool firstFrame);
MixBlend pose, bool attachments);
/// Returns true when all mixing from entries are complete.
bool updateMixingFrom(TrackEntry *to, float delta);
@ -536,7 +580,7 @@ namespace spine {
/// Sets the active TrackEntry for a given track number.
void setCurrent(size_t index, TrackEntry *current, bool interrupt);
/// Removes the next entry and all entries after it for the specified entry. */
/// Removes the TrackEntry::getNext() next entry and all entries after it for the specified entry.
void clearNext(TrackEntry *entry);
TrackEntry *expandToIndex(size_t index);

View File

@ -68,6 +68,7 @@ namespace spine {
friend class ToScaleY;
friend class FromShearY;
friend class ToShearY;
friend class AnimationState;
protected:
float _x, _y, _rotation, _scaleX, _scaleY, _shearX, _shearY;

View File

@ -91,6 +91,8 @@ namespace spine {
friend class TwoColorTimeline;
friend class AnimationState;
public:
Slot(SlotData &data, Skeleton &skeleton);

View File

@ -51,6 +51,7 @@ namespace spine {
friend class RGB2Timeline;
friend class PathConstraint;
friend class SkeletonJson;
friend class AnimationState;
protected:
Color _color;

View File

@ -492,7 +492,7 @@ bool AnimationState::apply(Skeleton &skeleton) {
applyAttachmentTimeline(static_cast<AttachmentTimeline *>(timeline), skeleton, applyTime, blend,
attachments);
else
timeline->apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection_In);
timeline->apply(skeleton, animationLast, applyTime, applyEvents, alpha, blend, MixDirection_In, false);
}
} else {
Vector<int> &timelineMode = current._timelineMode;
@ -516,7 +516,7 @@ bool AnimationState::apply(Skeleton &skeleton) {
blend, attachments);
else
timeline->apply(skeleton, animationLast, applyTime, applyEvents, alpha, timelineBlend,
MixDirection_In);
MixDirection_In, false);
}
}
@ -530,9 +530,9 @@ bool AnimationState::apply(Skeleton &skeleton) {
Vector<Slot *> &slots = skeleton.getSlots();
for (int i = 0, n = (int) slots.size(); i < n; i++) {
Slot *slot = slots[i];
if (slot->getAttachmentState() == setupState) {
if (slot->_attachmentState == setupState) {
const String &attachmentName = slot->getData().getAttachmentName();
slot->setAttachment(attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot->getData().getIndex(), attachmentName));
slot->_pose.setAttachment(attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot->getData().getIndex(), attachmentName));
}
}
_unkeyedState += 2;
@ -589,7 +589,7 @@ TrackEntry *AnimationState::setAnimation(size_t trackIndex, Animation *animation
bool interrupt = true;
TrackEntry *current = expandToIndex(trackIndex);
if (current != NULL) {
if (current->_nextTrackLast == -1) {
if (current->_nextTrackLast == -1 && current->_animation == animation) {
// Don't mix from an entry that was never applied.
_tracks[trackIndex] = current->_mixingFrom;
_queue->interrupt(current);
@ -740,7 +740,7 @@ void AnimationState::applyAttachmentTimeline(AttachmentTimeline *attachmentTimel
}
/* If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.*/
if (slot->getAttachmentState() <= _unkeyedState) slot->setAttachmentState(_unkeyedState + Setup);
if (slot->_attachmentState <= _unkeyedState) slot->_attachmentState = _unkeyedState + Setup;
}
@ -749,27 +749,29 @@ void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleto
if (firstFrame) timelinesRotation[i] = 0;
if (alpha == 1) {
rotateTimeline->apply(skeleton, 0, time, NULL, 1, blend, MixDirection_In);
static_cast<Timeline*>(rotateTimeline)->apply(skeleton, 0, time, NULL, 1, blend, MixDirection_In, false);
return;
}
Bone *bone = skeleton._bones[rotateTimeline->_boneIndex];
if (!bone->isActive()) return;
BoneLocal &pose = bone->_pose;
BoneLocal &setup = bone->_data._setup;
Vector<float> &frames = rotateTimeline->_frames;
float r1, r2;
if (time < frames[0]) {
switch (blend) {
case MixBlend_Setup:
bone->_rotation = bone->_data._rotation;
pose._rotation = setup._rotation;
default:
return;
case MixBlend_First:
r1 = bone->_rotation;
r2 = bone->_data._rotation;
r1 = pose._rotation;
r2 = setup._rotation;
}
} else {
r1 = blend == MixBlend_Setup ? bone->_data._rotation : bone->_rotation;
r2 = bone->_data._rotation + rotateTimeline->getCurveValue(time);
r1 = blend == MixBlend_Setup ? setup._rotation : pose._rotation;
r2 = setup._rotation + rotateTimeline->getCurveValue(time);
}
// Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
@ -804,7 +806,7 @@ void AnimationState::applyRotateTimeline(RotateTimeline *rotateTimeline, Skeleto
timelinesRotation[i] = total;
}
timelinesRotation[i + 1] = diff;
bone->_rotation = r1 + total * alpha;
pose._rotation = r1 + total * alpha;
}
bool AnimationState::updateMixingFrom(TrackEntry *to, float delta) {
@ -868,7 +870,7 @@ float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBle
if (blend == MixBlend_Add) {
for (size_t i = 0; i < timelineCount; i++)
timelines[i]->apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection_Out);
timelines[i]->apply(skeleton, animationLast, applyTime, events, alphaMix, blend, MixDirection_Out, false);
} else {
Vector<int> &timelineMode = from->_timelineMode;
Vector<TrackEntry *> &timelineHoldMix = from->_timelineHoldMix;
@ -920,7 +922,7 @@ float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBle
if (drawOrder && timeline->getRTTI().isExactly(DrawOrderTimeline::rtti) &&
timelineBlend == MixBlend_Setup)
direction = MixDirection_In;
timeline->apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction);
timeline->apply(skeleton, animationLast, applyTime, events, alpha, timelineBlend, direction, false);
}
}
}
@ -937,9 +939,9 @@ float AnimationState::applyMixingFrom(TrackEntry *to, Skeleton &skeleton, MixBle
}
void AnimationState::setAttachment(Skeleton &skeleton, Slot &slot, const String &attachmentName, bool attachments) {
slot.setAttachment(
slot._pose.setAttachment(
attachmentName.isEmpty() ? NULL : skeleton.getAttachment(slot.getData().getIndex(), attachmentName));
if (attachments) slot.setAttachmentState(_unkeyedState + Current);
if (attachments) slot._attachmentState = _unkeyedState + Current;
}
void AnimationState::queueEvents(TrackEntry *entry, float animationTime) {