Ver Fonte

Add animation blending

Panagiotis Christopoulos Charitos há 5 anos atrás
pai
commit
9f9c2fa6d2

+ 3 - 1
samples/skeletal_animation/Main.cpp

@@ -42,7 +42,9 @@ public:
 		{
 			AnimationPlayInfo animInfo;
 			animInfo.m_startTime = 0.5;
-			animInfo.m_repeatTimes = 2.0;
+			animInfo.m_repeatTimes = 3.0;
+			animInfo.m_blendInTime = 0.5;
+			animInfo.m_blendOutTime = 0.35;
 			getSceneGraph()
 				.findSceneNode("droid.001")
 				.getComponent<SkinComponent>()

+ 9 - 9
src/anki/math/Quat.h

@@ -79,11 +79,11 @@ public:
 
 	explicit TQuat(const TMat<T, 3, 3>& m3)
 	{
-		const T trace = m3(0, 0) + m3(1, 1) + m3(2, 2) + 1.0;
+		const T trace = m3(0, 0) + m3(1, 1) + m3(2, 2) + T(1.0);
 		if(trace > EPSILON)
 		{
-			T s = 0.5 / sqrt<T>(trace);
-			w() = 0.25 / s;
+			T s = T(0.5) / sqrt<T>(trace);
+			w() = T(0.25) / s;
 			x() = (m3(2, 1) - m3(1, 2)) * s;
 			y() = (m3(0, 2) - m3(2, 0)) * s;
 			z() = (m3(1, 0) - m3(0, 1)) * s;
@@ -92,27 +92,27 @@ public:
 		{
 			if(m3(0, 0) > m3(1, 1) && m3(0, 0) > m3(2, 2))
 			{
-				T s = 0.5 / sqrt<T>(1.0 + m3(0, 0) - m3(1, 1) - m3(2, 2));
+				T s = T(0.5) / sqrt<T>(T(1.0) + m3(0, 0) - m3(1, 1) - m3(2, 2));
 				w() = (m3(1, 2) - m3(2, 1)) * s;
-				x() = 0.25 / s;
+				x() = T(0.25) / s;
 				y() = (m3(0, 1) + m3(1, 0)) * s;
 				z() = (m3(0, 2) + m3(2, 0)) * s;
 			}
 			else if(m3(1, 1) > m3(2, 2))
 			{
-				T s = 0.5 / sqrt<T>(1.0 + m3(1, 1) - m3(0, 0) - m3(2, 2));
+				T s = T(0.5) / sqrt<T>(T(1.0) + m3(1, 1) - m3(0, 0) - m3(2, 2));
 				w() = (m3(0, 2) - m3(2, 0)) * s;
 				x() = (m3(0, 1) + m3(1, 0)) * s;
-				y() = 0.25 / s;
+				y() = T(0.25) / s;
 				z() = (m3(1, 2) + m3(2, 1)) * s;
 			}
 			else
 			{
-				T s = 0.5 / sqrt<T>(1.0 + m3(2, 2) - m3(0, 0) - m3(1, 1));
+				T s = T(0.5) / sqrt<T>(T(1.0) + m3(2, 2) - m3(0, 0) - m3(1, 1));
 				w() = (m3(0, 1) - m3(1, 0)) * s;
 				x() = (m3(0, 2) + m3(2, 0)) * s;
 				y() = (m3(1, 2) + m3(2, 1)) * s;
-				z() = 0.25 / s;
+				z() = T(0.25) / s;
 			}
 		}
 	}

+ 2 - 2
src/anki/math/Transform.h

@@ -38,13 +38,13 @@ public:
 		const TVec<T, 3> s1 = m4.getColumn(1).xyz();
 		const TVec<T, 3> s2 = m4.getColumn(2).xyz();
 
-		const TVec<T, 3> scales{s0.getLength(), s1.getLength(), s2.getLength()};
+		const TVec<T, 3> scales(s0.getLength(), s1.getLength(), s2.getLength());
 		const T E = T(0.001);
 		(void)E;
 		ANKI_ASSERT(
 			isZero(scales.x() - scales.y(), E) && isZero(scales.y() - scales.z(), E) && "Expecting uniform scale");
 
-		m_rotation.setColumns(s0 / scales.x(), s1 / scales.y(), s2 / scales.z(), TVec<T, 3>(0.0));
+		m_rotation.setColumns(s0 / scales.x(), s1 / scales.x(), s2 / scales.x(), TVec<T, 3>(0.0));
 		m_origin = m4.getTranslationPart().xyz0();
 		m_scale = scales.x();
 		checkW();

+ 61 - 7
src/anki/scene/components/SkinComponent.cpp

@@ -20,22 +20,38 @@ SkinComponent::SkinComponent(SceneNode* node, SkeletonResourcePtr skeleton)
 	ANKI_ASSERT(node);
 
 	m_boneTrfs.create(m_node->getAllocator(), m_skeleton->getBones().getSize());
-	for(Mat4& trf : m_boneTrfs)
+	m_animationTrfs.create(m_node->getAllocator(), m_skeleton->getBones().getSize());
+
+	for(U32 i = 0; i < m_boneTrfs.getSize(); ++i)
 	{
-		trf.setIdentity();
+		m_boneTrfs[i].setIdentity();
+		m_animationTrfs[i] = {Vec3(0.0f), Quat::getIdentity(), 1.0f};
 	}
 }
 
 SkinComponent::~SkinComponent()
 {
 	m_boneTrfs.destroy(m_node->getAllocator());
+	m_animationTrfs.destroy(m_node->getAllocator());
 }
 
 void SkinComponent::playAnimation(U32 track, AnimationResourcePtr anim, const AnimationPlayInfo& info)
 {
+	const Second animDuration = anim->getDuration();
+
 	m_tracks[track].m_anim = anim;
 	m_tracks[track].m_absoluteStartTime = m_absoluteTime + info.m_startTime;
 	m_tracks[track].m_relativeTimePassed = 0.0;
+	if(info.m_repeatTimes > 0.0)
+	{
+		m_tracks[track].m_blendInTime = min(animDuration * info.m_repeatTimes, info.m_blendInTime);
+		m_tracks[track].m_blendOutTime = min(animDuration * info.m_repeatTimes, info.m_blendOutTime);
+	}
+	else
+	{
+		m_tracks[track].m_blendInTime = info.m_blendInTime;
+		m_tracks[track].m_blendOutTime = 0.0; // Irrelevant
+	}
 	m_tracks[track].m_repeatTimes = info.m_repeatTimes;
 }
 
@@ -64,9 +80,10 @@ Error SkinComponent::update(SceneNode& node, Second prevTime, Second crntTime, B
 			continue;
 		}
 
-		const Second animationDuration = track.m_anim->getDuration();
+		const Second clipDuration = track.m_anim->getDuration();
+		const Second animationDuration = track.m_repeatTimes * clipDuration;
 
-		if(track.m_repeatTimes > 0.0 && track.m_relativeTimePassed > track.m_repeatTimes * animationDuration)
+		if(track.m_repeatTimes > 0.0 && track.m_relativeTimePassed > animationDuration)
 		{
 			// Animation finished
 			continue;
@@ -87,6 +104,7 @@ Error SkinComponent::update(SceneNode& node, Second prevTime, Second crntTime, B
 				ANKI_SCENE_LOGW("Animation is referencing unknown bone \"%s\"", &channel.m_name[0]);
 				continue;
 			}
+			const U32 boneIdx = bone->getIndex();
 
 			// Interpolate
 			Vec3 position;
@@ -94,9 +112,44 @@ Error SkinComponent::update(SceneNode& node, Second prevTime, Second crntTime, B
 			F32 scale;
 			track.m_anim->interpolate(i, animTime, position, rotation, scale);
 
+			// Blend with previous track
+			if(bonesAnimated.get(boneIdx) && (track.m_blendInTime > 0.0 || track.m_blendOutTime > 0.0))
+			{
+				F32 blendInFactor;
+				if(track.m_blendInTime > 0.0)
+				{
+					blendInFactor = min(1.0f, F32(animTime / track.m_blendInTime));
+				}
+				else
+				{
+					blendInFactor = 1.0f;
+				}
+
+				F32 blendOutFactor;
+				if(track.m_blendOutTime > 0.0)
+				{
+					blendOutFactor = min(1.0f, F32((animationDuration - animTime) / track.m_blendOutTime));
+				}
+				else
+				{
+					blendOutFactor = 1.0f;
+				}
+
+				const F32 factor = blendInFactor * blendOutFactor;
+
+				if(factor < 1.0f)
+				{
+					const Trf& prevTrf = m_animationTrfs[boneIdx];
+
+					position = linearInterpolate(prevTrf.m_translation, position, factor);
+					rotation = prevTrf.m_rotation.slerp(rotation, factor);
+					scale = linearInterpolate(prevTrf.m_scale, scale, factor);
+				}
+			}
+
 			// Store
-			bonesAnimated.set(bone->getIndex());
-			m_boneTrfs[bone->getIndex()] = Mat4(position.xyz1(), Mat3(rotation), scale);
+			bonesAnimated.set(boneIdx);
+			m_animationTrfs[boneIdx] = {position, rotation, scale};
 		}
 	}
 
@@ -125,7 +178,8 @@ void SkinComponent::visitBones(
 
 	if(bonesAnimated.get(bone.getIndex()))
 	{
-		outMat = parentTrf * m_boneTrfs[bone.getIndex()];
+		const Trf& t = m_animationTrfs[bone.getIndex()];
+		outMat = parentTrf * Mat4(t.m_translation.xyz1(), Mat3(t.m_rotation), t.m_scale);
 	}
 	else
 	{

+ 17 - 0
src/anki/scene/components/SkinComponent.h

@@ -26,6 +26,12 @@ public:
 
 	/// Negative means infinite.
 	F32 m_repeatTimes = 1.0f;
+
+	/// The time from when the animation starts until it fully replaces the animations of previous tracks.
+	Second m_blendInTime = 0.0f;
+
+	/// The time from when the animation ends until it until it has zero influence to the animations of previous tracks.
+	Second m_blendOutTime = 0.0f;
 };
 
 /// Skin component.
@@ -65,12 +71,23 @@ private:
 		AnimationResourcePtr m_anim;
 		Second m_absoluteStartTime = 0.0;
 		Second m_relativeTimePassed = 0.0;
+		Second m_blendInTime = 0.0;
+		Second m_blendOutTime = 0.0f;
 		F32 m_repeatTimes = 1.0f;
 	};
 
+	class Trf
+	{
+	public:
+		Vec3 m_translation;
+		Quat m_rotation;
+		F32 m_scale;
+	};
+
 	SceneNode* m_node;
 	SkeletonResourcePtr m_skeleton;
 	DynamicArray<Mat4> m_boneTrfs;
+	DynamicArray<Trf> m_animationTrfs;
 	Aabb m_boneBoundingVolume{Vec3(-1.0f), Vec3(1.0f)};
 	Array<Track, MAX_ANIMATION_TRACKS> m_tracks;
 	Second m_absoluteTime = 0.0;