Files
aego/anim/machine.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

186 lines
4.1 KiB
Go

package anim
import "aego/core/curve"
const Frozen int32 = -1
const maxHops = 8
type ChangeKind uint8
const (
ChangeEnter ChangeKind = iota
ChangeExit
ChangeFreeze
)
type Change struct {
Kind ChangeKind
State int32
}
// Machine is the state of one animator in a Graph; Prev is a state or Frozen, a pose captured when a blend was interrupted.
type Machine struct {
State int32
Time float64
Last float64
Prev int32
PrevTime float64
PrevLast float64
BlendT float32
BlendDur float32
Blend bool
Started bool
}
func ease(e curve.Easing, u float32) float32 {
if e == curve.None {
return u
}
return curve.Ease(e, u)
}
func (m *Machine) Weight(g *Graph) float32 {
if !m.Blend || m.BlendDur <= 0 {
return 1
}
return ease(g.Ease, min(max(m.BlendT/m.BlendDur, 0), 1))
}
func (m *Machine) WeightAt(g *Graph, blendT float32) float32 {
if !m.Blend || m.BlendDur <= 0 {
return 1
}
return ease(g.Ease, min(max(blendT/m.BlendDur, 0), 1))
}
func (m *Machine) Progress(g *Graph) float32 {
if !m.Started || int(m.State) >= len(g.States) {
return 0
}
l := g.States[m.State].Length
if l <= 0 {
return 0
}
return float32(m.Time / float64(l))
}
func (m *Machine) reached(g *Graph, exit float32) bool {
l := g.States[m.State].Length
return l <= 0 || m.Time >= float64(exit)*float64(l)
}
func (m *Machine) pick(g *Graph, params []float64) (int, bool) {
for pass := range 2 {
for i := range g.Transitions {
t := &g.Transitions[i]
anyState := t.From == AnyState
switch {
case (pass == 0) != anyState:
continue
case !anyState && t.From != m.State:
continue
case anyState && t.To == m.State:
continue
case m.Blend && !t.Interrupt:
continue
case t.HasExit && !m.reached(g, t.ExitTime):
continue
case !g.holds(t, params):
continue
}
return i, true
}
}
return -1, false
}
func (m *Machine) fire(to int32, dur float32, out []Change) []Change {
interrupt := m.Blend
if interrupt && m.Prev >= 0 {
out = append(out, Change{Kind: ChangeExit, State: m.Prev})
}
switch {
case dur <= 0:
out = append(out, Change{Kind: ChangeExit, State: m.State})
m.Blend, m.Prev, m.BlendT, m.BlendDur = false, Frozen, 0, 0
case interrupt:
out = append(out, Change{Kind: ChangeFreeze, State: m.State}, Change{Kind: ChangeExit, State: m.State})
m.Prev, m.PrevTime, m.PrevLast = Frozen, 0, 0
m.Blend, m.BlendT, m.BlendDur = true, 0, dur
default:
m.Prev, m.PrevTime, m.PrevLast = m.State, m.Time, m.Last
m.Blend, m.BlendT, m.BlendDur = true, 0, dur
}
m.State, m.Time, m.Last = to, 0, 0
return append(out, Change{Kind: ChangeEnter, State: to})
}
func (m *Machine) jump(g *Graph, to int32, dur float32, out []Change) []Change {
if to < 0 || int(to) >= len(g.States) {
return out
}
if !m.Started {
*m = Machine{State: to, Prev: Frozen, Started: true}
return append(out, Change{Kind: ChangeEnter, State: to})
}
return m.fire(to, dur, out)
}
func (m *Machine) Play(g *Graph, to int32, out []Change) []Change {
return m.jump(g, to, 0, out)
}
func (m *Machine) CrossFade(g *Graph, to int32, dur float32, out []Change) []Change {
return m.jump(g, to, max(dur, 0), out)
}
func (m *Machine) endBlend(out []Change) []Change {
if m.Prev >= 0 {
out = append(out, Change{Kind: ChangeExit, State: m.Prev})
}
m.Blend, m.Prev, m.BlendT, m.BlendDur = false, Frozen, 0, 0
return out
}
func (m *Machine) Step(g *Graph, dt float32, params []float64, out []Change) []Change {
if len(g.States) == 0 {
return out
}
if dt != dt || dt < 0 {
dt = 0
}
if !m.Started {
e := g.Entry
if e < 0 || int(e) >= len(g.States) {
e = 0
}
out = m.jump(g, e, 0, out)
}
m.Last = m.Time
m.Time += float64(dt) * float64(g.States[m.State].Speed)
if m.Blend {
m.PrevLast = m.PrevTime
if m.Prev >= 0 {
m.PrevTime += float64(dt) * float64(g.States[m.Prev].Speed)
}
m.BlendT += dt
if m.BlendT >= m.BlendDur {
out = m.endBlend(out)
}
}
for range maxHops {
i, ok := m.pick(g, params)
if !ok {
break
}
t := &g.Transitions[i]
g.consume(t, params)
out = m.fire(t.To, t.Duration, out)
if t.Duration > 0 {
break
}
}
return out
}