Files
aego/anim/ctrl_binary.go
T
sanspie 515f4ca1b3 Stage 11a: animation (D333–D340, D344, D354–D357)
Clips (.aeanim) and controllers (.aectrl) with importers, AnimationPlayer and Animator with crossfades, root motion and events on fixed steps, rewind support. Editor: Animation panel (dope sheet, curves, record, preview, trajectory), Animator panel, sprite sheet slicer and flipbooks, Programming workspace, export panel redesign. Motion 2D ledge climb. 11c: the component and Create palette.
2026-10-25 15:31:22 +03:00

150 lines
3.6 KiB
Go

package anim
import (
"math"
"strconv"
"aego/core/curve"
"aego/core/ids"
)
const (
ctrlMagic = "AECT"
CtrlBinaryVersion = 2
minParam = 13
minState = 35
minTransition = 22
minCond = 13
)
const (
transExit uint8 = 1 << iota
transInterrupt
)
func putF64(b []byte, f float64) []byte {
return le.AppendUint64(b, math.Float64bits(f))
}
func (r *reader) f64() float64 {
return math.Float64frombits(r.u64())
}
func boolByte(v bool) byte {
if v {
return 1
}
return 0
}
func MarshalGraph(g *Graph) []byte {
b := make([]byte, 0, 64+len(g.States)*minState+len(g.Transitions)*minTransition)
b = append(b, ctrlMagic...)
b = le.AppendUint32(b, CtrlBinaryVersion)
b = append(b, byte(g.Ease))
b = le.AppendUint32(b, uint32(g.Entry))
b = le.AppendUint32(b, uint32(len(g.Params)))
for _, p := range g.Params {
b = putStr(b, p.Name)
b = append(b, byte(p.Kind))
b = putF64(b, p.Default)
}
b = le.AppendUint32(b, uint32(len(g.States)))
for _, s := range g.States {
b = putStr(b, s.Name)
b = le.AppendUint64(b, s.Clip.Hi)
b = le.AppendUint64(b, s.Clip.Lo)
b = putF32(b, s.Speed)
b = append(b, byte(s.Loop), boolByte(s.HasLoop), byte(s.Events))
b = putF32(b, s.Pos[0])
b = putF32(b, s.Pos[1])
}
b = le.AppendUint32(b, uint32(len(g.Transitions)))
for i := range g.Transitions {
t := &g.Transitions[i]
var f uint8
if t.HasExit {
f |= transExit
}
if t.Interrupt {
f |= transInterrupt
}
b = le.AppendUint32(b, uint32(t.From))
b = le.AppendUint32(b, uint32(t.To))
b = putF32(b, t.Duration)
b = putF32(b, t.ExitTime)
b = append(b, f)
b = le.AppendUint32(b, uint32(len(t.Conds)))
for _, c := range t.Conds {
b = le.AppendUint32(b, uint32(c.Param))
b = append(b, byte(c.Op))
b = putF64(b, c.Value)
}
}
return b
}
func UnmarshalGraph(data []byte) (*Graph, error) {
if len(data) < 8 || string(data[:4]) != ctrlMagic {
return nil, badArtifact("not an animation controller artifact")
}
r := reader{b: data[4:]}
if v := r.u32(); v != CtrlBinaryVersion {
return nil, badArtifact("unsupported animation controller artifact version " + strconv.FormatUint(uint64(v), 10))
}
g := &Graph{}
g.Ease = curve.Easing(r.u8())
g.Entry = int32(r.u32())
g.Params = sized[Param](r.count(minParam))
for i := range g.Params {
p := &g.Params[i]
p.Name = r.str()
p.Kind = ParamKind(r.u8())
p.Default = r.f64()
}
g.States = sized[State](r.count(minState))
for i := range g.States {
s := &g.States[i]
s.Name = r.str()
hi := r.u64()
s.Clip = ids.AssetID{Hi: hi, Lo: r.u64()}
s.Speed = r.f32()
s.Loop = LoopMode(r.u8())
s.HasLoop = r.u8() != 0
s.Events = EventPolicy(r.u8())
s.Pos = [2]float32{r.f32(), r.f32()}
s.HasPos = true
}
g.Transitions = sized[Transition](r.count(minTransition))
for i := range g.Transitions {
t := &g.Transitions[i]
t.From = int32(r.u32())
t.To = int32(r.u32())
t.Duration = r.f32()
t.ExitTime = r.f32()
f := r.u8()
t.HasExit, t.Interrupt = f&transExit != 0, f&transInterrupt != 0
t.Conds = sized[Cond](r.count(minCond))
for j := range t.Conds {
c := &t.Conds[j]
c.Param = int32(r.u32())
c.Op = Op(r.u8())
c.Value = r.f64()
}
}
if r.bad || len(r.b) != 0 {
return nil, badArtifact("animation controller artifact is truncated or has trailing bytes")
}
for i := range g.Params {
if g.Params[i].Kind > ParamTrigger {
return nil, badArtifact("animation controller artifact has an unknown parameter kind")
}
}
for i := range g.States {
if s := &g.States[i]; s.Loop > LoopPingPong || s.Events > EventsIncoming {
return nil, badArtifact("animation controller artifact has an unknown loop or event policy")
}
}
return g, nil
}