Pārlūkot izejas kodu

world: add MeshAnimationPlayer

Part-of: #276
Daniele Bartolini 11 mēneši atpakaļ
vecāks
revīzija
0b53ff698e

+ 14 - 3
src/world/animation_state_machine.cpp

@@ -33,6 +33,7 @@ AnimationStateMachine::AnimationStateMachine(Allocator &a
 	, ResourceManager &rm
 	, UnitManager &um
 	, SpriteAnimationPlayer &sprite_player
+	, MeshAnimationPlayer &mesh_player
 	)
 	: _marker(ANIMATION_STATE_MACHINE_MARKER)
 	, _resource_manager(&rm)
@@ -41,6 +42,7 @@ AnimationStateMachine::AnimationStateMachine(Allocator &a
 	, _machines(a)
 	, _events(a)
 	, _sprite_animation_player(&sprite_player)
+	, _mesh_animation_player(&mesh_player)
 {
 	_unit_destroy_callback.destroy = unit_destroyed_callback_bridge;
 	_unit_destroy_callback.user_data = this;
@@ -190,15 +192,24 @@ void AnimationStateMachine::update(float dt)
 
 		if (mi.anim_resource != anim_resource) {
 			mi.anim_resource = anim_resource;
-			if (mi.anim_type == RESOURCE_TYPE_SPRITE_ANIMATION) {
-				sprite_animation_player::destroy(*_sprite_animation_player, mi.anim_id);
+			if (mi.anim_type == RESOURCE_TYPE_MESH_ANIMATION) {
+				if (mesh_animation_player::has(*_mesh_animation_player, mi.anim_id))
+					mesh_animation_player::destroy(*_mesh_animation_player, mi.anim_id);
+				mi.anim_id = mesh_animation_player::create(*_mesh_animation_player, (const MeshAnimationResource *)anim_resource);
+				mi.time = 0.0f;
+				mi.time_total = ((const MeshAnimationResource *)anim_resource)->total_time;
+			} else if (mi.anim_type == RESOURCE_TYPE_SPRITE_ANIMATION) {
+				if (sprite_animation_player::has(*_sprite_animation_player, mi.anim_id))
+					sprite_animation_player::destroy(*_sprite_animation_player, mi.anim_id);
 				mi.anim_id = sprite_animation_player::create(*_sprite_animation_player, (const SpriteAnimationResource *)anim_resource);
 				mi.time = 0.0f;
 				mi.time_total = ((const SpriteAnimationResource *)anim_resource)->total_time;
 			}
 		}
 
-		if (mi.anim_type == RESOURCE_TYPE_SPRITE_ANIMATION) {
+		if (mi.anim_type == RESOURCE_TYPE_MESH_ANIMATION) {
+			// TODO.
+		} else if (mi.anim_type == RESOURCE_TYPE_SPRITE_ANIMATION) {
 			sprite_animation_player::evaluate(*_sprite_animation_player
 				, mi.anim_id
 				, mi.time

+ 3 - 0
src/world/animation_state_machine.h

@@ -9,6 +9,7 @@
 #include "resource/state_machine_resource.h"
 #include "resource/types.h"
 #include "world/event_stream.h"
+#include "world/mesh_animation_player.h"
 #include "world/sprite_animation_player.h"
 #include "world/types.h"
 
@@ -38,12 +39,14 @@ struct AnimationStateMachine
 	EventStream _events;
 	UnitDestroyCallback _unit_destroy_callback;
 	SpriteAnimationPlayer *_sprite_animation_player;
+	MeshAnimationPlayer *_mesh_animation_player;
 
 	///
 	AnimationStateMachine(Allocator &a
 		, ResourceManager &rm
 		, UnitManager &um
 		, SpriteAnimationPlayer &sprite_player
+		, MeshAnimationPlayer &mesh_player
 		);
 
 	///

+ 170 - 0
src/world/mesh_animation_player.cpp

@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2012-2025 Daniele Bartolini et al.
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "core/containers/array.inl"
+#include "core/math/constants.h"
+#include "core/math/constants.h"
+#include "core/math/matrix4x4.inl"
+#include "core/math/quaternion.inl"
+#include "core/math/vector3.inl"
+#include "core/strings/string_id.inl"
+#include "device/device.h"
+#include "device/profiler.h"
+#include "resource/resource_manager.h"
+#include "world/mesh_animation_player.h"
+#include "world/scene_graph.h"
+#include <stdio.h>
+
+namespace crown
+{
+namespace mesh_animation_player
+{
+	/// Copies @a num animation keys from @a playhead into the
+	/// corresponding track segment @a segments. Returns a
+	/// pointer to the new playhead.
+	static const AnimationKey *fetch_keys(AnimationTrackSegment *tracks, const AnimationKey *playhead, u32 num = 1, u32 expected_track_id = UINT32_MAX)
+	{
+		if (expected_track_id != UINT32_MAX) {
+			CE_ASSERT(expected_track_id == playhead->h.track_id
+				, "Expected track %u stream gave %u"
+				, expected_track_id
+				, playhead->h.track_id
+				);
+		}
+
+		AnimationTrackSegment *t = &tracks[playhead->h.track_id];
+		for (u32 i = 0; i < num; ++i) {
+			t->keys[0] = t->keys[1];
+			t->keys[1] = *playhead++;
+		}
+
+		return playhead;
+	}
+
+	static void init(MeshAnimationPlayer &p, MeshAnimation &anim)
+	{
+		// Initialize tracks with animation data.
+		// We need 2 samples for each animation track to begin interpolating curves.
+		AnimationTrackSegment *tracks = &p._tracks[anim.tracks_offset];
+		for (u32 track_id = 0; track_id < anim.num_tracks; ++track_id) {
+			anim.playhead = fetch_keys(tracks, anim.playhead, 1, track_id);
+			anim.playhead = fetch_keys(tracks, anim.playhead, 1, track_id);
+		}
+	}
+
+	AnimationId create(MeshAnimationPlayer &p, const MeshAnimationResource *animation_resource)
+	{
+		MeshAnimationPlayer::Index &index = p._indices[p._freelist_dequeue];
+		p._freelist_dequeue = index.next;
+		index.id += ANIMATION_ID_ADD;
+		index.index = array::size(p._animations);
+
+		MeshAnimation anim;
+		anim.id = index.id;
+		anim.tracks_offset = array::size(p._tracks);
+		anim.num_tracks = animation_resource->num_tracks;
+		anim.playhead = mesh_animation_resource::animation_keys(animation_resource);
+		anim.animation_resource = animation_resource;
+		// Allocate tracks.
+		array::reserve(p._tracks, array::size(p._tracks) + animation_resource->num_tracks);
+		p._tracks._size += animation_resource->num_tracks;
+		init(p, anim);
+
+		array::push_back(p._animations, anim);
+		return anim.id;
+	}
+
+	void destroy(MeshAnimationPlayer &p, AnimationId anim_id)
+	{
+		MeshAnimationPlayer::Index &index = p._indices[anim_id & ANIMATION_INDEX_MASK];
+
+		MeshAnimation &a = p._animations[array::size(p._animations) - 1];
+		array::pop_back(p._animations);
+		p._indices[a.id & ANIMATION_INDEX_MASK].index = index.index;
+
+		index.index = UINT32_MAX;
+		p._indices[p._freelist_enqueue].next = anim_id & ANIMATION_INDEX_MASK;
+		p._freelist_enqueue = anim_id & ANIMATION_INDEX_MASK;
+	}
+
+	bool has(MeshAnimationPlayer &p, AnimationId anim_id)
+	{
+		MeshAnimationPlayer::Index &index = p._indices[anim_id & ANIMATION_INDEX_MASK];
+		return index.index != UINT32_MAX && index.id == anim_id;
+	}
+
+	void evaluate(MeshAnimationPlayer &p, AnimationId anim_id, f32 time, SceneGraph &scene_graph, const UnitId *bone_lookup)
+	{
+		MeshAnimationPlayer::Index &index = p._indices[anim_id & ANIMATION_INDEX_MASK];
+		MeshAnimation &anim = p._animations[index.index];
+		AnimationTrackSegment *tracks = &p._tracks[anim.tracks_offset];
+
+		u16 ts = time * 1000.0f;
+
+		const AnimationKey *first_key = mesh_animation_resource::animation_keys(anim.animation_resource);
+		if (anim.playhead - first_key == anim.animation_resource->num_keys) {
+			anim.playhead = first_key;
+			init(p, anim);
+			return;
+		}
+
+		// Fetch new keys until all tracks have enough data
+		// to interpolate values at current time.
+		u32 num_ok = 0;
+		while (num_ok != anim.num_tracks) {
+			num_ok = 0;
+			for (u32 track_id = 0; track_id < anim.num_tracks; ++track_id) {
+				AnimationTrackSegment *track = &tracks[track_id];
+
+				if (track->keys[0].h.time > ts || ts > track->keys[1].h.time)
+					anim.playhead = fetch_keys(tracks, anim.playhead, 1, track_id);
+				else
+					num_ok++;
+			}
+		}
+
+		const u16 *bone_ids = mesh_animation_resource::bone_ids(anim.animation_resource);
+
+		// Evaluate animation data at current time.
+		for (u32 track_id = 0; track_id < anim.num_tracks; ++track_id) {
+			AnimationTrackSegment *track = &tracks[track_id];
+
+			CE_ENSURE(track->keys[0].h.time <= ts && ts <= track->keys[1].h.time);
+			u16 n = ts - track->keys[0].h.time;
+			u16 d = track->keys[1].h.time - track->keys[0].h.time;
+			f32 t = f32(n)/f32(d);
+			CE_ENSURE(t >= 0 && t <= 1);
+
+			if (track->keys[0].h.type == AnimationKeyHeader::Type::POSITION) {
+				Vector3 pos = lerp(track->keys[0].p.value, track->keys[1].p.value, t);
+				TransformInstance ti = scene_graph.instance(bone_lookup[bone_ids[track_id]]);
+				scene_graph.set_local_position(ti, pos);
+			} else if (track->keys[0].h.type == AnimationKeyHeader::Type::ROTATION) {
+				Quaternion rot = lerp(track->keys[0].r.value, track->keys[1].r.value, t);
+				TransformInstance ti = scene_graph.instance(bone_lookup[bone_ids[track_id]]);
+				scene_graph.set_local_rotation(ti, rot);
+			} else {
+				CE_FATAL("Unknown key type %u in track %u", track->keys[0].h.type, track_id);
+			}
+		}
+	}
+
+} // namespace mesh_animation_player
+
+MeshAnimationPlayer::MeshAnimationPlayer(Allocator &a)
+	: _animations(a)
+	, _tracks(a)
+{
+	for (u32 i = 0; i < countof(_indices); ++i) {
+		_indices[i].id = i;
+		_indices[i].next = i + 1;
+		_indices[i].index = UINT32_MAX;
+	}
+
+	_freelist_dequeue = 0;
+	_freelist_enqueue = countof(_indices) - 1;
+}
+
+} // namespace crown

+ 69 - 0
src/world/mesh_animation_player.h

@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2012-2025 Daniele Bartolini et al.
+ * SPDX-License-Identifier: MIT
+ */
+
+#pragma once
+
+#include "resource/mesh_skeleton_resource.h"
+#include "resource/mesh_animation_resource.h"
+#include "world/types.h" // UnitId
+
+namespace crown
+{
+struct AnimationTrackSegment
+{
+	AnimationKey keys[2];
+};
+
+struct MeshAnimation
+{
+	AnimationId id;
+	u32 tracks_offset;
+	u32 num_tracks;
+	const AnimationKey *playhead; ///< Next key to fetch in the animation stream.
+	const MeshAnimationResource *animation_resource;
+};
+
+#define MAX_ANIMATIONS       1024
+#define ANIMATION_INDEX_MASK (MAX_ANIMATIONS - 1)
+#define ANIMATION_ID_ADD     MAX_ANIMATIONS
+
+/// Evaluates bones' positions and rotations
+/// in a mesh animation at a particular time.
+struct MeshAnimationPlayer
+{
+	struct Index
+	{
+		AnimationId id;
+		u32 index;      ///< Index into _animations.
+		u32 next;       ///< Next free index slot.
+	};
+
+	u32 _freelist_dequeue;
+	u32 _freelist_enqueue;
+	Index _indices[MAX_ANIMATIONS];
+	Array<MeshAnimation> _animations;
+	Array<AnimationTrackSegment> _tracks; ///< The currently evaluated segment in each animation track.
+
+	///
+	MeshAnimationPlayer(Allocator &a);
+};
+
+namespace mesh_animation_player
+{
+	///
+	AnimationId create(MeshAnimationPlayer &p, const MeshAnimationResource *animation_resource);
+
+	///
+	void destroy(MeshAnimationPlayer &p, AnimationId anim_id);
+
+	///
+	bool has(MeshAnimationPlayer &p, AnimationId anim_id);
+
+	///
+	void evaluate(MeshAnimationPlayer &p, AnimationId anim_id, f32 time, SceneGraph &scene_graph, const UnitId *bone_lookup);
+
+} // namespace mesh_animation_player
+
+} // namespace crown

+ 3 - 1
src/world/world.cpp

@@ -69,7 +69,8 @@ World::World(Allocator &a
 	_sound_world   = CE_NEW(*_allocator, SoundWorld)(*_allocator);
 	_script_world  = CE_NEW(*_allocator, ScriptWorld)(*_allocator, um, rm, env, *this);
 	_sprite_animation_player = CE_NEW(*_allocator, SpriteAnimationPlayer)(*_allocator);
-	_animation_state_machine = CE_NEW(*_allocator, AnimationStateMachine)(*_allocator, rm, um, *_sprite_animation_player);
+	_mesh_animation_player = CE_NEW(*_allocator, MeshAnimationPlayer)(*_allocator);
+	_animation_state_machine = CE_NEW(*_allocator, AnimationStateMachine)(*_allocator, rm, um, *_sprite_animation_player, *_mesh_animation_player);
 
 	_gui_buffer.create();
 
@@ -97,6 +98,7 @@ World::~World()
 
 	// Destroy subsystems
 	CE_DELETE(*_allocator, _animation_state_machine);
+	CE_DELETE(*_allocator, _mesh_animation_player);
 	CE_DELETE(*_allocator, _sprite_animation_player);
 	CE_DELETE(*_allocator, _script_world);
 	CE_DELETE(*_allocator, _sound_world);

+ 2 - 0
src/world/world.h

@@ -13,6 +13,7 @@
 #include "resource/types.h"
 #include "world/event_stream.h"
 #include "world/gui.h"
+#include "world/mesh_animation_player.h"
 #include "world/sprite_animation_player.h"
 #include "world/types.h"
 
@@ -52,6 +53,7 @@ struct World
 	SoundWorld *_sound_world;
 	ScriptWorld *_script_world;
 	SpriteAnimationPlayer *_sprite_animation_player;
+	MeshAnimationPlayer *_mesh_animation_player;
 	AnimationStateMachine *_animation_state_machine;
 
 	Array<UnitId> _units;