mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 22:34:53 +08:00
[lua] Finished porting AnimationState, needs testing and fixes
This commit is contained in:
parent
44f5540d37
commit
15ae8183f7
@ -85,6 +85,7 @@ local function binarySearch (values, target, step)
|
||||
current = math.floor((low + high) / 2)
|
||||
end
|
||||
end
|
||||
Animation.binarySearch = binarySearch
|
||||
|
||||
local function binarySearch1 (values, target)
|
||||
local low = 0
|
||||
|
||||
@ -37,6 +37,12 @@ local math_min = math.min
|
||||
local math_abs = math.abs
|
||||
local math_signum = utils.signum
|
||||
|
||||
local function zlen(array)
|
||||
return #array + 1
|
||||
end
|
||||
|
||||
local EMPTY_ANIMATION = Animation.new("<empty>", {}, 0)
|
||||
|
||||
local EventType = {
|
||||
start = 0,
|
||||
interrupt = 1,
|
||||
@ -371,6 +377,400 @@ function AnimationState:applyMixingFrom (entry, skeleton)
|
||||
return mix
|
||||
end
|
||||
|
||||
-- CONTINUE WITH applyRotateTimeline here
|
||||
function AnimationState:applyRotateTimeline (timeline, skeleton, time, alpha, setupPose, timelinesRotation, i, firstFrame)
|
||||
if alpha == 1 then
|
||||
timeline:apply(skeleton, 0, time, nil, 1, setupPose, false)
|
||||
return
|
||||
end
|
||||
|
||||
local rotateTimeline = timeline
|
||||
local frames = rotateTimeline.frames
|
||||
if time < frames[0] then return end -- Time is before first frame.
|
||||
|
||||
local bone = skeleton.bones[rotateTimeline.boneIndex]
|
||||
|
||||
local r2 = 0
|
||||
if time >= frames[zlen(frames) - Animation.RotateTimeline.ENTRIES] then -- Time is after last frame.
|
||||
r2 = bone.data.rotation + frames[zlen(frames) + PREV_ROTATION]
|
||||
else
|
||||
-- Interpolate between the previous frame and the current frame.
|
||||
local frame = Animation.binarySearch(frames, time, ENTRIES)
|
||||
local prevRotation = frames[frame + PREV_ROTATION]
|
||||
local frameTime = frames[frame]
|
||||
local percent = rotateTimeline:getCurvePercent(math_floor(frame / 2) - 1,
|
||||
1 - (time - frameTime) / (frames[frame + PREV_TIME] - frameTime))
|
||||
|
||||
r2 = frames[frame + ROTATION] - prevRotation
|
||||
r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360
|
||||
r2 = prevRotation + r2 * percent + bone.data.rotation
|
||||
r2 = r2 - (16384 - math_floor(16384.499999999996 - r2 / 360)) * 360
|
||||
end
|
||||
|
||||
-- Mix between rotations using the direction of the shortest route on the first frame while detecting crosses.
|
||||
local r1 = bone.rotation
|
||||
if setupPose then r1 = bone.data.rotation end
|
||||
local total = 0
|
||||
local diff = r2 - r1
|
||||
if diff == 0 then
|
||||
if firstFrame then
|
||||
timelinesRotation[i] = 0
|
||||
total = 0
|
||||
else
|
||||
total = timelinesRotation[i]
|
||||
end
|
||||
else
|
||||
diff = diff - (16384 - math_floor(16384.499999999996 - diff / 360)) * 360
|
||||
local lastTotal = 0
|
||||
local lastDiff = 0
|
||||
if firstFrame then
|
||||
lastTotal = 0
|
||||
lastDiff = diff
|
||||
else
|
||||
lastTotal = timelinesRotation[i] -- Angle and direction of mix, including loops.
|
||||
lastDiff = timelinesRotation[i + 1] -- Difference between bones.
|
||||
end
|
||||
local current = diff > 0
|
||||
local dir = lastTotal >= 0
|
||||
-- Detect cross at 0 (not 180).
|
||||
if math_signum(lastDiff) ~= math_signum(diff) and math_abs(lastDiff) <= 90 then
|
||||
-- A cross after a 360 rotation is a loop.
|
||||
if math_abs(lastTotal) > 180 then lastTotal = lastTotal + 360 * math_signum(lastTotal) end
|
||||
dir = current
|
||||
end
|
||||
total = diff + lastTotal - math_ceil(lastTotal / 360 - 0.5) * 360 -- FIXME used to be %360, store loops as part of lastTotal.
|
||||
if dir ~= current then total = total + 360 * Math.signum(lastTotal) end
|
||||
timelinesRotation[i] = total
|
||||
end
|
||||
timelinesRotation[i + 1] = diff
|
||||
r1 = r1 + total * alpha
|
||||
bone.rotation = r1 - (16384 - math_floor(16384.499999999996 - r1 / 360)) * 360
|
||||
end
|
||||
|
||||
function AnimationState:queueEvents (entry, animationTime)
|
||||
local animationStart = entry.animationStart
|
||||
local animationEnd = entry.animationEnd
|
||||
local duration = animationEnd - animationStart
|
||||
local trackLastWrapped = entry.trackLast % duration
|
||||
|
||||
-- Queue events before complete.
|
||||
local events = self.events
|
||||
local queue = self.queue
|
||||
local i = 1
|
||||
local n = #events
|
||||
while i <= n do
|
||||
local event = events[i]
|
||||
if event.time < trackLastWrapped then break end
|
||||
if not (event.time > animationEnd) then -- Discard events outside animation start/end.
|
||||
queue:event(entry, event)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
-- Queue complete if completed a loop iteration or the animation.
|
||||
local queueComplete = false
|
||||
if entry.loop then
|
||||
queueComplete = (trackLastWrapped > entry.trackTime % duration)
|
||||
else
|
||||
queueComplete = (animationTime >= animationEnd and entry.animationLast < animationEnd)
|
||||
end
|
||||
if queueComplete then
|
||||
queue:complete(entry)
|
||||
end
|
||||
|
||||
-- Queue events after complete.
|
||||
while i <= n do
|
||||
local event = events[i]
|
||||
if not (event.time < animationStart) then --// Discard events outside animation start/end.
|
||||
queue.event(entry, event)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
events = {}
|
||||
end
|
||||
|
||||
function AnimationState:clearTracks ()
|
||||
local queue = self.queue
|
||||
local tracks = self.tracks
|
||||
queue.drainDisabled = true;
|
||||
for i,track in pairs(tracks) do
|
||||
self:clearTrack(i)
|
||||
end
|
||||
tracks = {}
|
||||
queue.drainDisabled = false;
|
||||
queue:drain();
|
||||
end
|
||||
|
||||
function AnimationState:clearTrack (trackIndex)
|
||||
local tracks = self.tracks
|
||||
local queue = self.queue
|
||||
local current = tracks[trackIndex]
|
||||
if current == nil then return end
|
||||
|
||||
queue:_end(current)
|
||||
|
||||
self:disposeNext(current)
|
||||
|
||||
local entry = current;
|
||||
while (true) do
|
||||
local from = entry.mixingFrom
|
||||
if from == nil then break end
|
||||
queue:_end(from)
|
||||
entry.mixingFrom = nil
|
||||
entry = from
|
||||
end
|
||||
|
||||
tracks[current.trackIndex] = nil
|
||||
|
||||
queue:drain()
|
||||
end
|
||||
|
||||
function AnimationState:setCurrent (index, current)
|
||||
local from = self:expandToIndex(index)
|
||||
local tracks = self.tracks
|
||||
local queue = self.queue
|
||||
tracks[index] = current
|
||||
|
||||
if from then
|
||||
queue:interrupt(from)
|
||||
current.mixingFrom = from
|
||||
current.mixTime = 0
|
||||
|
||||
-- If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
|
||||
if from.mixingFrom then current.mixAlpha = current.mixAlpha * math_min(from.mixTime / from.mixDuration, 1) end
|
||||
end
|
||||
|
||||
queue:start(current)
|
||||
end
|
||||
|
||||
function AnimationState:setAnimationByName (trackIndex, animationName, loop)
|
||||
local animation = self.data.skeletonData:findAnimation(animationName)
|
||||
if not animation then error("Animation not found: " .. animationName, 2) end
|
||||
return self:setAnimation(trackIndex, animation, loop)
|
||||
end
|
||||
|
||||
function AnimationState:setAnimation (trackIndex, animation, loop)
|
||||
if not animation then error("animation cannot be null.") end
|
||||
local current = self:expandToIndex(trackIndex)
|
||||
local queue = self.queue
|
||||
if current then
|
||||
if current.nextTrackLast == -1 then
|
||||
-- Don't mix from an entry that was never applied.
|
||||
tracks[trackIndex] = nil
|
||||
queue:interrupt(current)
|
||||
queue:_end(current)
|
||||
self:disposeNext(current)
|
||||
current = nil
|
||||
else
|
||||
self:disposeNext(current)
|
||||
end
|
||||
end
|
||||
local entry = self:trackEntry(trackIndex, animation, loop, current)
|
||||
self:setCurrent(trackIndex, entry)
|
||||
queue:drain()
|
||||
return entry
|
||||
end
|
||||
|
||||
function AnimationState:addAnimationByName (trackIndex, animationName, loop, delay)
|
||||
local animation = data.skeletonData:findAnimation(animationName)
|
||||
if not animation then error("Animation not found: " + animationName) end
|
||||
return self:addAnimation(trackIndex, animation, loop, delay)
|
||||
end
|
||||
|
||||
function AnimationState:addAnimation (trackIndex, animationName, loop, delay)
|
||||
if not nimation then error("animation cannot be null.") end
|
||||
|
||||
local last = self:expandToIndex(trackIndex)
|
||||
if last then
|
||||
while last.next do
|
||||
last = last.next
|
||||
end
|
||||
end
|
||||
|
||||
local entry = self:trackEntry(trackIndex, animation, loop, last)
|
||||
local queue = self.queue
|
||||
|
||||
if not last then
|
||||
self:setCurrent(trackIndex, entry)
|
||||
queue:drain()
|
||||
else
|
||||
last.next = entry
|
||||
if delay <= 0 then
|
||||
local duration = last.animationEnd - last.animationStart
|
||||
if duration ~= 0 then
|
||||
delay = delay + duration * (1 + math_floor(last.trackTime / duration)) - data:getMix(last.animation, animation)
|
||||
else
|
||||
delay = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
entry.delay = delay
|
||||
return entry
|
||||
end
|
||||
|
||||
function AnimationState:setEmptyAnimation (trackIndex, mixDuration)
|
||||
local entry = self:setAnimation(trackIndex, EMPTY_ANIMATION, false)
|
||||
entry.mixDuration = mixDuration
|
||||
entry.trackEnd = mixDuration
|
||||
return entry
|
||||
end
|
||||
|
||||
function AnimationState:addEmptyAnimation (trackIndex, mixDuration, delay)
|
||||
if delay <= 0 then delay = delay - mixDuration end
|
||||
local entry = self:addAnimation(trackIndex, EMPTY_ANIMATION, false, delay)
|
||||
entry.mixDuration = mixDuration
|
||||
entry.trackEnd = mixDuration
|
||||
return entry
|
||||
end
|
||||
|
||||
function AnimationState:setEmptyAnimations (mixDuration)
|
||||
local queue = self.queue
|
||||
queue.drainDisabled = true
|
||||
for i,current in pairs(self.tracks) do
|
||||
if current then self:setEmptyAnimation(current.trackIndex, mixDuration) end
|
||||
end
|
||||
queue.drainDisabled = false
|
||||
queue:drain()
|
||||
end
|
||||
|
||||
function AnimationState:expandToIndex (index)
|
||||
return self.tracks[index]
|
||||
end
|
||||
|
||||
function AnimationState:trackEntry (trackIndex, animation, loop, last)
|
||||
local data = self.data
|
||||
local entry = TrackEntry.new()
|
||||
entry.trackIndex = trackIndex
|
||||
entry.animation = animation
|
||||
entry.loop = loop
|
||||
|
||||
entry.eventThreshold = 0
|
||||
entry.attachmentThreshold = 0
|
||||
entry.drawOrderThreshold = 0
|
||||
|
||||
entry.animationStart = 0
|
||||
entry.animationEnd = animation.duration
|
||||
entry.animationLast = -1
|
||||
entry.nextAnimationLast = -1
|
||||
|
||||
entry.delay = 0
|
||||
entry.trackTime = 0
|
||||
entry.trackLast = -1
|
||||
entry.nextTrackLast = -1
|
||||
if loop then
|
||||
entry.trackEnd = 999999999
|
||||
else
|
||||
entry.trackEnd = entry.animationEnd
|
||||
end
|
||||
entry.timeScale = 1
|
||||
|
||||
entry.alpha = 1
|
||||
entry.mixAlpha = 1
|
||||
entry.mixTime = 0
|
||||
if not last then
|
||||
entry.mixDuration = 0
|
||||
else
|
||||
entry.mixDuration = data:getMix(last.animation, animation)
|
||||
end
|
||||
return entry
|
||||
end
|
||||
|
||||
function AnimationState:disposeNext (entry)
|
||||
local _next = entry.next
|
||||
local queue = self.queue
|
||||
while _next do
|
||||
queue:dispose(_next)
|
||||
_next = next.next
|
||||
end
|
||||
entry.next = nil
|
||||
end
|
||||
|
||||
function AnimationState:animationsChanged ()
|
||||
self.animationsChanged = false;
|
||||
|
||||
self.propertyIDs = {}
|
||||
local propertyIDs = self.propertyIDs;
|
||||
|
||||
-- need to get the highest index cause Lua is funny
|
||||
local highest = -1
|
||||
for i,entry in pairs(tracks) do
|
||||
if i > highest then highest = i end
|
||||
end
|
||||
|
||||
-- Set timelinesFirst for all entries, from lowest track to highest.
|
||||
local i = 1
|
||||
local n = highest
|
||||
local tracks = self.tracks
|
||||
while i <= n do
|
||||
local entry = tracks[i]
|
||||
if entry then
|
||||
self:setTimelinesFirst(entry);
|
||||
i = i + 1
|
||||
break;
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
while i <= n do
|
||||
local entry = tracks[i]
|
||||
if entry then self:checkTimelinesFirst(entry) end
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
function AnimationState:setTimelinesFirst (entry)
|
||||
if entry.mixingFrom then
|
||||
self:setTimelinesFirst(entry.mixingFrom)
|
||||
self:checkTimelinesUsage(entry, entry.timelinesFirst)
|
||||
return
|
||||
end
|
||||
local propertyIDs = self.propertyIDs
|
||||
local n = #entry.animation.timelines
|
||||
local timelines = entry.animation.timelines
|
||||
entry.timelinesFirst = {}
|
||||
local usage = entry.timelinesFirst;
|
||||
local i = 1
|
||||
while i <= n do
|
||||
local id = "" .. timelines[i]:getPropertyId()
|
||||
propertyIDs[id] = id
|
||||
usage[i] = true;
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
function AnimationState:checkTimlinesFirst (entry)
|
||||
if entry.mixingFrom then self:checkTimelinesFirst(entry.mixingFrom) end
|
||||
self:checkTimelinesUsage(entry, entry.timelinesFirst)
|
||||
end
|
||||
|
||||
function AnimationState:checkTimelinesUsage (entry, usageArray)
|
||||
local propertyIDs = self.propertyIDs
|
||||
local n = #entry.animation.timelines
|
||||
local timelines = entry.animation.timelines
|
||||
local usage = usageArray
|
||||
while i <= n do
|
||||
local id = "" .. timelines[i]:getPropertyId()
|
||||
local contained = propertyIDs[id] == id
|
||||
propertyIDs[id] = id
|
||||
usage[i] = not contained
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
function AnimationState:getCurrent (trackIndex)
|
||||
return self.tracks[trackIndex]
|
||||
end
|
||||
|
||||
function AnimationState:clearListeners ()
|
||||
self.onStart = nil
|
||||
self.onInterrupt = nil
|
||||
self.onEnd = nil
|
||||
self.onComplete = nil
|
||||
self.onDispose = nil
|
||||
self.onEvent = nil
|
||||
end
|
||||
|
||||
function AnimationState:clearListenerNotificatin ()
|
||||
self.queue:clear()
|
||||
end
|
||||
|
||||
return AnimationState
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user