287 lines
8.0 KiB
Plaintext
287 lines
8.0 KiB
Plaintext
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];
|
|
model := get_model_by_handle(e.renderable.model);
|
|
|
|
if state.type == {
|
|
case .SINGLE;
|
|
{
|
|
animation := *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 := model.animations[state.blend_tree.points[p0].index];
|
|
anim1 := 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];
|
|
model := get_model_by_handle(e.renderable.model);
|
|
|
|
if state.type == {
|
|
case .SINGLE;
|
|
{
|
|
state.single.time += dt * state.single.multiplier;
|
|
|
|
animation := *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 := *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};
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|