Browse Source

Add the ability to play animations from GLTF

Panagiotis Christopoulos Charitos 3 years ago
parent
commit
6dabf37cf4

+ 6 - 312
AnKi/Importer/GltfImporter.cpp

@@ -221,11 +221,6 @@ Error GltfImporter::writeAll()
 {
 	populateNodePtrToIdx();
 
-	for(const cgltf_animation* anim = m_gltf->animations; anim < m_gltf->animations + m_gltf->animations_count; ++anim)
-	{
-		ANKI_CHECK(writeAnimation(*anim));
-	}
-
 	StringAuto sceneFname(m_alloc);
 	sceneFname.sprintf("%sScene.lua", m_outDir.cstr());
 	ANKI_CHECK(m_sceneFile.open(sceneFname.toCString(), FileOpenFlag::WRITE));
@@ -261,6 +256,11 @@ Error GltfImporter::writeAll()
 		return threadErr;
 	}
 
+	for(const cgltf_animation* anim = m_gltf->animations; anim < m_gltf->animations + m_gltf->animations_count; ++anim)
+	{
+		ANKI_CHECK(writeAnimation(*anim));
+	}
+
 	return err;
 }
 
@@ -360,7 +360,7 @@ void GltfImporter::populateNodePtrToIdx()
 
 StringAuto GltfImporter::getNodeName(const cgltf_node& node)
 {
-	StringAuto out{m_alloc};
+	StringAuto out(m_alloc);
 
 	if(node.name)
 	{
@@ -895,312 +895,6 @@ Error GltfImporter::writeModel(const cgltf_mesh& mesh)
 	return Error::NONE;
 }
 
-template<typename T>
-class GltfAnimKey
-{
-public:
-	Second m_time;
-	T m_value;
-};
-
-class GltfAnimChannel
-{
-public:
-	StringAuto m_name;
-	DynamicArrayAuto<GltfAnimKey<Vec3>> m_positions;
-	DynamicArrayAuto<GltfAnimKey<Quat>> m_rotations;
-	DynamicArrayAuto<GltfAnimKey<F32>> m_scales;
-
-	GltfAnimChannel(GenericMemoryPoolAllocator<U8> alloc)
-		: m_name(alloc)
-		, m_positions(alloc)
-		, m_rotations(alloc)
-		, m_scales(alloc)
-	{
-	}
-};
-
-/// Optimize out same animation keys.
-template<typename T, typename TZeroFunc, typename TLerpFunc>
-static void optimizeChannel(DynamicArrayAuto<GltfAnimKey<T>>& arr, const T& identity, TZeroFunc isZeroFunc,
-							TLerpFunc lerpFunc)
-{
-	if(arr.getSize() < 3)
-	{
-		return;
-	}
-
-	DynamicArrayAuto<GltfAnimKey<T>> newArr(arr.getAllocator());
-	newArr.emplaceBack(arr.getFront());
-	for(U32 i = 1; i < arr.getSize() - 1; ++i)
-	{
-		const GltfAnimKey<T>& left = arr[i - 1];
-		const GltfAnimKey<T>& middle = arr[i];
-		const GltfAnimKey<T>& right = arr[i + 1];
-
-		if(left.m_value == middle.m_value && middle.m_value == right.m_value)
-		{
-			// Skip it
-		}
-		else
-		{
-			const F32 factor = F32((middle.m_time - left.m_time) / (right.m_time - left.m_time));
-			ANKI_ASSERT(factor > 0.0f && factor < 1.0f);
-			const T lerpRez = lerpFunc(left.m_value, right.m_value, factor);
-			if(isZeroFunc(middle.m_value - lerpRez))
-			{
-				// It's redundant, skip it
-			}
-			else
-			{
-				newArr.emplaceBack(middle);
-			}
-		}
-	}
-	newArr.emplaceBack(arr.getBack());
-	ANKI_ASSERT(newArr.getSize() <= arr.getSize());
-
-	// Check if identity
-	if(newArr.getSize() == 2 && isZeroFunc(newArr[0].m_value - newArr[1].m_value)
-	   && isZeroFunc(newArr[0].m_value - identity))
-	{
-		newArr.destroy();
-	}
-
-	arr.destroy();
-	arr = std::move(newArr);
-}
-
-Error GltfImporter::writeAnimation(const cgltf_animation& anim)
-{
-	StringAuto fname(m_alloc);
-	fname.sprintf("%s%s", m_outDir.cstr(), computeAnimationResourceFilename(anim).cstr());
-	fname = fixFilename(fname);
-	ANKI_IMPORTER_LOGV("Importing animation %s", fname.cstr());
-
-	// Gather the channels
-	HashMapAuto<CString, Array<const cgltf_animation_channel*, 3>> channelMap(m_alloc);
-	U32 channelCount = 0;
-	for(U i = 0; i < anim.channels_count; ++i)
-	{
-		const cgltf_animation_channel& channel = anim.channels[i];
-		const StringAuto channelName = getNodeName(*channel.target_node);
-
-		U idx;
-		switch(channel.target_path)
-		{
-		case cgltf_animation_path_type_translation:
-			idx = 0;
-			break;
-		case cgltf_animation_path_type_rotation:
-			idx = 1;
-			break;
-		case cgltf_animation_path_type_scale:
-			idx = 2;
-			break;
-		default:
-			ANKI_ASSERT(0);
-			idx = 0;
-		}
-
-		auto it = channelMap.find(channelName.toCString());
-		if(it != channelMap.getEnd())
-		{
-			(*it)[idx] = &channel;
-		}
-		else
-		{
-			Array<const cgltf_animation_channel*, 3> arr = {};
-			arr[idx] = &channel;
-			channelMap.emplace(channelName.toCString(), arr);
-			++channelCount;
-		}
-	}
-
-	// Gather the keys
-	DynamicArrayAuto<GltfAnimChannel> tempChannels(m_alloc, channelCount, m_alloc);
-	channelCount = 0;
-	for(auto it = channelMap.getBegin(); it != channelMap.getEnd(); ++it)
-	{
-		Array<const cgltf_animation_channel*, 3> arr = *it;
-		const cgltf_animation_channel& anyChannel = (arr[0]) ? *arr[0] : ((arr[1]) ? *arr[1] : *arr[2]);
-		const StringAuto channelName = getNodeName(*anyChannel.target_node);
-
-		tempChannels[channelCount].m_name = channelName;
-
-		// Positions
-		if(arr[0])
-		{
-			const cgltf_animation_channel& channel = *arr[0];
-			DynamicArrayAuto<F32> keys(m_alloc);
-			readAccessor(*channel.sampler->input, keys);
-			DynamicArrayAuto<Vec3> positions(m_alloc);
-			readAccessor(*channel.sampler->output, positions);
-			if(keys.getSize() != positions.getSize())
-			{
-				ANKI_IMPORTER_LOGE("Position count should match they keyframes");
-				return Error::USER_DATA;
-			}
-
-			for(U32 i = 0; i < keys.getSize(); ++i)
-			{
-				GltfAnimKey<Vec3> key;
-				key.m_time = keys[i];
-				key.m_value = Vec3(positions[i].x(), positions[i].y(), positions[i].z());
-
-				tempChannels[channelCount].m_positions.emplaceBack(key);
-			}
-		}
-
-		// Rotations
-		if(arr[1])
-		{
-			const cgltf_animation_channel& channel = *arr[1];
-			DynamicArrayAuto<F32> keys(m_alloc);
-			readAccessor(*channel.sampler->input, keys);
-			DynamicArrayAuto<Quat> rotations(m_alloc);
-			readAccessor(*channel.sampler->output, rotations);
-			if(keys.getSize() != rotations.getSize())
-			{
-				ANKI_IMPORTER_LOGE("Rotation count should match they keyframes");
-				return Error::USER_DATA;
-			}
-
-			for(U32 i = 0; i < keys.getSize(); ++i)
-			{
-				GltfAnimKey<Quat> key;
-				key.m_time = keys[i];
-				key.m_value = Quat(rotations[i].x(), rotations[i].y(), rotations[i].z(), rotations[i].w());
-
-				tempChannels[channelCount].m_rotations.emplaceBack(key);
-			}
-		}
-
-		// Scales
-		if(arr[2])
-		{
-			const cgltf_animation_channel& channel = *arr[2];
-			DynamicArrayAuto<F32> keys(m_alloc);
-			readAccessor(*channel.sampler->input, keys);
-			DynamicArrayAuto<Vec3> scales(m_alloc);
-			readAccessor(*channel.sampler->output, scales);
-			if(keys.getSize() != scales.getSize())
-			{
-				ANKI_IMPORTER_LOGE("Scale count should match they keyframes");
-				return Error::USER_DATA;
-			}
-
-			for(U32 i = 0; i < keys.getSize(); ++i)
-			{
-				const F32 scaleEpsilon = 0.0001f;
-				if(absolute(scales[i][0] - scales[i][1]) > scaleEpsilon
-				   || absolute(scales[i][0] - scales[i][2]) > scaleEpsilon)
-				{
-					ANKI_IMPORTER_LOGE("Expecting uniform scale");
-					return Error::USER_DATA;
-				}
-
-				GltfAnimKey<F32> key;
-				key.m_time = keys[i];
-				key.m_value = scales[i][0];
-
-				if(absolute(key.m_value - 1.0f) <= scaleEpsilon)
-				{
-					key.m_value = 1.0f;
-				}
-
-				tempChannels[channelCount].m_scales.emplaceBack(key);
-			}
-		}
-
-		++channelCount;
-	}
-
-	// Optimize animation
-	constexpr F32 KILL_EPSILON = 0.001f; // 1 millimiter
-	for(GltfAnimChannel& channel : tempChannels)
-	{
-		optimizeChannel(
-			channel.m_positions, Vec3(0.0f),
-			[&](const Vec3& a) -> Bool {
-				return a.abs() < KILL_EPSILON;
-			},
-			[&](const Vec3& a, const Vec3& b, F32 u) -> Vec3 {
-				return linearInterpolate(a, b, u);
-			});
-		optimizeChannel(
-			channel.m_rotations, Quat::getIdentity(),
-			[&](const Quat& a) -> Bool {
-				return a.abs() < Quat(EPSILON * 20.0f);
-			},
-			[&](const Quat& a, const Quat& b, F32 u) -> Quat {
-				return a.slerp(b, u);
-			});
-		optimizeChannel(
-			channel.m_scales, 1.0f,
-			[&](const F32& a) -> Bool {
-				return absolute(a) < KILL_EPSILON;
-			},
-			[&](const F32& a, const F32& b, F32 u) -> F32 {
-				return linearInterpolate(a, b, u);
-			});
-	}
-
-	// Write file
-	File file;
-	ANKI_CHECK(file.open(fname.toCString(), FileOpenFlag::WRITE));
-
-	ANKI_CHECK(file.writeTextf("%s\n<animation>\n", XML_HEADER));
-	ANKI_CHECK(file.writeText("\t<channels>\n"));
-
-	for(const GltfAnimChannel& channel : tempChannels)
-	{
-		ANKI_CHECK(file.writeTextf("\t\t<channel name=\"%s\">\n", channel.m_name.cstr()));
-
-		// Positions
-		if(channel.m_positions.getSize())
-		{
-			ANKI_CHECK(file.writeText("\t\t\t<positionKeys>\n"));
-			for(const GltfAnimKey<Vec3>& key : channel.m_positions)
-			{
-				ANKI_CHECK(file.writeTextf("\t\t\t\t<key time=\"%f\">%f %f %f</key>\n", key.m_time, key.m_value.x(),
-										   key.m_value.y(), key.m_value.z()));
-			}
-			ANKI_CHECK(file.writeText("\t\t\t</positionKeys>\n"));
-		}
-
-		// Rotations
-		if(channel.m_rotations.getSize())
-		{
-			ANKI_CHECK(file.writeText("\t\t\t<rotationKeys>\n"));
-			for(const GltfAnimKey<Quat>& key : channel.m_rotations)
-			{
-				ANKI_CHECK(file.writeTextf("\t\t\t\t<key time=\"%f\">%f %f %f %f</key>\n", key.m_time, key.m_value.x(),
-										   key.m_value.y(), key.m_value.z(), key.m_value.w()));
-			}
-			ANKI_CHECK(file.writeText("\t\t\t</rotationKeys>\n"));
-		}
-
-		// Scales
-		if(channel.m_scales.getSize())
-		{
-			ANKI_CHECK(file.writeText("\t\t\t<scaleKeys>\n"));
-			for(const GltfAnimKey<F32>& key : channel.m_scales)
-			{
-				ANKI_CHECK(file.writeTextf("\t\t\t\t<key time=\"%f\">%f</key>\n", key.m_time, key.m_value));
-			}
-			ANKI_CHECK(file.writeText("\t\t\t</scaleKeys>\n"));
-		}
-
-		ANKI_CHECK(file.writeText("\t\t</channel>\n"));
-	}
-
-	ANKI_CHECK(file.writeText("\t</channels>\n"));
-	ANKI_CHECK(file.writeText("</animation>\n"));
-
-	return Error::NONE;
-}
-
 Error GltfImporter::writeSkeleton(const cgltf_skin& skin)
 {
 	StringAuto fname(m_alloc);

+ 350 - 0
AnKi/Importer/GltfImporterAnimation.cpp

@@ -0,0 +1,350 @@
+// Copyright (C) 2009-2022, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Importer/GltfImporter.h>
+
+namespace anki {
+
+template<typename T>
+class GltfAnimKey
+{
+public:
+	Second m_time;
+	T m_value;
+};
+
+class GltfAnimChannel
+{
+public:
+	StringAuto m_name;
+	DynamicArrayAuto<GltfAnimKey<Vec3>> m_positions;
+	DynamicArrayAuto<GltfAnimKey<Quat>> m_rotations;
+	DynamicArrayAuto<GltfAnimKey<F32>> m_scales;
+	const cgltf_node* m_targetNode;
+
+	GltfAnimChannel(GenericMemoryPoolAllocator<U8> alloc)
+		: m_name(alloc)
+		, m_positions(alloc)
+		, m_rotations(alloc)
+		, m_scales(alloc)
+	{
+	}
+};
+
+/// Optimize out same animation keys.
+template<typename T, typename TZeroFunc, typename TLerpFunc>
+static void optimizeChannel(DynamicArrayAuto<GltfAnimKey<T>>& arr, const T& identity, TZeroFunc isZeroFunc,
+							TLerpFunc lerpFunc)
+{
+	if(arr.getSize() < 3)
+	{
+		return;
+	}
+
+	DynamicArrayAuto<GltfAnimKey<T>> newArr(arr.getAllocator());
+	newArr.emplaceBack(arr.getFront());
+	for(U32 i = 1; i < arr.getSize() - 1; ++i)
+	{
+		const GltfAnimKey<T>& left = arr[i - 1];
+		const GltfAnimKey<T>& middle = arr[i];
+		const GltfAnimKey<T>& right = arr[i + 1];
+
+		if(left.m_value == middle.m_value && middle.m_value == right.m_value)
+		{
+			// Skip it
+		}
+		else
+		{
+			const F32 factor = F32((middle.m_time - left.m_time) / (right.m_time - left.m_time));
+			ANKI_ASSERT(factor > 0.0f && factor < 1.0f);
+			const T lerpRez = lerpFunc(left.m_value, right.m_value, factor);
+			if(isZeroFunc(middle.m_value - lerpRez))
+			{
+				// It's redundant, skip it
+			}
+			else
+			{
+				newArr.emplaceBack(middle);
+			}
+		}
+	}
+	newArr.emplaceBack(arr.getBack());
+	ANKI_ASSERT(newArr.getSize() <= arr.getSize());
+
+	// Check if identity
+	if(newArr.getSize() == 2 && isZeroFunc(newArr[0].m_value - newArr[1].m_value)
+	   && isZeroFunc(newArr[0].m_value - identity))
+	{
+		newArr.destroy();
+	}
+
+	arr.destroy();
+	arr = std::move(newArr);
+}
+
+Error GltfImporter::writeAnimation(const cgltf_animation& anim)
+{
+	StringAuto fname(m_alloc);
+	StringAuto animFname = computeAnimationResourceFilename(anim);
+	fname.sprintf("%s%s", m_outDir.cstr(), animFname.cstr());
+	fname = fixFilename(fname);
+	ANKI_IMPORTER_LOGV("Importing animation %s", fname.cstr());
+
+	// Gather the channels
+	HashMapAuto<CString, Array<const cgltf_animation_channel*, 3>> channelMap(m_alloc);
+	U32 channelCount = 0;
+	for(U i = 0; i < anim.channels_count; ++i)
+	{
+		const cgltf_animation_channel& channel = anim.channels[i];
+		const StringAuto channelName = getNodeName(*channel.target_node);
+
+		U idx;
+		switch(channel.target_path)
+		{
+		case cgltf_animation_path_type_translation:
+			idx = 0;
+			break;
+		case cgltf_animation_path_type_rotation:
+			idx = 1;
+			break;
+		case cgltf_animation_path_type_scale:
+			idx = 2;
+			break;
+		default:
+			ANKI_ASSERT(0);
+			idx = 0;
+		}
+
+		auto it = channelMap.find(channelName.toCString());
+		if(it != channelMap.getEnd())
+		{
+			(*it)[idx] = &channel;
+		}
+		else
+		{
+			Array<const cgltf_animation_channel*, 3> arr = {};
+			arr[idx] = &channel;
+			channelMap.emplace(channelName.toCString(), arr);
+			++channelCount;
+		}
+	}
+
+	// Gather the keys
+	DynamicArrayAuto<GltfAnimChannel> tempChannels(m_alloc, channelCount, m_alloc);
+	channelCount = 0;
+	for(auto it = channelMap.getBegin(); it != channelMap.getEnd(); ++it)
+	{
+		Array<const cgltf_animation_channel*, 3> arr = *it;
+		const cgltf_animation_channel& anyChannel = (arr[0]) ? *arr[0] : ((arr[1]) ? *arr[1] : *arr[2]);
+		const StringAuto channelName = getNodeName(*anyChannel.target_node);
+
+		tempChannels[channelCount].m_name = channelName;
+		tempChannels[channelCount].m_targetNode = anyChannel.target_node;
+
+		// Positions
+		if(arr[0])
+		{
+			const cgltf_animation_channel& channel = *arr[0];
+			DynamicArrayAuto<F32> keys(m_alloc);
+			readAccessor(*channel.sampler->input, keys);
+			DynamicArrayAuto<Vec3> positions(m_alloc);
+			readAccessor(*channel.sampler->output, positions);
+			if(keys.getSize() != positions.getSize())
+			{
+				ANKI_IMPORTER_LOGE("Position count should match they keyframes");
+				return Error::USER_DATA;
+			}
+
+			for(U32 i = 0; i < keys.getSize(); ++i)
+			{
+				GltfAnimKey<Vec3> key;
+				key.m_time = keys[i];
+				key.m_value = Vec3(positions[i].x(), positions[i].y(), positions[i].z());
+
+				tempChannels[channelCount].m_positions.emplaceBack(key);
+			}
+		}
+
+		// Rotations
+		if(arr[1])
+		{
+			const cgltf_animation_channel& channel = *arr[1];
+			DynamicArrayAuto<F32> keys(m_alloc);
+			readAccessor(*channel.sampler->input, keys);
+			DynamicArrayAuto<Quat> rotations(m_alloc);
+			readAccessor(*channel.sampler->output, rotations);
+			if(keys.getSize() != rotations.getSize())
+			{
+				ANKI_IMPORTER_LOGE("Rotation count should match they keyframes");
+				return Error::USER_DATA;
+			}
+
+			for(U32 i = 0; i < keys.getSize(); ++i)
+			{
+				GltfAnimKey<Quat> key;
+				key.m_time = keys[i];
+				key.m_value = Quat(rotations[i].x(), rotations[i].y(), rotations[i].z(), rotations[i].w());
+
+				tempChannels[channelCount].m_rotations.emplaceBack(key);
+			}
+		}
+
+		// Scales
+		if(arr[2])
+		{
+			const cgltf_animation_channel& channel = *arr[2];
+			DynamicArrayAuto<F32> keys(m_alloc);
+			readAccessor(*channel.sampler->input, keys);
+			DynamicArrayAuto<Vec3> scales(m_alloc);
+			readAccessor(*channel.sampler->output, scales);
+			if(keys.getSize() != scales.getSize())
+			{
+				ANKI_IMPORTER_LOGE("Scale count should match they keyframes");
+				return Error::USER_DATA;
+			}
+
+			Bool scaleErrorReported = false;
+			for(U32 i = 0; i < keys.getSize(); ++i)
+			{
+				const F32 scaleEpsilon = 0.0001f;
+
+				// Normalize the scale because scaleEpsilon is relative
+				Vec3 scale = scales[i];
+				scale.normalize();
+
+				if(!scaleErrorReported
+				   && (absolute(scale[0] - scale[1]) > scaleEpsilon || absolute(scale[0] - scale[2]) > scaleEpsilon))
+				{
+					ANKI_IMPORTER_LOGW("Expecting uniform scale (%f %f %f)", scales[i].x(), scales[i].y(),
+									   scales[i].z());
+					scaleErrorReported = true;
+				}
+
+				GltfAnimKey<F32> key;
+				key.m_time = keys[i];
+				key.m_value = scales[i][0];
+
+				if(absolute(key.m_value - 1.0f) <= scaleEpsilon)
+				{
+					key.m_value = 1.0f;
+				}
+
+				tempChannels[channelCount].m_scales.emplaceBack(key);
+			}
+		}
+
+		++channelCount;
+	}
+
+	// Optimize animation
+	constexpr F32 KILL_EPSILON = 0.001f; // 1 millimiter
+	for(GltfAnimChannel& channel : tempChannels)
+	{
+		optimizeChannel(
+			channel.m_positions, Vec3(0.0f),
+			[&](const Vec3& a) -> Bool {
+				return a.abs() < KILL_EPSILON;
+			},
+			[&](const Vec3& a, const Vec3& b, F32 u) -> Vec3 {
+				return linearInterpolate(a, b, u);
+			});
+		optimizeChannel(
+			channel.m_rotations, Quat::getIdentity(),
+			[&](const Quat& a) -> Bool {
+				return a.abs() < Quat(EPSILON * 20.0f);
+			},
+			[&](const Quat& a, const Quat& b, F32 u) -> Quat {
+				return a.slerp(b, u);
+			});
+		optimizeChannel(
+			channel.m_scales, 1.0f,
+			[&](const F32& a) -> Bool {
+				return absolute(a) < KILL_EPSILON;
+			},
+			[&](const F32& a, const F32& b, F32 u) -> F32 {
+				return linearInterpolate(a, b, u);
+			});
+	}
+
+	// Write file
+	File file;
+	ANKI_CHECK(file.open(fname.toCString(), FileOpenFlag::WRITE));
+
+	ANKI_CHECK(file.writeTextf("%s\n<animation>\n", XML_HEADER));
+	ANKI_CHECK(file.writeText("\t<channels>\n"));
+
+	for(const GltfAnimChannel& channel : tempChannels)
+	{
+		ANKI_CHECK(file.writeTextf("\t\t<channel name=\"%s\">\n", channel.m_name.cstr()));
+
+		// Positions
+		if(channel.m_positions.getSize())
+		{
+			ANKI_CHECK(file.writeText("\t\t\t<positionKeys>\n"));
+			for(const GltfAnimKey<Vec3>& key : channel.m_positions)
+			{
+				ANKI_CHECK(file.writeTextf("\t\t\t\t<key time=\"%f\">%f %f %f</key>\n", key.m_time, key.m_value.x(),
+										   key.m_value.y(), key.m_value.z()));
+			}
+			ANKI_CHECK(file.writeText("\t\t\t</positionKeys>\n"));
+		}
+
+		// Rotations
+		if(channel.m_rotations.getSize())
+		{
+			ANKI_CHECK(file.writeText("\t\t\t<rotationKeys>\n"));
+			for(const GltfAnimKey<Quat>& key : channel.m_rotations)
+			{
+				ANKI_CHECK(file.writeTextf("\t\t\t\t<key time=\"%f\">%f %f %f %f</key>\n", key.m_time, key.m_value.x(),
+										   key.m_value.y(), key.m_value.z(), key.m_value.w()));
+			}
+			ANKI_CHECK(file.writeText("\t\t\t</rotationKeys>\n"));
+		}
+
+		// Scales
+		if(channel.m_scales.getSize())
+		{
+			ANKI_CHECK(file.writeText("\t\t\t<scaleKeys>\n"));
+			for(const GltfAnimKey<F32>& key : channel.m_scales)
+			{
+				ANKI_CHECK(file.writeTextf("\t\t\t\t<key time=\"%f\">%f</key>\n", key.m_time, key.m_value));
+			}
+			ANKI_CHECK(file.writeText("\t\t\t</scaleKeys>\n"));
+		}
+
+		ANKI_CHECK(file.writeText("\t\t</channel>\n"));
+	}
+
+	ANKI_CHECK(file.writeText("\t</channels>\n"));
+	ANKI_CHECK(file.writeText("</animation>\n"));
+
+	// Hook up the animation to the scene
+	for(const GltfAnimChannel& channel : tempChannels)
+	{
+		if(channel.m_targetNode == nullptr)
+		{
+			continue;
+		}
+
+		// Only animate cameras for now
+		const cgltf_node& node = *channel.m_targetNode;
+		if((node.camera == nullptr) || node.name == nullptr)
+		{
+			continue;
+		}
+
+		// ANKI_CHECK(m_sceneFile.writeText("--[[\n"));
+
+		ANKI_CHECK(m_sceneFile.writeTextf("\nnode = scene:tryFindSceneNode(\"%s\")\n", node.name));
+		ANKI_CHECK(m_sceneFile.writeTextf("getEventManager():newAnimationEvent(\"%s\", \"%s\", node)\n",
+										  animFname.cstr(), node.name));
+
+		// ANKI_CHECK(m_sceneFile.writeText("--]]\n"));
+	}
+
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 1 - 1
AnKi/Resource/AnimationResource.h

@@ -73,7 +73,7 @@ public:
 	Error load(const ResourceFilename& filename, Bool async);
 
 	/// Get a vector of all animation channels
-	const DynamicArray<AnimationChannel>& getChannels() const
+	ConstWeakArray<AnimationChannel> getChannels() const
 	{
 		return m_channels;
 	}

+ 20 - 3
AnKi/Scene/Events/AnimationEvent.cpp

@@ -5,6 +5,7 @@
 
 #include <AnKi/Scene/Events/AnimationEvent.h>
 #include <AnKi/Scene/SceneNode.h>
+#include <AnKi/Scene/SceneGraph.h>
 #include <AnKi/Scene/Components/MoveComponent.h>
 #include <AnKi/Resource/ResourceManager.h>
 
@@ -15,10 +16,26 @@ AnimationEvent::AnimationEvent(EventManager* manager)
 {
 }
 
-Error AnimationEvent::init(const AnimationResourcePtr& anim, SceneNode* movableSceneNode)
+Error AnimationEvent::init(CString animationFilename, CString channelName, SceneNode* movableSceneNode)
 {
 	ANKI_ASSERT(movableSceneNode);
-	m_anim = anim;
+	ANKI_CHECK(getSceneGraph().getResourceManager().loadResource(animationFilename, m_anim));
+
+	m_channelIndex = 0;
+	for(const AnimationChannel& channel : m_anim->getChannels())
+	{
+		if(channel.m_name == channelName)
+		{
+			break;
+		}
+		++m_channelIndex;
+	}
+
+	if(m_channelIndex == m_anim->getChannels().getSize())
+	{
+		ANKI_SCENE_LOGE("Can't initialize AnimationEvent. Channel not found: %s", channelName.cstr());
+		return Error::USER_DATA;
+	}
 
 	Event::init(m_anim->getStartingTime(), m_anim->getDuration());
 	m_reanimate = true;
@@ -32,7 +49,7 @@ Error AnimationEvent::update([[maybe_unused]] Second prevUpdateTime, [[maybe_unu
 	Vec3 pos;
 	Quat rot;
 	F32 scale = 1.0;
-	m_anim->interpolate(0, crntTime, pos, rot, scale);
+	m_anim->interpolate(m_channelIndex, crntTime, pos, rot, scale);
 
 	Transform trf;
 	trf.setOrigin(pos.xyz0());

+ 2 - 1
AnKi/Scene/Events/AnimationEvent.h

@@ -17,13 +17,14 @@ class AnimationEvent : public Event
 public:
 	AnimationEvent(EventManager* manager);
 
-	Error init(const AnimationResourcePtr& anim, SceneNode* movableSceneNode);
+	Error init(CString animationFilename, CString channel, SceneNode* movableSceneNode);
 
 	/// Implements Event::update
 	Error update(Second prevUpdateTime, Second crntTime) override;
 
 private:
 	AnimationResourcePtr m_anim;
+	U32 m_channelIndex = 0;
 };
 /// @}
 

+ 191 - 38
AnKi/Script/Scene.cpp

@@ -62,7 +62,7 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 using WeakArrayBodyComponentPtr = WeakArray<BodyComponent*>;
 
 LuaUserDataTypeInfo luaUserDataTypeInfoWeakArraySceneNodePtr = {
-	-2049340231983728743, "WeakArraySceneNodePtr", LuaUserData::computeSizeForGarbageCollected<WeakArraySceneNodePtr>(),
+	-8739932090814685424, "WeakArraySceneNodePtr", LuaUserData::computeSizeForGarbageCollected<WeakArraySceneNodePtr>(),
 	nullptr, nullptr};
 
 template<>
@@ -182,7 +182,7 @@ static inline void wrapWeakArraySceneNodePtr(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoWeakArrayBodyComponentPtr = {
-	-77306059902609659, "WeakArrayBodyComponentPtr",
+	-7706209051073311304, "WeakArrayBodyComponentPtr",
 	LuaUserData::computeSizeForGarbageCollected<WeakArrayBodyComponentPtr>(), nullptr, nullptr};
 
 template<>
@@ -301,7 +301,7 @@ static inline void wrapWeakArrayBodyComponentPtr(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoMoveComponent = {5705089883580564808, "MoveComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoMoveComponent = {-8687627984466261764, "MoveComponent",
 														LuaUserData::computeSizeForGarbageCollected<MoveComponent>(),
 														nullptr, nullptr};
 
@@ -699,7 +699,7 @@ static inline void wrapMoveComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoLightComponent = {1983317719094136706, "LightComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoLightComponent = {7365641557642621447, "LightComponent",
 														 LuaUserData::computeSizeForGarbageCollected<LightComponent>(),
 														 nullptr, nullptr};
 
@@ -1263,7 +1263,7 @@ static inline void wrapLightComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoDecalComponent = {1608322450957771206, "DecalComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoDecalComponent = {5204471888737717555, "DecalComponent",
 														 LuaUserData::computeSizeForGarbageCollected<DecalComponent>(),
 														 nullptr, nullptr};
 
@@ -1467,7 +1467,7 @@ static inline void wrapDecalComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoLensFlareComponent = {
-	8907976916708253981, "LensFlareComponent", LuaUserData::computeSizeForGarbageCollected<LensFlareComponent>(),
+	3129239218800905411, "LensFlareComponent", LuaUserData::computeSizeForGarbageCollected<LensFlareComponent>(),
 	nullptr, nullptr};
 
 template<>
@@ -1639,7 +1639,7 @@ static inline void wrapLensFlareComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoBodyComponent = {3200642028059037222, "BodyComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoBodyComponent = {-2321292920370861788, "BodyComponent",
 														LuaUserData::computeSizeForGarbageCollected<BodyComponent>(),
 														nullptr, nullptr};
 
@@ -1812,7 +1812,7 @@ static inline void wrapBodyComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoTriggerComponent = {
-	-6986213597269759334, "TriggerComponent", LuaUserData::computeSizeForGarbageCollected<TriggerComponent>(), nullptr,
+	8727234490176981006, "TriggerComponent", LuaUserData::computeSizeForGarbageCollected<TriggerComponent>(), nullptr,
 	nullptr};
 
 template<>
@@ -1976,7 +1976,7 @@ static inline void wrapTriggerComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityComponent = {
-	-5264030377238763574, "FogDensityComponent", LuaUserData::computeSizeForGarbageCollected<FogDensityComponent>(),
+	-1966916321067375525, "FogDensityComponent", LuaUserData::computeSizeForGarbageCollected<FogDensityComponent>(),
 	nullptr, nullptr};
 
 template<>
@@ -2180,7 +2180,7 @@ static inline void wrapFogDensityComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoFrustumComponent = {
-	-6878389274404774307, "FrustumComponent", LuaUserData::computeSizeForGarbageCollected<FrustumComponent>(), nullptr,
+	-1191485642898408008, "FrustumComponent", LuaUserData::computeSizeForGarbageCollected<FrustumComponent>(), nullptr,
 	nullptr};
 
 template<>
@@ -2357,7 +2357,7 @@ static inline void wrapFrustumComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoGlobalIlluminationProbeComponent = {
-	-915714841666302735, "GlobalIlluminationProbeComponent",
+	-1018126859883492712, "GlobalIlluminationProbeComponent",
 	LuaUserData::computeSizeForGarbageCollected<GlobalIlluminationProbeComponent>(), nullptr, nullptr};
 
 template<>
@@ -2604,7 +2604,7 @@ static inline void wrapGlobalIlluminationProbeComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoReflectionProbeComponent = {
-	-4107696363672449414, "ReflectionProbeComponent",
+	-927979292880454957, "ReflectionProbeComponent",
 	LuaUserData::computeSizeForGarbageCollected<ReflectionProbeComponent>(), nullptr, nullptr};
 
 template<>
@@ -2720,7 +2720,7 @@ static inline void wrapReflectionProbeComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoParticleEmitterComponent = {
-	149711203612747693, "ParticleEmitterComponent",
+	-6511516637176772129, "ParticleEmitterComponent",
 	LuaUserData::computeSizeForGarbageCollected<ParticleEmitterComponent>(), nullptr, nullptr};
 
 template<>
@@ -2794,7 +2794,7 @@ static inline void wrapParticleEmitterComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoGpuParticleEmitterComponent = {
-	-7426951470065804035, "GpuParticleEmitterComponent",
+	7059837890409742045, "GpuParticleEmitterComponent",
 	LuaUserData::computeSizeForGarbageCollected<GpuParticleEmitterComponent>(), nullptr, nullptr};
 
 template<>
@@ -2867,7 +2867,7 @@ static inline void wrapGpuParticleEmitterComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoModelComponent = {6773543014035061075, "ModelComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoModelComponent = {-3624966836559375132, "ModelComponent",
 														 LuaUserData::computeSizeForGarbageCollected<ModelComponent>(),
 														 nullptr, nullptr};
 
@@ -2940,7 +2940,7 @@ static inline void wrapModelComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoSkinComponent = {-3949860037553736583, "SkinComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoSkinComponent = {-8546506900929446763, "SkinComponent",
 														LuaUserData::computeSizeForGarbageCollected<SkinComponent>(),
 														nullptr, nullptr};
 
@@ -3014,7 +3014,7 @@ static inline void wrapSkinComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoSkyboxComponent = {
-	-4224390319590151870, "SkyboxComponent", LuaUserData::computeSizeForGarbageCollected<SkyboxComponent>(), nullptr,
+	-5875722332241774365, "SkyboxComponent", LuaUserData::computeSizeForGarbageCollected<SkyboxComponent>(), nullptr,
 	nullptr};
 
 template<>
@@ -3316,7 +3316,7 @@ static inline void wrapSkyboxComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode = {
-	7340783203210742539, "SceneNode", LuaUserData::computeSizeForGarbageCollected<SceneNode>(), nullptr, nullptr};
+	-5266826471794230711, "SceneNode", LuaUserData::computeSizeForGarbageCollected<SceneNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<SceneNode>()
@@ -4261,7 +4261,7 @@ static inline void wrapSceneNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoModelNode = {
-	-7084871930487371158, "ModelNode", LuaUserData::computeSizeForGarbageCollected<ModelNode>(), nullptr, nullptr};
+	-8050065867798521056, "ModelNode", LuaUserData::computeSizeForGarbageCollected<ModelNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<ModelNode>()
@@ -4324,7 +4324,7 @@ static inline void wrapModelNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoPerspectiveCameraNode = {
-	6296894285925075603, "PerspectiveCameraNode", LuaUserData::computeSizeForGarbageCollected<PerspectiveCameraNode>(),
+	4225474333355308316, "PerspectiveCameraNode", LuaUserData::computeSizeForGarbageCollected<PerspectiveCameraNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4387,7 +4387,7 @@ static inline void wrapPerspectiveCameraNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoPointLightNode = {6492387717863479905, "PointLightNode",
+LuaUserDataTypeInfo luaUserDataTypeInfoPointLightNode = {-476103666317335701, "PointLightNode",
 														 LuaUserData::computeSizeForGarbageCollected<PointLightNode>(),
 														 nullptr, nullptr};
 
@@ -4451,7 +4451,7 @@ static inline void wrapPointLightNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoSpotLightNode = {-8891132373613260050, "SpotLightNode",
+LuaUserDataTypeInfo luaUserDataTypeInfoSpotLightNode = {-9028941236445099739, "SpotLightNode",
 														LuaUserData::computeSizeForGarbageCollected<SpotLightNode>(),
 														nullptr, nullptr};
 
@@ -4516,7 +4516,7 @@ static inline void wrapSpotLightNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoDirectionalLightNode = {
-	3941685298661161126, "DirectionalLightNode", LuaUserData::computeSizeForGarbageCollected<DirectionalLightNode>(),
+	-5885044536233253731, "DirectionalLightNode", LuaUserData::computeSizeForGarbageCollected<DirectionalLightNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4580,7 +4580,7 @@ static inline void wrapDirectionalLightNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoStaticCollisionNode = {
-	-6253595010562372816, "StaticCollisionNode", LuaUserData::computeSizeForGarbageCollected<StaticCollisionNode>(),
+	-3321869028945608148, "StaticCollisionNode", LuaUserData::computeSizeForGarbageCollected<StaticCollisionNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4644,7 +4644,7 @@ static inline void wrapStaticCollisionNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoParticleEmitterNode = {
-	-8333277180212471620, "ParticleEmitterNode", LuaUserData::computeSizeForGarbageCollected<ParticleEmitterNode>(),
+	7361250518817995771, "ParticleEmitterNode", LuaUserData::computeSizeForGarbageCollected<ParticleEmitterNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4708,7 +4708,7 @@ static inline void wrapParticleEmitterNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoGpuParticleEmitterNode = {
-	-5871911407760419933, "GpuParticleEmitterNode",
+	-2839508838597227498, "GpuParticleEmitterNode",
 	LuaUserData::computeSizeForGarbageCollected<GpuParticleEmitterNode>(), nullptr, nullptr};
 
 template<>
@@ -4772,7 +4772,7 @@ static inline void wrapGpuParticleEmitterNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoReflectionProbeNode = {
-	3724239195748386698, "ReflectionProbeNode", LuaUserData::computeSizeForGarbageCollected<ReflectionProbeNode>(),
+	-5375614308316633893, "ReflectionProbeNode", LuaUserData::computeSizeForGarbageCollected<ReflectionProbeNode>(),
 	nullptr, nullptr};
 
 template<>
@@ -4836,7 +4836,7 @@ static inline void wrapReflectionProbeNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoDecalNode = {
-	8580638555371839795, "DecalNode", LuaUserData::computeSizeForGarbageCollected<DecalNode>(), nullptr, nullptr};
+	5640592796414838784, "DecalNode", LuaUserData::computeSizeForGarbageCollected<DecalNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<DecalNode>()
@@ -4899,7 +4899,7 @@ static inline void wrapDecalNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoTriggerNode = {
-	4136814088244084712, "TriggerNode", LuaUserData::computeSizeForGarbageCollected<TriggerNode>(), nullptr, nullptr};
+	6454325227279593862, "TriggerNode", LuaUserData::computeSizeForGarbageCollected<TriggerNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<TriggerNode>()
@@ -4961,7 +4961,7 @@ static inline void wrapTriggerNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityNode = {-758649891971596130, "FogDensityNode",
+LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityNode = {-2016225087705583098, "FogDensityNode",
 														 LuaUserData::computeSizeForGarbageCollected<FogDensityNode>(),
 														 nullptr, nullptr};
 
@@ -5026,7 +5026,7 @@ static inline void wrapFogDensityNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoGlobalIlluminationProbeNode = {
-	-4911210073662168021, "GlobalIlluminationProbeNode",
+	881842436982629931, "GlobalIlluminationProbeNode",
 	LuaUserData::computeSizeForGarbageCollected<GlobalIlluminationProbeNode>(), nullptr, nullptr};
 
 template<>
@@ -5090,7 +5090,7 @@ static inline void wrapGlobalIlluminationProbeNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoSkyboxNode = {
-	-1115369417956297324, "SkyboxNode", LuaUserData::computeSizeForGarbageCollected<SkyboxNode>(), nullptr, nullptr};
+	-2160062479151552786, "SkyboxNode", LuaUserData::computeSizeForGarbageCollected<SkyboxNode>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<SkyboxNode>()
@@ -5153,7 +5153,7 @@ static inline void wrapSkyboxNode(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoSceneGraph = {
-	-1529114004329230731, "SceneGraph", LuaUserData::computeSizeForGarbageCollected<SceneGraph>(), nullptr, nullptr};
+	1565953082694138001, "SceneGraph", LuaUserData::computeSizeForGarbageCollected<SceneGraph>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<SceneGraph>()
@@ -5977,6 +5977,65 @@ static int wrapSceneGraphsetActiveCameraNode(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap method SceneGraph::tryFindSceneNode.
+static inline int pwrapSceneGraphtryFindSceneNode(lua_State* l)
+{
+	[[maybe_unused]] LuaUserData* ud;
+	[[maybe_unused]] void* voidp;
+	[[maybe_unused]] PtrSize size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 2)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoSceneGraph, ud))
+	{
+		return -1;
+	}
+
+	SceneGraph* self = ud->getData<SceneGraph>();
+
+	// Pop arguments
+	const char* arg0;
+	if(ANKI_UNLIKELY(LuaBinder::checkString(l, 2, arg0)))
+	{
+		return -1;
+	}
+
+	// Call the method
+	SceneNode* ret = self->tryFindSceneNode(arg0);
+
+	// Push return value
+	if(ANKI_UNLIKELY(ret == nullptr))
+	{
+		lua_pushstring(l, "Glue code returned nullptr");
+		return -1;
+	}
+
+	voidp = lua_newuserdata(l, sizeof(LuaUserData));
+	ud = static_cast<LuaUserData*>(voidp);
+	luaL_setmetatable(l, "SceneNode");
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode;
+	ud->initPointed(&luaUserDataTypeInfoSceneNode, ret);
+
+	return 1;
+}
+
+/// Wrap method SceneGraph::tryFindSceneNode.
+static int wrapSceneGraphtryFindSceneNode(lua_State* l)
+{
+	int res = pwrapSceneGraphtryFindSceneNode(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Wrap class SceneGraph.
 static inline void wrapSceneGraph(lua_State* l)
 {
@@ -5995,10 +6054,11 @@ static inline void wrapSceneGraph(lua_State* l)
 	LuaBinder::pushLuaCFuncMethod(l, "newGlobalIlluminationProbeNode", wrapSceneGraphnewGlobalIlluminationProbeNode);
 	LuaBinder::pushLuaCFuncMethod(l, "newSkyboxNode", wrapSceneGraphnewSkyboxNode);
 	LuaBinder::pushLuaCFuncMethod(l, "setActiveCameraNode", wrapSceneGraphsetActiveCameraNode);
+	LuaBinder::pushLuaCFuncMethod(l, "tryFindSceneNode", wrapSceneGraphtryFindSceneNode);
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoEvent = {9135137537256635648, "Event",
+LuaUserDataTypeInfo luaUserDataTypeInfoEvent = {-127482733468747645, "Event",
 												LuaUserData::computeSizeForGarbageCollected<Event>(), nullptr, nullptr};
 
 template<>
@@ -6064,7 +6124,7 @@ static inline void wrapEvent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoLightEvent = {
-	-2703942033941419884, "LightEvent", LuaUserData::computeSizeForGarbageCollected<LightEvent>(), nullptr, nullptr};
+	-1511364883147733152, "LightEvent", LuaUserData::computeSizeForGarbageCollected<LightEvent>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<LightEvent>()
@@ -6183,7 +6243,7 @@ static inline void wrapLightEvent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoScriptEvent = {
-	2332491948276401877, "ScriptEvent", LuaUserData::computeSizeForGarbageCollected<ScriptEvent>(), nullptr, nullptr};
+	-577692491395731788, "ScriptEvent", LuaUserData::computeSizeForGarbageCollected<ScriptEvent>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<ScriptEvent>()
@@ -6199,7 +6259,7 @@ static inline void wrapScriptEvent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoJitterMoveEvent = {
-	4544058840347801874, "JitterMoveEvent", LuaUserData::computeSizeForGarbageCollected<JitterMoveEvent>(), nullptr,
+	3496086456905036915, "JitterMoveEvent", LuaUserData::computeSizeForGarbageCollected<JitterMoveEvent>(), nullptr,
 	nullptr};
 
 template<>
@@ -6274,8 +6334,25 @@ static inline void wrapJitterMoveEvent(lua_State* l)
 	lua_settop(l, 0);
 }
 
+LuaUserDataTypeInfo luaUserDataTypeInfoAnimationEvent = {-1032810681493696534, "AnimationEvent",
+														 LuaUserData::computeSizeForGarbageCollected<AnimationEvent>(),
+														 nullptr, nullptr};
+
+template<>
+const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<AnimationEvent>()
+{
+	return luaUserDataTypeInfoAnimationEvent;
+}
+
+/// Wrap class AnimationEvent.
+static inline void wrapAnimationEvent(lua_State* l)
+{
+	LuaBinder::createClass(l, &luaUserDataTypeInfoAnimationEvent);
+	lua_settop(l, 0);
+}
+
 LuaUserDataTypeInfo luaUserDataTypeInfoEventManager = {
-	-174784136875931487, "EventManager", LuaUserData::computeSizeForGarbageCollected<EventManager>(), nullptr, nullptr};
+	330041233457660952, "EventManager", LuaUserData::computeSizeForGarbageCollected<EventManager>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<EventManager>()
@@ -6502,6 +6579,80 @@ static int wrapEventManagernewJitterMoveEvent(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap method EventManager::newAnimationEvent.
+static inline int pwrapEventManagernewAnimationEvent(lua_State* l)
+{
+	[[maybe_unused]] LuaUserData* ud;
+	[[maybe_unused]] void* voidp;
+	[[maybe_unused]] PtrSize size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 4)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoEventManager, ud))
+	{
+		return -1;
+	}
+
+	EventManager* self = ud->getData<EventManager>();
+
+	// Pop arguments
+	const char* arg0;
+	if(ANKI_UNLIKELY(LuaBinder::checkString(l, 2, arg0)))
+	{
+		return -1;
+	}
+
+	const char* arg1;
+	if(ANKI_UNLIKELY(LuaBinder::checkString(l, 3, arg1)))
+	{
+		return -1;
+	}
+
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode;
+	if(ANKI_UNLIKELY(LuaBinder::checkUserData(l, 4, luaUserDataTypeInfoSceneNode, ud)))
+	{
+		return -1;
+	}
+
+	SceneNode* iarg2 = ud->getData<SceneNode>();
+	SceneNode* arg2(iarg2);
+
+	// Call the method
+	AnimationEvent* ret = newEvent<AnimationEvent>(self, arg0, arg1, arg2);
+
+	// Push return value
+	if(ANKI_UNLIKELY(ret == nullptr))
+	{
+		lua_pushstring(l, "Glue code returned nullptr");
+		return -1;
+	}
+
+	voidp = lua_newuserdata(l, sizeof(LuaUserData));
+	ud = static_cast<LuaUserData*>(voidp);
+	luaL_setmetatable(l, "AnimationEvent");
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoAnimationEvent;
+	ud->initPointed(&luaUserDataTypeInfoAnimationEvent, ret);
+
+	return 1;
+}
+
+/// Wrap method EventManager::newAnimationEvent.
+static int wrapEventManagernewAnimationEvent(lua_State* l)
+{
+	int res = pwrapEventManagernewAnimationEvent(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Wrap class EventManager.
 static inline void wrapEventManager(lua_State* l)
 {
@@ -6509,6 +6660,7 @@ static inline void wrapEventManager(lua_State* l)
 	LuaBinder::pushLuaCFuncMethod(l, "newLightEvent", wrapEventManagernewLightEvent);
 	LuaBinder::pushLuaCFuncMethod(l, "newScriptEvent", wrapEventManagernewScriptEvent);
 	LuaBinder::pushLuaCFuncMethod(l, "newJitterMoveEvent", wrapEventManagernewJitterMoveEvent);
+	LuaBinder::pushLuaCFuncMethod(l, "newAnimationEvent", wrapEventManagernewAnimationEvent);
 	lua_settop(l, 0);
 }
 
@@ -6640,6 +6792,7 @@ void wrapModuleScene(lua_State* l)
 	wrapLightEvent(l);
 	wrapScriptEvent(l);
 	wrapJitterMoveEvent(l);
+	wrapAnimationEvent(l);
 	wrapEventManager(l);
 	LuaBinder::pushLuaCFunc(l, "getSceneGraph", wrapgetSceneGraph);
 	LuaBinder::pushLuaCFunc(l, "getEventManager", wrapgetEventManager);

+ 18 - 0
AnKi/Script/Scene.xml

@@ -695,6 +695,12 @@ using WeakArrayBodyComponentPtr = WeakArray<BodyComponent*>;
 						<arg>SceneNode*</arg>
 					</args>
 				</method>
+				<method name="tryFindSceneNode">
+					<args>
+						<arg>CString</arg>
+					</args>
+					<return>SceneNode*</return>
+				</method>
 			</methods>
 		</class>
 
@@ -734,6 +740,8 @@ using WeakArrayBodyComponentPtr = WeakArray<BodyComponent*>;
 			</methods>
 		</class>
 
+		<class name="AnimationEvent"/>
+
 		<class name="EventManager">
 			<methods>
 				<method name="newLightEvent">
@@ -765,6 +773,16 @@ using WeakArrayBodyComponentPtr = WeakArray<BodyComponent*>;
 					</args>
 					<return>JitterMoveEvent*</return>
 				</method>
+
+				<method name="newAnimationEvent">
+					<overrideCall><![CDATA[AnimationEvent* ret = newEvent<AnimationEvent>(self, arg0, arg1, arg2);]]></overrideCall>
+					<args>
+						<arg>CString</arg>
+						<arg>CString</arg>
+						<arg>SceneNode*</arg>
+					</args>
+					<return>AnimationEvent*</return>
+				</method>
 			</methods>
 		</class>
 	</classes>

+ 4 - 4
AnKi/Shaders/GBufferCommon.glsl

@@ -26,8 +26,8 @@ ANKI_BINDLESS_SET(MATERIAL_SET_BINDLESS)
 layout(location = VERTEX_ATTRIBUTE_ID_POSITION) in Vec3 in_position;
 
 #	if ANKI_TECHNIQUE == RENDERING_TECHNIQUE_GBUFFER
-layout(location = VERTEX_ATTRIBUTE_ID_NORMAL) in Vec3 in_normal;
-layout(location = VERTEX_ATTRIBUTE_ID_TANGENT) in Vec4 in_tangent;
+layout(location = VERTEX_ATTRIBUTE_ID_NORMAL) in ANKI_RP Vec3 in_normal;
+layout(location = VERTEX_ATTRIBUTE_ID_TANGENT) in ANKI_RP Vec4 in_tangent;
 #	endif
 
 #	if ANKI_TECHNIQUE == RENDERING_TECHNIQUE_GBUFFER || ALPHA_TEST
@@ -53,7 +53,7 @@ layout(location = 0) out Vec2 out_uv;
 #	if ANKI_TECHNIQUE == RENDERING_TECHNIQUE_GBUFFER
 layout(location = 1) out ANKI_RP Vec3 out_normal;
 layout(location = 2) out ANKI_RP Vec3 out_tangent;
-layout(location = 3) out Vec3 out_bitangent;
+layout(location = 3) out ANKI_RP Vec3 out_bitangent;
 
 #		if REALLY_USING_PARALLAX
 layout(location = 4) out F32 out_distFromTheCamera;
@@ -81,7 +81,7 @@ layout(location = 0) in Vec2 in_uv;
 #	if ANKI_TECHNIQUE == RENDERING_TECHNIQUE_GBUFFER
 layout(location = 1) in ANKI_RP Vec3 in_normal;
 layout(location = 2) in ANKI_RP Vec3 in_tangent;
-layout(location = 3) in Vec3 in_bitangent;
+layout(location = 3) in ANKI_RP Vec3 in_bitangent;
 
 #		if REALLY_USING_PARALLAX
 layout(location = 4) in F32 in_distFromTheCamera;