Skip to content

Commit

Permalink
Add animation_player and animation_context classes. Add animation_com…
Browse files Browse the repository at this point in the history
…ponent struct. Change function signature of animation_track output functions. Add animation duration functions. Make animation_system advance animation_component players.
  • Loading branch information
cjhoward committed Jun 22, 2024
1 parent b3d0a9b commit b18f7f3
Show file tree
Hide file tree
Showing 16 changed files with 486 additions and 68 deletions.
18 changes: 18 additions & 0 deletions src/engine/animation/animation-context.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2023 C. J. Howard
// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef ANTKEEPER_ANIMATION_ANIMATION_CONTEXT_HPP
#define ANTKEEPER_ANIMATION_ANIMATION_CONTEXT_HPP

#include <entt/entt.hpp>

/**
* Context for animation track output functions.
*/
struct animation_context
{
/** Handle to the entity being animated. */
entt::handle handle;
};

#endif // ANTKEEPER_ANIMATION_ANIMATION_CONTEXT_HPP
11 changes: 11 additions & 0 deletions src/engine/animation/animation-curve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#include <engine/animation/animation-curve.hpp>
#include <algorithm>
#include <iterator>

float animation_curve::evaluate(float time) const
{
Expand All @@ -28,3 +30,12 @@ float animation_curve::evaluate(float time) const
return m_interpolator(*previous, *next, time);
}

float animation_curve::duration() const
{
if (m_keyframes.empty())
{
return 0.0f;
}

return std::max(0.0f, m_keyframes.rbegin()->time);
}
3 changes: 3 additions & 0 deletions src/engine/animation/animation-curve.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class animation_curve
return m_extrapolator;
}

/** Returns the non-negative duration of the curve, in seconds. */
[[nodiscard]] float duration() const;

private:
keyframe_container m_keyframes;
keyframe_interpolator_type m_interpolator{interpolate_keyframes_linear};
Expand Down
20 changes: 20 additions & 0 deletions src/engine/animation/animation-player-state.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2023 C. J. Howard
// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef ANTKEEPER_ANIMATION_ANIMATION_PLAYBACK_STATE_HPP
#define ANTKEEPER_ANIMATION_ANIMATION_PLAYBACK_STATE_HPP

/** Animation player states. */
enum class animation_player_state
{
/** Animation player is stopped. */
stopped,

/** Animation player is playing. */
playing,

/** Animation player is paused. */
paused
};

#endif // ANTKEEPER_ANIMATION_ANIMATION_PLAYBACK_STATE_HPP
131 changes: 131 additions & 0 deletions src/engine/animation/animation-player.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-FileCopyrightText: 2023 C. J. Howard
// SPDX-License-Identifier: GPL-3.0-or-later

#include <engine/animation/animation-player.hpp>

void animation_player::advance(float seconds)
{
if (!m_sequence)
{
// No active animation sequence, advance position and return
m_position += seconds;
return;
}

// Remember previous playback position
const auto previous_position = m_position;

// Advance playback position
m_position += seconds;

// Loop
std::size_t loop_count = 0;
if (m_looping && m_position >= m_sequence_duration)
{
if (m_sequence_duration > 0.0f)
{
// Calculate looped position
const auto looped_position = std::fmod(m_position, m_sequence_duration);

// Calculate number of times looped
loop_count = static_cast<std::size_t>(m_position / m_sequence_duration);

// Set current position to looped position
m_position = looped_position;
}
else
{
// Zero-duration looping sequence
m_position = 0.0f;
}
}

for (const auto& [path, track]: m_sequence->tracks())
{
if (!track.output())
{
// Ignore tracks with no output functions
continue;
}

if (m_sample_buffer.size() < track.channels().size())
{
// Grow sample buffer to accommodate track channels
m_sample_buffer.resize(track.channels().size());
}

// Sample track
track.sample(m_position, m_sample_buffer);

// Pass sample buffer and animation context to track output function
track.output()(m_sample_buffer, m_context);
}

if (loop_count)
{
// Trigger cues on [previous position, m_sequence_duration)
m_sequence->trigger_cues(previous_position, m_sequence_duration, m_context);

// For each additional loop, trigger cues on [0, m_sequence_duration)
for (std::size_t i = 1; i < loop_count; ++i)
{
m_sequence->trigger_cues(0.0f, m_sequence_duration, m_context);
}

// Trigger cues on [0, m_position)
m_sequence->trigger_cues(0.0f, m_position, m_context);
}
else
{
// Trigger cues on [previous_position, m_position)
m_sequence->trigger_cues(previous_position, m_position, m_context);
}
}

void animation_player::play(std::shared_ptr<animation_sequence> sequence)
{
m_sequence = std::move(sequence);

// Determine duration of sequence
if (m_sequence)
{
m_sequence_duration = m_sequence->duration();
}
else
{
m_sequence_duration = 0.0f;
}

m_state = animation_player_state::playing;
}

void animation_player::play()
{
m_state = animation_player_state::playing;
}

void animation_player::stop()
{
m_state = animation_player_state::stopped;
m_position = 0.0f;
}

void animation_player::rewind()
{
m_position = 0.0f;
}

void animation_player::pause()
{
m_state = animation_player_state::paused;
}

void animation_player::seek(float seconds)
{
m_position = seconds;
}

void animation_player::loop(bool enabled)
{
m_looping = enabled;
}
121 changes: 121 additions & 0 deletions src/engine/animation/animation-player.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: 2023 C. J. Howard
// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef ANTKEEPER_ANIMATION_ANIMATION_PLAYER_HPP
#define ANTKEEPER_ANIMATION_ANIMATION_PLAYER_HPP

#include <engine/animation/animation-player-state.hpp>
#include <engine/animation/animation-sequence.hpp>
#include <engine/animation/animation-context.hpp>
#include <memory>
#include <functional>
#include <map>
#include <string>
#include <span>
#include <vector>

/**
* Plays animation sequences.
*/
class animation_player
{
public:
/**
* Advances the animation sequence by a given timestep.
*
* @param seconds Timestep, in seconds.
*/
void advance(float seconds);

/**
* Starts playing an animation sequence.
*
* @param sequence Animation sequence to play.
*/
void play(std::shared_ptr<animation_sequence> sequence);

/** Starts playing the current animation sequence. */
void play();

/** Stops playing the current animation sequence. */
void stop();

/** Rewinds the current animation sequence. */
void rewind();

/** Pauses the current animation sequence. */
void pause();

/**
* Sets the playback position of the animation player.
*
* @param seconds Offset from the start of an animation sequence, in seconds.
*/
void seek(float seconds);

/**
* Enables or disables looping of the animation sequence.
*
* @param enabled `true` to enable looping, `false` to disable looping.
*/
void loop(bool enabled);

/** Returns the state of the animation player. */
[[nodiscard]] inline constexpr const auto& state() const noexcept
{
return m_state;
}

/** Returns `true` if the animation player is stopped, `false` otherwise. */
[[nodiscard]] inline constexpr auto is_stopped() const noexcept
{
return m_state == animation_player_state::stopped;
}

/** Returns `true` if the animation player is playing, `false` otherwise. */
[[nodiscard]] inline constexpr auto is_playing() const noexcept
{
return m_state == animation_player_state::playing;
}

/** Returns `true` if the animation player is paused, `false` otherwise. */
[[nodiscard]] inline constexpr auto is_paused() const noexcept
{
return m_state == animation_player_state::paused;
}

/** Returns the playback position of the animation player. */
[[nodiscard]] inline constexpr auto position() const noexcept
{
return m_position;
}

/** Returns `true` if sequence looping is enabled, `false` otherwise. */
[[nodiscard]] inline constexpr auto is_looping() const noexcept
{
return m_looping;
}

/** Returns a reference to the animation context of the player. */
[[nodiscard]] inline constexpr auto& context() noexcept
{
return m_context;
}

/** @copydoc context() */
[[nodiscard]] inline constexpr const auto& context() const noexcept
{
return m_context;
}

private:
std::shared_ptr<animation_sequence> m_sequence;
float m_sequence_duration{};
animation_player_state m_state{animation_player_state::stopped};
float m_position{};
bool m_looping{};
std::vector<float> m_sample_buffer;
animation_context m_context{};
};

#endif // ANTKEEPER_ANIMATION_PLAYER_HPP
22 changes: 13 additions & 9 deletions src/engine/animation/animation-sequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,11 @@
#include <engine/resources/deserializer.hpp>
#include <engine/resources/deserialize-error.hpp>
#include <engine/resources/resource-loader.hpp>
#include <algorithm>
#include <format>
#include <nlohmann/json.hpp>

void animation_sequence::sample_tracks(void* context, float time) const
{
for (const auto& [key, track]: m_tracks)
{
track.sample(context, time);
}
}

void animation_sequence::trigger_cues(void* context, float start_time, float end_time) const
void animation_sequence::trigger_cues(float start_time, float end_time, animation_context& context) const
{
const auto start_it = m_cues.lower_bound(start_time);
const auto end_it = m_cues.upper_bound(end_time);
Expand All @@ -34,6 +27,17 @@ void animation_sequence::trigger_cues(void* context, float start_time, float end
}
}

float animation_sequence::duration() const
{
float max_duration = 0.0f;
for (const auto& [path, track]: m_tracks)
{
max_duration = std::max(max_duration, track.duration());
}

return max_duration;
}

/**
* Deserializes an animation sequence.
*
Expand Down
Loading

0 comments on commit b18f7f3

Please sign in to comment.