Initial commit
This commit is contained in:
285
animation/animator.jai
Normal file
285
animation/animator.jai
Normal file
@@ -0,0 +1,285 @@
|
||||
Animation_State_Type :: enum {
|
||||
SINGLE;
|
||||
BLEND_TREE_1D;
|
||||
}
|
||||
|
||||
Animation_Point :: struct {
|
||||
position: float;
|
||||
index: s32;
|
||||
|
||||
time: float;
|
||||
multiplier: float = 1.0;
|
||||
looping: bool;
|
||||
}
|
||||
|
||||
Animation_Transition :: struct {
|
||||
time: float;
|
||||
duration: float;
|
||||
|
||||
from_index: s32;
|
||||
to_index: s32;
|
||||
}
|
||||
|
||||
Animation_State :: struct {
|
||||
type: Animation_State_Type;
|
||||
|
||||
union {
|
||||
single : struct {
|
||||
time: float;
|
||||
multiplier: float;
|
||||
looping: bool;
|
||||
index: s32;
|
||||
}
|
||||
blend_tree : struct {
|
||||
value: float; // Range is 0.0 - 1.0
|
||||
points: [..] Animation_Point;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Animator :: struct {
|
||||
playing: bool;
|
||||
|
||||
state_index: s32;
|
||||
states: [..] Animation_State;
|
||||
|
||||
playing_transition: bool;
|
||||
transition : Animation_Transition;
|
||||
}
|
||||
|
||||
sample_animation_node :: (animation: Animation, f0: s32, f1: s32, t: float, node_index: s32) -> position: Vector3, rotation: Quaternion, scale: Vector3 {
|
||||
if node_index >= animation.nodes.count return .{0,0,0}, .{0,0,0,1}, .{1,1,1};
|
||||
|
||||
node_anim := animation.nodes[node_index];
|
||||
pos : Vector3 = ---;
|
||||
rot : Quaternion = ---;
|
||||
scale : Vector3 = ---;
|
||||
|
||||
if node_anim.position.count == 0 {
|
||||
pos = node_anim.const_position;
|
||||
} else {
|
||||
pos = lerp(node_anim.position[f0], node_anim.position[f1], t);
|
||||
}
|
||||
|
||||
if node_anim.rotation.count == 0 {
|
||||
rot = node_anim.const_rotation;
|
||||
} else {
|
||||
rot = slerp(node_anim.rotation[f0], node_anim.rotation[f1], t);
|
||||
}
|
||||
|
||||
if node_anim.scale.count == 0 {
|
||||
scale = node_anim.const_scale;
|
||||
} else {
|
||||
scale = lerp(node_anim.scale[f0], node_anim.scale[f1], t);
|
||||
}
|
||||
|
||||
return pos, rot, scale;
|
||||
}
|
||||
|
||||
sample_animation :: (e: *Entity, animation: Animation, time: float, weight: float) {
|
||||
frame_time := time * animation.framerate;
|
||||
|
||||
// Sample!
|
||||
f0 := min(cast(s32)frame_time + 0, animation.num_frames - 1);
|
||||
f1 := min(cast(s32)frame_time + 1, animation.num_frames - 1);
|
||||
|
||||
t := cast(float)min(frame_time - cast(float)f0, 1.0);
|
||||
|
||||
for * e.renderable.nodes {
|
||||
pos, rot, scale := sample_animation_node(animation, f0, f1, t, cast(s32)it_index);
|
||||
it.transform.position += pos * weight;
|
||||
|
||||
// Make sure that we interpolate with the shortest path
|
||||
if dot(it.transform.orientation, rot) < 0.0 {
|
||||
rot = -rot;
|
||||
}
|
||||
|
||||
it.transform.orientation += rot * weight;
|
||||
it.transform.scale += scale * weight;
|
||||
}
|
||||
}
|
||||
|
||||
animation_is_done :: (e: *Entity) -> bool {
|
||||
assert(cast(bool)(e.flags & Entity_Flags.ANIMATED));
|
||||
|
||||
animator := e.animator;
|
||||
|
||||
if animator.playing_transition return false;
|
||||
|
||||
state := animator.states[animator.state_index];
|
||||
|
||||
if state.type == {
|
||||
case .SINGLE;
|
||||
animation := e.renderable.model.animations[state.single.index];
|
||||
return state.single.time >= animation.duration - 0.0001;
|
||||
case .BLEND_TREE_1D;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
animation_time :: (e: *Entity) -> float {
|
||||
assert(cast(bool)(e.flags & Entity_Flags.ANIMATED));
|
||||
|
||||
animator := e.animator;
|
||||
|
||||
if animator.playing_transition return 0.0;
|
||||
|
||||
state := animator.states[animator.state_index];
|
||||
|
||||
if state.type == {
|
||||
case .SINGLE;
|
||||
animation := e.renderable.model.animations[state.single.index];
|
||||
return state.single.time;
|
||||
case .BLEND_TREE_1D;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
animation_time_normalized :: (e: *Entity) -> float {
|
||||
assert(cast(bool)(e.flags & Entity_Flags.ANIMATED));
|
||||
|
||||
animator := e.animator;
|
||||
|
||||
if animator.playing_transition return 0.0;
|
||||
|
||||
state := animator.states[animator.state_index];
|
||||
|
||||
if state.type == {
|
||||
case .SINGLE;
|
||||
animation := e.renderable.model.animations[state.single.index];
|
||||
return state.single.time / xx animation.duration;
|
||||
case .BLEND_TREE_1D;
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
sample_animation_state :: (e: *Entity, index: s32, weight: float) {
|
||||
state := *e.animator.states[index];
|
||||
|
||||
if state.type == {
|
||||
case .SINGLE;
|
||||
{
|
||||
animation := *e.renderable.model.animations[state.single.index];
|
||||
sample_animation(e, animation, min(state.single.time, cast(float)animation.duration - 0.0001), weight);
|
||||
}
|
||||
case .BLEND_TREE_1D;
|
||||
{
|
||||
p0 : s32;
|
||||
p1 : s32;
|
||||
found := false;
|
||||
current_value := state.blend_tree.value;
|
||||
|
||||
for index: 0..state.blend_tree.points.count-1 {
|
||||
p := *state.blend_tree.points[index];
|
||||
|
||||
if current_value >= p.position && index != state.blend_tree.points.count-1 {
|
||||
p0 = xx index;
|
||||
p1 = p0 + 1;
|
||||
}
|
||||
}
|
||||
|
||||
p0_position := state.blend_tree.points[p0].position;
|
||||
p1_position := state.blend_tree.points[p1].position;
|
||||
position := state.blend_tree.value - p0_position;
|
||||
range := p1_position - p0_position;
|
||||
alpha := position / range;
|
||||
|
||||
anim0 := e.renderable.model.animations[state.blend_tree.points[p0].index];
|
||||
anim1 := e.renderable.model.animations[state.blend_tree.points[p1].index];
|
||||
time0 := state.blend_tree.points[p0].time;
|
||||
time1 := state.blend_tree.points[p1].time;
|
||||
|
||||
sample_animation(e, anim0, time0, (1.0 - alpha) * weight);
|
||||
sample_animation(e, anim1, time1, alpha * weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transition_to_animation_state :: (e: *Entity, index: s32, transition_duration: float) {
|
||||
assert(cast(bool)(e.flags & Entity_Flags.ANIMATED));
|
||||
|
||||
e.animator.playing_transition = true;
|
||||
e.animator.transition.time = 0.0;
|
||||
e.animator.transition.duration = transition_duration;
|
||||
e.animator.transition.from_index = e.animator.state_index;
|
||||
e.animator.transition.to_index = index;
|
||||
|
||||
// @Incomplete: Missing blend tree
|
||||
e.animator.states[index].single.time = 0.0;
|
||||
}
|
||||
|
||||
update_animation_state :: (e: *Entity, index: s32, dt: float) {
|
||||
state := *e.animator.states[index];
|
||||
|
||||
if state.type == {
|
||||
case .SINGLE;
|
||||
{
|
||||
state.single.time += dt * state.single.multiplier;
|
||||
|
||||
animation := *e.renderable.model.animations[state.single.index];
|
||||
|
||||
if state.single.time > animation.duration {
|
||||
if state.single.looping {
|
||||
state.single.time = cast(float)(state.single.time - animation.duration);
|
||||
} else {
|
||||
state.single.time = xx animation.duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
case .BLEND_TREE_1D;
|
||||
{
|
||||
p0 : s32;
|
||||
p1 : s32;
|
||||
found := false;
|
||||
|
||||
for *p: state.blend_tree.points {
|
||||
p.time += dt * p.multiplier;
|
||||
|
||||
animation := *e.renderable.model.animations[p.index];
|
||||
|
||||
if p.time > animation.duration {
|
||||
if p.looping {
|
||||
p.time = cast(float)(p.time - animation.duration);
|
||||
} else {
|
||||
p.time = xx animation.duration - 0.0001; // Is this correct?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_animator :: (e: *Entity, animator: *Animator, dt: float) {
|
||||
if animator.playing {
|
||||
// Reset all transforms
|
||||
for * e.renderable.nodes {
|
||||
it.transform.position = .{0,0,0};
|
||||
it.transform.scale = .{0,0,0};
|
||||
it.transform.orientation = .{0,0,0,0};
|
||||
it.has_sampled_animation = false;
|
||||
}
|
||||
|
||||
if animator.playing_transition {
|
||||
update_animation_state(e, animator.transition.from_index, dt);
|
||||
update_animation_state(e, animator.transition.to_index, dt);
|
||||
|
||||
animator.transition.time += dt;
|
||||
alpha := animator.transition.time / animator.transition.duration;
|
||||
clamped := clamp(alpha, 0.0, 1.0);
|
||||
|
||||
sample_animation_state(e, animator.transition.from_index, 1.0 - clamped);
|
||||
sample_animation_state(e, animator.transition.to_index, clamped);
|
||||
|
||||
if alpha >= 1.0 {
|
||||
animator.playing_transition = false;
|
||||
animator.state_index = animator.transition.to_index;
|
||||
}
|
||||
} else {
|
||||
update_animation_state(e, animator.state_index, dt);
|
||||
sample_animation_state(e, animator.state_index, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user