Adding support for animating all image resources.

This commit is contained in:
Martin Haye 2016-10-17 09:26:47 -07:00
parent dce735786e
commit 6a049d3e33
3 changed files with 261 additions and 65 deletions

View File

@ -772,57 +772,12 @@ class A2PackPartitions
def packPortrait(imgEl)
{
def num = portraits.size() + 1
def name = imgEl.@name ?: "img$num"
def animFrameNum = 0
def animFlags
def m = (name =~ /^(.*)\*(\d+)(\w*)$/)
if (m) {
name = m[0][1]
animFrameNum = m[0][2].toInteger()
animFlags = m[0][3].toLowerCase()
def (name, animFrameNum, animFlags) = decodeImageName(imgEl.@name ?: "img$num")
if (!portraits.containsKey(name)) {
def num = portraits.size() + 1
portraits[name] = [num:num, anim:new AnimBuf()]
}
//println "Packing 126 image named '$name'."
def buf = parse126Data(imgEl)
if (animFrameNum == 1) {
def flagByte
switch (animFlags) {
case "" : flagByte = 0; break
case "f" : flagByte = 0x20; break
case "fb": flagByte = 0x40; break
case "r" : flagByte = 0x80; break
default : throw new Exception("Unrecognized animation flags '$animFlags'")
}
def newBuf = ByteBuffer.allocate(50000) // plenty of room
buf.flip() // crazy stuff to append one buffer to another
newBuf.put(buf)
def endPos = newBuf.position()
newBuf.position(0)
newBuf.put((byte) (1 + flagByte))
newBuf.position(endPos)
portraits[name] = [num:num, buf:newBuf]
}
else if (animFrameNum > 1) {
if (!portraits[name])
throw new Exception("Can't find first frame for animation '$name'")
num = portraits.size() // in other words, do not increment
buf.flip() // crazy stuff to append one buffer to another
buf.get() // skip 1st byte - unused flags
def out = portraits[name].buf
out.put(buf)
// Increment the frame count
def endPos = out.position()
out.position(0)
def before = out.get()
out.position(0)
out.put((byte)(before+1))
out.position(endPos)
//println "$name: ${out.position()} bytes."
}
else
portraits[name] = [num:num, buf:buf]
//println "...uncompressed: ${buf.position()} bytes."
portraits[name].anim.addImage(animFrameNum, animFlags, parse126Data(imgEl))
}
def packTile(imgEl)
@ -1830,6 +1785,11 @@ class A2PackPartitions
textureImgs.each { image -> packTexture(image) }
if (!grabEntireFromCache("portraits", portraits, xmlLastMod)) {
portraitImgs.each { image -> packPortrait(image) }
portraits.each { name, portrait ->
println "Packing portrait $name."
portrait.buf = portrait.anim.pack()
portrait.anim = null
}
addEntireToCache("portraits", portraits, xmlLastMod)
}
@ -2510,6 +2470,20 @@ end
}
}
def decodeImageName(rawName)
{
def name = rawName
def animFrameNum = 1
def animFlags
def m = (name =~ /^(.*)\*(\d+)(\w*)$/)
if (m) {
name = m[0][1]
animFrameNum = m[0][2].toInteger()
animFlags = m[0][3].toLowerCase()
}
return [name, animFrameNum, animFlags]
}
def dataGen(xmlPath)
{
// Open the XML data file produced by Outlaw Editor
@ -2526,21 +2500,11 @@ end
def portraitNum = 0
dataIn.image.sort{it.@name.toLowerCase()}.each { image ->
def category = image.@category?.toLowerCase()
def name = image.@name
if (category == "portrait") {
def animFrameNum = 0
def animFlags
def m = (name =~ /^(.*)\*(\d+)(\w*)$/)
if (m) {
name = m[0][1]
animFrameNum = m[0][2].toInteger()
animFlags = m[0][3].toLowerCase()
}
if (animFrameNum <= 1) {
++portraitNum
out.println "const PO${humanNameToSymbol(name, false)} = $portraitNum"
portraits[name] = [] // placeholder during dataGen phase
}
def (name, animFrameNum, animFlags) = decodeImageName(image.@name)
if (category == "portrait" && animFrameNum == 1) {
++portraitNum
out.println "const PO${humanNameToSymbol(name, false)} = $portraitNum"
portraits[name] = [] // placeholder during dataGen phase
}
}
out.println "const PO_LAST = $portraitNum"
@ -3443,4 +3407,130 @@ end
}
}
class AnimBuf
{
def animFlags
def buffers = []
def changeOffsets = [] as Set
def patches = []
def addImage(animFrameNum, animFlags, imgBuf)
{
if (animFrameNum == 1)
this.animFlags = animFlags
buffers << imgBuf
assert animFrameNum == buffers.size() : "Missing animation frame"
}
def pack()
{
def buf = ByteBuffer.allocate(50000) // plenty of room
// If no animation, add a stub to the start of the (only) image and return it
assert buffers.size() >= 1
if (buffers.size() == 1) {
buf.put((byte)0)
buf.put((byte)0)
buffers[0].flip() // crazy stuff to append one buffer to another
buf.put(buffers[0])
return buf
}
// Locate the change offsets and form a set of patches
findChangeOffsets()
changeOffsets.sort().each { pos -> addPatch(pos) }
println "Change offsets: ${changeOffsets.sort()}"
println "Patches: $patches"
// At start of buffer, put offset to animation header, then the first frame
def offset = buffers[0].position() + 2 // 2 for header
buf.put((byte)(offset & 0xFF))
buf.put((byte)((offset >> 8) & 0xFF))
buffers[0].flip()
buf.put(buffers[0])
// Now append the full animation header
def flagByte
switch (animFlags) {
case "" : flagByte = 0; break
case "f" : flagByte = 1; break
case "fb": flagByte = 2; break
case "r" : flagByte = 3; break
default : throw new Exception("Unrecognized animation flags '$animFlags'")
}
buf.put((byte) flagByte)
buf.put((byte)0) // used to store current anim dir
buf.put((byte)(buffers.size() - 1)) // index of last frame
buf.put((byte)0) // used to store current anim frame
// Next comes the length of each patch (they're all the same)
int patchLength = 0
patches.each { patch ->
patchLength += patch.end - patch.start + 1
}
buf.put((byte)(patchLength & 0xFF))
buf.put((byte)((patchLength>>8) & 0xFF))
// And then the length of the patch offset table
def tblLength = (patches.size() * 2) + 1 // 1 for end of table
buf.put((byte)(tblLength & 0xFF))
buf.put((byte)((tblLength>>8) & 0xFF))
// After the animation header, write out the patch offset table.
def prevEnd = 0
patches.each { patch ->
buf.put((byte)(patch.start - prevEnd))
buf.put((byte)(patch.end - patch.start))
//println "Patch: ${patch.start - prevEnd} ${patch.end - patch.start}"
prevEnd = patch.end
}
buf.put((byte)0xFF)
// Finally write patch data for each image (including the base image, so
// one can loop around from last to first.)
buffers.each { img ->
patches.each { patch ->
(patch.start ..< patch.end) { pos ->
buf.put((byte)img.get(pos))
}
}
}
// All done.
return buf
}
// Determine all the buffer positions that any animation frame differs from
// the base image.
def findChangeOffsets()
{
ByteBuffer base = buffers[0]
buffers[1..-1].each { ByteBuffer buf ->
assert base.position() == buf.position() : "internal: buffers must be equal size"
(0..<base.position()).each { pos ->
if (base.get(pos) != buf.get(pos))
changeOffsets << pos
}
}
}
def addPatch(int pos)
{
// See if we can glom on to the previous patch
def last = patches.isEmpty() ? [start:0, end:0] : patches[-1]
if (last.end > 0 && pos < last.end + 3 && pos - last.start < 254) {
last.end = pos+1
return
}
// Skip to the right position
while (pos - last.end >= 254) {
last = [start:last.end+254, end:last.end+254]
patches << last
}
// And add a new patch
patches << [start:pos, end:pos+1]
}
}
}

View File

@ -2989,6 +2989,95 @@ showDiskActivity: !zone
bne -
.done rts
;------------------------------------------------------------------------------
; Advance all animated resources by one frame.
; Params: X = direction change (0=no change, 1=forward, $FF=backward).
; Only applied to resources marked as "forward/backward" order.
; Y = number of frames to skip.
; Only applied to resources marked as "random" order.
advanceAnims:
stx resType ; store direction-change
sty resNum ; store frames-to-skip
lda #0
sta .ret+1 ; clear count of animated
ldx isAuxCmd ; grab correct starting segment (0=main mem, 1=aux)
.loop: lda tSegType,x ; segment flags and type
bpl .next ; skip non-active
and #$F ; get type
cmp #RES_TYPE_PORTRAIT
beq .anim ; found an animated resource type
bne .next ; not animated; skip
.anim lda tSegAdrLo,x ; pointer to start of resource
sta pTmp
lda tSegAdrHi,x ; ...hi byte too
sta pTmp+1
ldy #1
lda (pTmp),y ; check anim header offset
ora (pTmp),y
beq .next ; if zero, resource is not aniimated
lda (pTmp),y ; grab offset
clc
adc pTmp ; add to starting addr
sta tmp ; to obtain addr of animation header
iny
lda (pTmp),y ; hi byte too
adc pTmp+1
sta tmp+1
txa ; save link number we're scanning
pha
.chkr ldy #0
lda (tmp),y ; get animation type (1=Forward, 2=Forward+Backward, 3=Random)
cmp #3 ; is it random?
bne .chkfb
ldx resNum ; number of frames to skip
beq .res ; if zero, nothing to do
- lda #1 ; direction = forward
jsr .fwd ; advance one frame
dec resNum ; number to advance
bne - ; loop for specified number of skips
beq .doptch ; and go do the patching (always taken)
.chkfb iny ; index of current dir
cmp #2 ; is it a forward+backward anim?
bne .setdir
lda resType ; get change to dir
beq .adv ; not changing? just advance
.setdir sta (pTmp),y ; store new dir
.adv lda (pTmp),y ; get current dir
jsr .fwbk ; advance the frame number in that direction
.doptch jsr .patch ; apply patch for the new frame
.res pla ; restore link number we're scanning
tax
.next: lda tSegLink,x ; next in chain
tax ; to X reg index
bne .loop ; non-zero = not end of chain - loop again
.ret lda #0 ; return count of number actually patched (self-modified by .patch below)
rts
.fwbk ldy #3 ; index of current frame number
clc
adc (tmp),y ; advance in direction
dey ; index of number of frames
bcc + ; carry can only be set if dir=-1 and we wrapped around
lda (tmp),y ; get number of frames
sbc #1 ; minus one (we know carry is already set)
+ cmp (tmp),y ; are we at the limit of number of frames?
bne +
lda #0 ; back to start
+ iny ; index of current frame number
sta (tmp),y ; and store it
rts
.patch inc .ret+1 ; count number we have actually changed
; TODO
rts
;------------------------------------------------------------------------------
; Segment tables

View File

@ -86,6 +86,23 @@
; The remainder of the file is the data for the resources, in order of their
; table appearance.
;
; ------------------------------------------------------------
; Animated resource format (frame images, portraits, textures)
; bytes 0-1: offset to animation header (or $0000 if not animated)
; bytes 2-n: invariant image data
; Followed by animation header:
; byte 0: animation type (1=Forward, 2=Forward+Backward, 3=Random)
; byte 1: current anim dir
; byte 2: index of last frame (= number of frames *minus 1*)
; byte 3: current anim frame
; bytes 4-5: length of any patch data segment (they're all the same length)
; bytes 6-7: length of patch offset table
; Followed by patch offset table. Each entry:
; byte 0: # of invariant bytes to skip (can be zero) ($FF for end of table)
; byte 1: # of patch bytes to copy (can be zero)
; Followed by patch data segments. Each segment:
; bytes 0-n: raw data (intelligible only by using patch offset table)
mainLoader = $800
auxLoader = mainLoader+3