[lua] Finished porting AnimationState, needs testing and fixes

This commit is contained in:
badlogic 2016-11-01 10:49:04 +01:00
parent 44f5540d37
commit 15ae8183f7
2 changed files with 402 additions and 1 deletions

View File

@ -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

View File

@ -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