Skip to content

Instantly share code, notes, and snippets.

@abextm
Created March 31, 2019 01:05
Show Gist options
  • Save abextm/6b61ebec38e6b89c5b097856f0b16319 to your computer and use it in GitHub Desktop.
Save abextm/6b61ebec38e6b89c5b097856f0b16319 to your computer and use it in GitHub Desktop.
crab animation writer
package main
import (
"bytes"
"encoding/binary"
"io"
"os"
"github.com/natefinch/atomic"
)
type Joint uint
type Frame struct {
id int
Map map[Joint][]int
}
func frame(m map[Joint][]int) *Frame {
return &Frame{
Map: m,
}
}
const UNSET = 0x1FFFF
const U = UNSET
type SequenceStep struct {
Frame *Frame
Length int
}
var fi = `C:\Users\Abex\Documents\runelite\runelite-client\src\main\resources\crabAnim`
var fi2 = fi + "2"
func Write(steps []SequenceStep) {
out, err := os.Create(fi2)
if err != nil {
panic(err)
}
defer out.Close()
frameCount := 0
writeFrameCount := writeLater(out, 4, func() {
binary.Write(out, binary.BigEndian, uint32(frameCount))
})
sequence := &bytes.Buffer{}
for _, step := range steps {
f := step.Frame
if f.id == 0 {
frameCount++
f.id = frameCount
length := 0
trailer := &bytes.Buffer{}
writeLen := writeLater(out, 4, func() {
binary.Write(out, binary.BigEndian, uint32(length))
})
ptrStart, _ := out.Seek(0, os.SEEK_CUR)
binary.Write(out, binary.BigEndian, uint16(f.id-1))
max := Joint(0)
for i := range f.Map {
if i > max {
max = i
}
}
binary.Write(out, binary.BigEndian, uint8(max+1))
for i := Joint(0); i <= max; i++ {
instr := f.Map[i]
bitfield := byte(0)
for i, v := range instr {
if v == UNSET {
continue
}
if v > 0x3f || v < -0x40 {
v += 0xC000
binary.Write(trailer, binary.BigEndian, uint16(v))
} else {
v += 0x40
trailer.WriteByte(uint8(v))
}
bitfield |= 1 << uint(i)
}
binary.Write(out, binary.BigEndian, bitfield)
}
io.Copy(out, trailer)
ptrEnd, _ := out.Seek(0, os.SEEK_CUR)
length = int(ptrEnd - ptrStart)
writeLen()
}
sequence.WriteByte(uint8(f.id - 1))
sequence.WriteByte(uint8(step.Length))
}
writeFrameCount()
binary.Write(out, binary.BigEndian, uint32(sequence.Len()/2))
io.Copy(out, sequence)
out.Close()
err = atomic.ReplaceFile(fi2, fi)
if err != nil {
panic(err)
}
}
func writeLater(fi io.WriteSeeker, gap int, fn func()) func() {
to, err := fi.Seek(0, os.SEEK_CUR)
if err != nil {
panic(err)
}
fi.Write(make([]byte, gap))
return func() {
ret, err := fi.Seek(0, os.SEEK_CUR)
if err != nil {
panic(err)
}
_, err = fi.Seek(to, os.SEEK_SET)
if err != nil {
panic(err)
}
fn()
_, err = fi.Seek(ret, os.SEEK_SET)
if err != nil {
panic(err)
}
}
}
package main
import "math"
func leg(base Joint) Leg {
return Leg{
Base: base,
Mid: base + 2,
Tip: base + 4,
}
}
type Leg struct {
Base Joint
Mid Joint
Tip Joint
}
const (
LeftEyeRot Joint = 1
LeftEyeScale Joint = 2
LeftEyeMid Joint = 4
LeftEyeBase Joint = 6
RightEyeRot Joint = 8
RightEyeScale Joint = 9
RightEyeMid Joint = 11
RightEyeBase Joint = 13
RightMouthEdge Joint = 15
)
var (
LeftLeg3 = leg(17)
LeftLeg2 = leg(23)
LeftLeg1 = leg(29)
RightLeg3 = leg(35)
RightLeg2 = leg(41)
RightLeg1 = leg(47)
)
const (
RightShoulder Joint = 53
RightWrist Joint = 55
RightThumb Joint = 57
LeftShoulder Joint = 59
LeftWrist Joint = 61
LeftThumb Joint = 63
BodyRotate Joint = 65
BodyShift Joint = 66
BodyScale Joint = 67
BodyOpacity Joint = 68
)
const (
ArmLengthA = 45
ArmLengthB = 48
ArmLengthC = 21
ArmDx = 60
ArmDy = 42
)
func main() {
frames := make([]*Frame, 5)
halfLen := (len(frames) / 2)
radius := 24
radiusStep := radius / halfLen
height := 28
heightStep := height / halfLen
for i := range frames {
ci := i - halfLen
aci := ci
if aci < 0 {
aci = -aci
}
f := &Frame{Map: map[Joint][]int{}}
frames[i] = f
f.Map[BodyScale] = []int{96, 96, 96}
dx := radius - (i * radiusStep)
dy := (halfLen - aci) * heightStep
f.Map[BodyShift] = []int{dx, -dy}
tip := j2r(128 + 12)
f.Map[LeftLeg1.Tip] = []int{U, U, 152 - r2j(tip)}
var elbowDelta float64
var simpleJointLen float64
var shoulderToFloor float64
var shoulderToFloorAngle float64
{
A := tip
const b = ArmLengthB
const c = ArmLengthC
a := math.Sqrt(b*b + c*c - 2*b*c*math.Cos(A))
C := math.Asin((math.Sin(A) / a) * c)
elbowDelta = C
simpleJointLen = a
}
{
g := float64(.79)
a := float64(60/g - float64(dx))
b := float64(42/g + float64(dy))
c := math.Sqrt(a*a + b*b)
A := math.Asin(a / c)
shoulderToFloor = c
shoulderToFloorAngle = A
}
{
a := shoulderToFloor
const b = ArmLengthA
c := simpleJointLen
A := math.Acos((b*b + c*c - a*a) / (2 * b * c))
C := math.Acos((a*a + b*b - c*c) / (2 * a * b))
f.Map[LeftLeg1.Base] = []int{U, U, -84 + r2j(C+shoulderToFloorAngle)}
f.Map[LeftLeg1.Mid] = []int{U, U, 192 + r2j(A+elbowDelta)}
}
f.Map[LeftEyeBase] = []int{aci * 2, U, ci * 2}
f.Map[LeftEyeMid] = f.Map[LeftEyeBase]
f.Map[RightEyeBase] = f.Map[LeftEyeBase]
f.Map[RightEyeMid] = f.Map[LeftEyeMid]
f.Map[RightShoulder] = []int{U, U, 32 - i*4}
f.Map[LeftShoulder] = []int{U, U, -16 - i*4}
}
for i := range frames {
frames[i].Map[LeftLeg2.Base] = frames[i].Map[LeftLeg1.Base]
frames[i].Map[LeftLeg2.Mid] = frames[i].Map[LeftLeg1.Mid]
frames[i].Map[LeftLeg2.Tip] = frames[i].Map[LeftLeg1.Tip]
frames[i].Map[LeftLeg3.Base] = frames[i].Map[LeftLeg1.Base]
frames[i].Map[LeftLeg3.Mid] = frames[i].Map[LeftLeg1.Mid]
frames[i].Map[LeftLeg3.Tip] = frames[i].Map[LeftLeg1.Tip]
}
for i := range frames {
e := len(frames) - 1
frames[e-i].Map[RightLeg1.Base] = invU(frames[i].Map[LeftLeg1.Base])
frames[e-i].Map[RightLeg1.Mid] = invU(frames[i].Map[LeftLeg1.Mid])
frames[e-i].Map[RightLeg1.Tip] = invU(frames[i].Map[LeftLeg1.Tip])
frames[e-i].Map[RightLeg2.Base] = invU(frames[i].Map[LeftLeg1.Base])
frames[e-i].Map[RightLeg2.Mid] = invU(frames[i].Map[LeftLeg1.Mid])
frames[e-i].Map[RightLeg2.Tip] = invU(frames[i].Map[LeftLeg1.Tip])
frames[e-i].Map[RightLeg3.Base] = invU(frames[i].Map[LeftLeg1.Base])
frames[e-i].Map[RightLeg3.Mid] = invU(frames[i].Map[LeftLeg1.Mid])
frames[e-i].Map[RightLeg3.Tip] = invU(frames[i].Map[LeftLeg1.Tip])
}
mul := 2
// 24 frames to match 125 bpm
//
out := []SequenceStep{
{frames[0], 5 * mul},
{frames[1], 3 * mul},
{frames[2], 2 * mul},
{frames[3], 2 * mul},
{frames[4], 5 * mul},
{frames[3], 2 * mul},
{frames[2], 2 * mul},
{frames[1], 3 * mul},
{frames[0], 0}, // Fake the anim smoothing
}
Write(out)
}
func invU(a []int) []int {
out := make([]int, len(a))
for i, v := range a {
if i == 0 {
out[i] = v
} else {
if v == U {
out[i] = U
} else {
out[i] = -v
}
}
}
return out
}
func r2j(a float64) int {
return int(a*(128/math.Pi) + .5)
}
func j2r(j int) float64 {
return float64(j) * (math.Pi / 128)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment