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); } } }