Skip to content

Commit

Permalink
Added support for color and attachment timelines. Also cleaned up ske…
Browse files Browse the repository at this point in the history
…leton loader a bit
  • Loading branch information
Elias Naur committed Oct 30, 2013
1 parent 6763592 commit c7f1585
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 25 deletions.
114 changes: 114 additions & 0 deletions animation.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,120 @@ func (t *ScaleTimeline) Apply(skeleton *Skeleton, time, alpha float32) {
bone.ScaleY += (bone.Data.scaleY - 1 + lastFrameY + (frames[frameIndex+2]-lastFrameY)*percent - bone.ScaleY) * alpha
}

type ColorTimeline struct {
slotIndex int
frames []float32
curve *Curve
}

func NewColorTimeline(l int) *ColorTimeline {
return &ColorTimeline{
frames: make([]float32, l*5),
curve: NewCurve(l),
}
}

func (t *ColorTimeline) frameCount() int {
return t.curve.frameCount()
}

func (t *ColorTimeline) setFrame(index int, time, r, g, b, a float32) {
index *= 5
frames := t.frames
frames[index] = time
frames[index+1] = r
frames[index+2] = g
frames[index+3] = b
frames[index+4] = a
}

func (t *ColorTimeline) Apply(skeleton *Skeleton, time, alpha float32) {
frames := t.frames
if time < frames[0] {
return // Time is before first frame.
}

slot := skeleton.Slots[t.slotIndex]

if time >= frames[len(t.frames)-5] { // Time is after last frame.
i := len(frames) - 1
slot.R = frames[i-3]
slot.G = frames[i-2]
slot.B = frames[i-1]
slot.A = frames[i]
return
}

// Interpolate between the last frame and the current frame.
frameIndex := binarySearch(frames, time, 5)
lastFrameR := frames[frameIndex-4]
lastFrameG := frames[frameIndex-3]
lastFrameB := frames[frameIndex-2]
lastFrameA := frames[frameIndex-1]
frameTime := frames[frameIndex]
percent := 1 - (time-frameTime)/(frames[frameIndex-5]-frameTime)
percent = t.curve.CurvePercent(frameIndex/5-1, percent)

r := lastFrameR + (frames[frameIndex+1]-lastFrameR)*percent
g := lastFrameG + (frames[frameIndex+2]-lastFrameG)*percent
b := lastFrameB + (frames[frameIndex+3]-lastFrameB)*percent
a := lastFrameA + (frames[frameIndex+4]-lastFrameA)*percent
if alpha < 1 {
slot.R += (r - slot.R) * alpha
slot.G += (g - slot.G) * alpha
slot.B += (b - slot.B) * alpha
slot.A += (a - slot.A) * alpha
} else {
slot.R = r
slot.G = g
slot.B = b
slot.A = a
}
}

type AttachmentTimeline struct {
slotIndex int
frames []float32
attachmentNames []string
}

func NewAttachmentTimeline(l int) *AttachmentTimeline {
return &AttachmentTimeline{
frames: make([]float32, l),
attachmentNames: make([]string, l),
}
}

func (t *AttachmentTimeline) frameCount() int {
return len(t.frames)
}

func (t *AttachmentTimeline) setFrame(index int, time float32, attachmentName string) {
t.frames[index] = time
t.attachmentNames[index] = attachmentName
}

func (t *AttachmentTimeline) Apply(skeleton *Skeleton, time, alpha float32) {
frames := t.frames
if time < frames[0] {
return // Time is before first frame.
}

var frameIndex int
if time >= frames[len(frames)-1] { // Time is after last frame.
frameIndex = len(frames) - 1
} else {
frameIndex = binarySearch(frames, time, 1) - 1
}

attachmentName := t.attachmentNames[frameIndex]
var attachment Attachment
if attachmentName != "" {
attachment = skeleton.AttachmentBySlotIndex(t.slotIndex, attachmentName)
}
skeleton.Slots[t.slotIndex].Attachment = attachment
}

type Animation struct {
name string
timelines []Timeline
Expand Down
14 changes: 6 additions & 8 deletions attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,14 @@ func (r *RegionAttachment) SetUVs(u float32, v float32, u2 float32, v2 float32,
func (r *RegionAttachment) updateOffset() {
width := r.Width
height := r.Height
localX2 := width / 2
localY2 := height / 2
localX := -localX2
localY := -localY2
scaleX := r.ScaleX
scaleY := r.ScaleY
localX *= scaleX
localY *= scaleY
localX2 *= scaleX
localY2 *= scaleY
regionScaleX := width / r.RegionOriginalWidth * scaleX;
regionScaleY := height / r.RegionOriginalHeight * scaleY;
localX := -width / 2 * scaleX + r.RegionOffsetX * regionScaleX;
localY := -height / 2 * scaleY + r.RegionOffsetY * regionScaleY;
localX2 := localX + r.RegionWidth * regionScaleX;
localY2 := localY + r.RegionHeight * regionScaleY;
rotation := r.Rotation
rads := float64(rotation) * math.Pi / 180
cos := float32(math.Cos(rads))
Expand Down
4 changes: 4 additions & 0 deletions curve.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ func NewCurve(frameCount int) *Curve {
return curve
}

func (c *Curve) frameCount() int {
return len(c.curves)/6 + 1
}

func (c *Curve) SetLinear(index int) {
c.curves[index*6] = 0
}
Expand Down
75 changes: 58 additions & 17 deletions spine.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (
)

type fileAnim struct {
Bones map[string]map[string][]interface{} `json:"bones"`
Bones map[string]map[string][]map[string]interface{} `json:"bones"`
Slots map[string]map[string][]map[string]interface{} `json:"slots"`
}

type fileSlot struct {
Expand Down Expand Up @@ -140,18 +141,14 @@ func New(r io.Reader, scale float32, loader AttachmentLoader) (*SkeletonData, er
slotData := NewSlotData(slot.Name, boneData)

if color := slot.Color; color != "" {
if red, err := strconv.ParseUint(color[0:2], 16, 8); err != nil {
slotData.r = float32(red) / 255.0
}
if green, err := strconv.ParseUint(color[2:4], 16, 8); err != nil {
slotData.g = float32(green) / 255.0
}
if blue, err := strconv.ParseUint(color[4:6], 16, 8); err != nil {
slotData.b = float32(blue) / 255.0
}
if alpha, err := strconv.ParseUint(color[6:8], 16, 8); err != nil {
slotData.a = float32(alpha) / 255.0
c, err := toColor(color)
if err != nil {
return nil, errors.New("spine: failed to parse color: " + err.Error())
}
slotData.r = c[0]
slotData.g = c[1]
slotData.b = c[2]
slotData.a = c[3]
}

slotData.attachmentName = slot.Attachment
Expand Down Expand Up @@ -185,18 +182,18 @@ func New(r io.Reader, scale float32, loader AttachmentLoader) (*SkeletonData, er
}
}

for animName, boneMap := range root.Animations {
for animName, fileAnim := range root.Animations {
timelines := make([]Timeline, 0)
duration := float32(0)
for boneName, timelineMap := range boneMap.Bones {
for boneName, timelineMap := range fileAnim.Bones {
boneIndex, _ := skeletonData.findBone(boneName)
for timelineType, timelineData := range timelineMap {
if timelineType == "rotate" {
n := len(timelineData)
timeline := NewRotateTimeline(n)
timeline.boneIndex = boneIndex
for i := 0; i < n; i++ {
valueMap := timelineData[i].(map[string]interface{})
valueMap := timelineData[i]
time := float32(valueMap["time"].(float64))
angle := float32(valueMap["angle"].(float64))
timeline.setFrame(i, time, angle)
Expand All @@ -212,7 +209,7 @@ func New(r io.Reader, scale float32, loader AttachmentLoader) (*SkeletonData, er
timeline := NewTranslateTimeline(n)
timeline.boneIndex = boneIndex
for i := 0; i < n; i++ {
valueMap := timelineData[i].(map[string]interface{})
valueMap := timelineData[i]
x := float32(0)
if xx, ok := valueMap["x"].(float64); ok {
x = float32(xx) * scale
Expand All @@ -235,7 +232,7 @@ func New(r io.Reader, scale float32, loader AttachmentLoader) (*SkeletonData, er
timeline := NewScaleTimeline(n)
timeline.boneIndex = boneIndex
for i := 0; i < n; i++ {
valueMap := timelineData[i].(map[string]interface{})
valueMap := timelineData[i]
x := float32(0)
if xx, ok := valueMap["x"].(float64); ok {
x = float32(xx)
Expand All @@ -256,13 +253,57 @@ func New(r io.Reader, scale float32, loader AttachmentLoader) (*SkeletonData, er
}
}
}
for slotName, timelineMap := range fileAnim.Slots {
slotIndex, _ := skeletonData.findSlot(slotName)

for timelineName, values := range timelineMap {
n := len(values)
if timelineName == "color" {
timeline := NewColorTimeline(n)
timeline.slotIndex = slotIndex

for frameIndex, valueMap := range values {
time := float32(valueMap["time"].(float64))
c, err := toColor(valueMap["color"].(string))
if err != nil {
return nil, errors.New("spine: failed to parse color: " + err.Error())
}
timeline.setFrame(frameIndex, time, c[0], c[1], c[2], c[3])
readCurve(timeline.curve, frameIndex, valueMap)
}
duration = float32(math.Max(float64(duration), float64(timeline.frames[timeline.frameCount()*5-5])))
timelines = append(timelines, timeline)
} else if timelineName == "attachment" {
timeline := NewAttachmentTimeline(n)
timeline.slotIndex = slotIndex

for frameIndex, valueMap := range values {
time := float32(valueMap["time"].(float64))
timeline.setFrame(frameIndex, time, valueMap["name"].(string))
}
duration = float32(math.Max(float64(duration), float64(timeline.frames[timeline.frameCount()-1])))
timelines = append(timelines, timeline)
}
}
}
anim := NewAnimation(animName, timelines, duration)
skeletonData.animations = append(skeletonData.animations, anim)
}

return skeletonData, nil
}

func toColor(colorStr string) (c [4]float32, err error) {
for i := 0; i < len(c); i++ {
var b uint64
if b, err = strconv.ParseUint(colorStr[i*2:(i+1)*2], 16, 8); err != nil {
return
}
c[i] = float32(b)/255.0
}
return
}

func readAttachment(attachment *RegionAttachment, at fileAttachment, scale float32) {
if x, ok := at.X.(float64); ok {
attachment.X = float32(x) * scale
Expand Down

0 comments on commit c7f1585

Please sign in to comment.