The exact commands to make this commit were: git reset --hard origin/master find -type f | # list all regular files grep -E '\.(h|cpp|fsh|vsh|mm)|LICENSE$' | # filter for text files xargs -n 1 -P $(nproc) sed -i 's:\s*$::' # for each file, trim trailing whitespace including the CR git commit -a
		
			
				
	
	
		
			1531 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1531 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright (C) 2002-2012 Nikolaus Gebhardt
 | |
| // This file is part of the "Irrlicht Engine".
 | |
| // For conditions of distribution and use, see copyright notice in irrlicht.h
 | |
| 
 | |
| #include "CSkinnedMesh.h"
 | |
| #include "CBoneSceneNode.h"
 | |
| #include "IAnimatedMeshSceneNode.h"
 | |
| #include "os.h"
 | |
| 
 | |
| namespace
 | |
| {
 | |
| 	// Frames must always be increasing, so we remove objects where this isn't the case
 | |
| 	// return number of kicked keys
 | |
| 	template <class T> // T = objects containing a "frame" variable
 | |
| 	irr::u32 dropBadKeys(irr::core::array<T>& array)
 | |
| 	{
 | |
| 		if (array.size()<2)
 | |
| 			return 0;
 | |
| 
 | |
| 		irr::u32 n=1;	// new index
 | |
| 		for(irr::u32 j=1;j<array.size();++j)
 | |
| 		{
 | |
| 			if (array[j].frame < array[n-1].frame)
 | |
| 				continue; //bad frame, unneeded and may cause problems
 | |
| 			if ( n != j )
 | |
| 				array[n] = array[j];
 | |
| 			++n;
 | |
| 		}
 | |
| 		irr::u32 d = array.size()-n; // remove already copied keys
 | |
| 		if ( d > 0 )
 | |
| 		{
 | |
| 			array.erase(n, d);
 | |
| 		}
 | |
| 		return d;
 | |
| 	}
 | |
| 
 | |
| 	// drop identical middle keys - we only need the first and last
 | |
| 	// return number of kicked keys
 | |
| 	template <class T, typename Cmp> // Cmp = comparison for keys of type T
 | |
| 	irr::u32 dropMiddleKeys(irr::core::array<T>& array, Cmp & cmp)
 | |
| 	{
 | |
| 		if ( array.size() < 3 )
 | |
| 			return 0;
 | |
| 
 | |
| 		irr::u32 s = 0; 	// old index for current key
 | |
| 		irr::u32 n = 1;	// new index for next key
 | |
| 		for(irr::u32 j=1;j<array.size();++j)
 | |
| 		{
 | |
| 			if ( cmp(array[j], array[s]) )
 | |
| 				continue;	// same key, handle later
 | |
| 
 | |
| 			if ( j > s+1 ) // had there been identical keys?
 | |
| 				array[n++] = array[j-1]; // keep the last
 | |
| 			array[n++] = array[j]; // keep the new one
 | |
| 			s = j;
 | |
| 		}
 | |
| 		if ( array.size() > s+1 ) // identical keys at the array end?
 | |
| 			array[n++] = array[array.size()-1]; // keep the last
 | |
| 
 | |
| 		irr::u32 d = array.size()-n; // remove already copied keys
 | |
| 		if ( d > 0 )
 | |
| 		{
 | |
| 			array.erase(n, d);
 | |
| 		}
 | |
| 		return d;
 | |
| 	}
 | |
| 
 | |
| 	bool identicalPos(const irr::scene::ISkinnedMesh::SPositionKey& a, const irr::scene::ISkinnedMesh::SPositionKey& b)
 | |
| 	{
 | |
| 		return a.position == b.position;
 | |
| 	}
 | |
| 
 | |
| 	bool identicalScale(const irr::scene::ISkinnedMesh::SScaleKey& a, const irr::scene::ISkinnedMesh::SScaleKey& b)
 | |
| 	{
 | |
| 		return a.scale == b.scale;
 | |
| 	}
 | |
| 
 | |
| 	bool identicalRotation(const irr::scene::ISkinnedMesh::SRotationKey& a, const irr::scene::ISkinnedMesh::SRotationKey& b)
 | |
| 	{
 | |
| 		return a.rotation == b.rotation;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| namespace irr
 | |
| {
 | |
| namespace scene
 | |
| {
 | |
| 
 | |
| 
 | |
| //! constructor
 | |
| CSkinnedMesh::CSkinnedMesh()
 | |
| : SkinningBuffers(0), EndFrame(0.f), FramesPerSecond(25.f),
 | |
| 	LastAnimatedFrame(-1), SkinnedLastFrame(false),
 | |
| 	InterpolationMode(EIM_LINEAR),
 | |
| 	HasAnimation(false), PreparedForSkinning(false),
 | |
| 	AnimateNormals(true), HardwareSkinning(false)
 | |
| {
 | |
| 	#ifdef _DEBUG
 | |
| 	setDebugName("CSkinnedMesh");
 | |
| 	#endif
 | |
| 
 | |
| 	SkinningBuffers=&LocalBuffers;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! destructor
 | |
| CSkinnedMesh::~CSkinnedMesh()
 | |
| {
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 		delete AllJoints[i];
 | |
| 
 | |
| 	for (u32 j=0; j<LocalBuffers.size(); ++j)
 | |
| 	{
 | |
| 		if (LocalBuffers[j])
 | |
| 			LocalBuffers[j]->drop();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| //! returns the amount of frames in milliseconds.
 | |
| //! If the amount is 1, it is a static (=non animated) mesh.
 | |
| u32 CSkinnedMesh::getFrameCount() const
 | |
| {
 | |
| 	return core::floor32(EndFrame+1.f);
 | |
| }
 | |
| 
 | |
| 
 | |
| //! Gets the default animation speed of the animated mesh.
 | |
| /** \return Amount of frames per second. If the amount is 0, it is a static, non animated mesh. */
 | |
| f32 CSkinnedMesh::getAnimationSpeed() const
 | |
| {
 | |
| 	return FramesPerSecond;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! Gets the frame count of the animated mesh.
 | |
| /** \param fps Frames per second to play the animation with. If the amount is 0, it is not animated.
 | |
| The actual speed is set in the scene node the mesh is instantiated in.*/
 | |
| void CSkinnedMesh::setAnimationSpeed(f32 fps)
 | |
| {
 | |
| 	FramesPerSecond=fps;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! returns the animated mesh based on a detail level. 0 is the lowest, 255 the highest detail. Note, that some Meshes will ignore the detail level.
 | |
| IMesh* CSkinnedMesh::getMesh(s32 frame, s32 detailLevel, s32 startFrameLoop, s32 endFrameLoop)
 | |
| {
 | |
| 	//animate(frame,startFrameLoop, endFrameLoop);
 | |
| 	if (frame==-1)
 | |
| 		return this;
 | |
| 
 | |
| 	animateMesh((f32)frame, 1.0f);
 | |
| 	skinMesh();
 | |
| 	return this;
 | |
| }
 | |
| 
 | |
| 
 | |
| //--------------------------------------------------------------------------
 | |
| //			Keyframe Animation
 | |
| //--------------------------------------------------------------------------
 | |
| 
 | |
| 
 | |
| //! Animates this mesh's joints based on frame input
 | |
| //! blend: {0-old position, 1-New position}
 | |
| void CSkinnedMesh::animateMesh(f32 frame, f32 blend)
 | |
| {
 | |
| 	if (!HasAnimation || LastAnimatedFrame==frame)
 | |
| 		return;
 | |
| 
 | |
| 	LastAnimatedFrame=frame;
 | |
| 	SkinnedLastFrame=false;
 | |
| 
 | |
| 	if (blend<=0.f)
 | |
| 		return; //No need to animate
 | |
| 
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		//The joints can be animated here with no input from their
 | |
| 		//parents, but for setAnimationMode extra checks are needed
 | |
| 		//to their parents
 | |
| 		SJoint *joint = AllJoints[i];
 | |
| 
 | |
| 		const core::vector3df oldPosition = joint->Animatedposition;
 | |
| 		const core::vector3df oldScale = joint->Animatedscale;
 | |
| 		const core::quaternion oldRotation = joint->Animatedrotation;
 | |
| 
 | |
| 		core::vector3df position = oldPosition;
 | |
| 		core::vector3df scale = oldScale;
 | |
| 		core::quaternion rotation = oldRotation;
 | |
| 
 | |
| 		getFrameData(frame, joint,
 | |
| 				position, joint->positionHint,
 | |
| 				scale, joint->scaleHint,
 | |
| 				rotation, joint->rotationHint);
 | |
| 
 | |
| 		if (blend==1.0f)
 | |
| 		{
 | |
| 			//No blending needed
 | |
| 			joint->Animatedposition = position;
 | |
| 			joint->Animatedscale = scale;
 | |
| 			joint->Animatedrotation = rotation;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			//Blend animation
 | |
| 			joint->Animatedposition = core::lerp(oldPosition, position, blend);
 | |
| 			joint->Animatedscale = core::lerp(oldScale, scale, blend);
 | |
| 			joint->Animatedrotation.slerp(oldRotation, rotation, blend);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	//Note:
 | |
| 	//LocalAnimatedMatrix needs to be built at some point, but this function may be called lots of times for
 | |
| 	//one render (to play two animations at the same time) LocalAnimatedMatrix only needs to be built once.
 | |
| 	//a call to buildAllLocalAnimatedMatrices is needed before skinning the mesh, and before the user gets the joints to move
 | |
| 
 | |
| 	//----------------
 | |
| 	// Temp!
 | |
| 	buildAllLocalAnimatedMatrices();
 | |
| 	//-----------------
 | |
| 
 | |
| 	updateBoundingBox();
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::buildAllLocalAnimatedMatrices()
 | |
| {
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		SJoint *joint = AllJoints[i];
 | |
| 
 | |
| 		//Could be faster:
 | |
| 
 | |
| 		if (joint->UseAnimationFrom &&
 | |
| 			(joint->UseAnimationFrom->PositionKeys.size() ||
 | |
| 			 joint->UseAnimationFrom->ScaleKeys.size() ||
 | |
| 			 joint->UseAnimationFrom->RotationKeys.size() ))
 | |
| 		{
 | |
| 			joint->GlobalSkinningSpace=false;
 | |
| 
 | |
| 			// IRR_TEST_BROKEN_QUATERNION_USE: TODO - switched to getMatrix_transposed instead of getMatrix for downward compatibility.
 | |
| 			//								   Not tested so far if this was correct or wrong before quaternion fix!
 | |
| 			joint->Animatedrotation.getMatrix_transposed(joint->LocalAnimatedMatrix);
 | |
| 
 | |
| 			// --- joint->LocalAnimatedMatrix *= joint->Animatedrotation.getMatrix() ---
 | |
| 			f32 *m1 = joint->LocalAnimatedMatrix.pointer();
 | |
| 			core::vector3df &Pos = joint->Animatedposition;
 | |
| 			m1[0] += Pos.X*m1[3];
 | |
| 			m1[1] += Pos.Y*m1[3];
 | |
| 			m1[2] += Pos.Z*m1[3];
 | |
| 			m1[4] += Pos.X*m1[7];
 | |
| 			m1[5] += Pos.Y*m1[7];
 | |
| 			m1[6] += Pos.Z*m1[7];
 | |
| 			m1[8] += Pos.X*m1[11];
 | |
| 			m1[9] += Pos.Y*m1[11];
 | |
| 			m1[10] += Pos.Z*m1[11];
 | |
| 			m1[12] += Pos.X*m1[15];
 | |
| 			m1[13] += Pos.Y*m1[15];
 | |
| 			m1[14] += Pos.Z*m1[15];
 | |
| 			// -----------------------------------
 | |
| 
 | |
| 			if (joint->ScaleKeys.size())
 | |
| 			{
 | |
| 				/*
 | |
| 				core::matrix4 scaleMatrix;
 | |
| 				scaleMatrix.setScale(joint->Animatedscale);
 | |
| 				joint->LocalAnimatedMatrix *= scaleMatrix;
 | |
| 				*/
 | |
| 
 | |
| 				// -------- joint->LocalAnimatedMatrix *= scaleMatrix -----------------
 | |
| 				core::matrix4& mat = joint->LocalAnimatedMatrix;
 | |
| 				mat[0] *= joint->Animatedscale.X;
 | |
| 				mat[1] *= joint->Animatedscale.X;
 | |
| 				mat[2] *= joint->Animatedscale.X;
 | |
| 				mat[3] *= joint->Animatedscale.X;
 | |
| 				mat[4] *= joint->Animatedscale.Y;
 | |
| 				mat[5] *= joint->Animatedscale.Y;
 | |
| 				mat[6] *= joint->Animatedscale.Y;
 | |
| 				mat[7] *= joint->Animatedscale.Y;
 | |
| 				mat[8] *= joint->Animatedscale.Z;
 | |
| 				mat[9] *= joint->Animatedscale.Z;
 | |
| 				mat[10] *= joint->Animatedscale.Z;
 | |
| 				mat[11] *= joint->Animatedscale.Z;
 | |
| 				// -----------------------------------
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			joint->LocalAnimatedMatrix=joint->LocalMatrix;
 | |
| 		}
 | |
| 	}
 | |
| 	SkinnedLastFrame=false;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::buildAllGlobalAnimatedMatrices(SJoint *joint, SJoint *parentJoint)
 | |
| {
 | |
| 	if (!joint)
 | |
| 	{
 | |
| 		for (u32 i=0; i<RootJoints.size(); ++i)
 | |
| 			buildAllGlobalAnimatedMatrices(RootJoints[i], 0);
 | |
| 		return;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		// Find global matrix...
 | |
| 		if (!parentJoint || joint->GlobalSkinningSpace)
 | |
| 			joint->GlobalAnimatedMatrix = joint->LocalAnimatedMatrix;
 | |
| 		else
 | |
| 			joint->GlobalAnimatedMatrix = parentJoint->GlobalAnimatedMatrix * joint->LocalAnimatedMatrix;
 | |
| 	}
 | |
| 
 | |
| 	for (u32 j=0; j<joint->Children.size(); ++j)
 | |
| 		buildAllGlobalAnimatedMatrices(joint->Children[j], joint);
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::getFrameData(f32 frame, SJoint *joint,
 | |
| 				core::vector3df &position, s32 &positionHint,
 | |
| 				core::vector3df &scale, s32 &scaleHint,
 | |
| 				core::quaternion &rotation, s32 &rotationHint)
 | |
| {
 | |
| 	s32 foundPositionIndex = -1;
 | |
| 	s32 foundScaleIndex = -1;
 | |
| 	s32 foundRotationIndex = -1;
 | |
| 
 | |
| 	if (joint->UseAnimationFrom)
 | |
| 	{
 | |
| 		const core::array<SPositionKey> &PositionKeys=joint->UseAnimationFrom->PositionKeys;
 | |
| 		const core::array<SScaleKey> &ScaleKeys=joint->UseAnimationFrom->ScaleKeys;
 | |
| 		const core::array<SRotationKey> &RotationKeys=joint->UseAnimationFrom->RotationKeys;
 | |
| 
 | |
| 		if (PositionKeys.size())
 | |
| 		{
 | |
| 			foundPositionIndex = -1;
 | |
| 
 | |
| 			//Test the Hints...
 | |
| 			if (positionHint>=0 && (u32)positionHint < PositionKeys.size())
 | |
| 			{
 | |
| 				//check this hint
 | |
| 				if (positionHint>0 && PositionKeys[positionHint].frame>=frame && PositionKeys[positionHint-1].frame<frame )
 | |
| 					foundPositionIndex=positionHint;
 | |
| 				else if (positionHint+1 < (s32)PositionKeys.size())
 | |
| 				{
 | |
| 					//check the next index
 | |
| 					if ( PositionKeys[positionHint+1].frame>=frame &&
 | |
| 							PositionKeys[positionHint+0].frame<frame)
 | |
| 					{
 | |
| 						positionHint++;
 | |
| 						foundPositionIndex=positionHint;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			//The hint test failed, do a full scan...
 | |
| 			if (foundPositionIndex==-1)
 | |
| 			{
 | |
| 				for (u32 i=0; i<PositionKeys.size(); ++i)
 | |
| 				{
 | |
| 					if (PositionKeys[i].frame >= frame) //Keys should to be sorted by frame
 | |
| 					{
 | |
| 						foundPositionIndex=i;
 | |
| 						positionHint=i;
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			//Do interpolation...
 | |
| 			if (foundPositionIndex!=-1)
 | |
| 			{
 | |
| 				if (InterpolationMode==EIM_CONSTANT || foundPositionIndex==0)
 | |
| 				{
 | |
| 					position = PositionKeys[foundPositionIndex].position;
 | |
| 				}
 | |
| 				else if (InterpolationMode==EIM_LINEAR)
 | |
| 				{
 | |
| 					const SPositionKey& KeyA = PositionKeys[foundPositionIndex];
 | |
| 					const SPositionKey& KeyB = PositionKeys[foundPositionIndex-1];
 | |
| 
 | |
| 					const f32 fd1 = frame - KeyA.frame;
 | |
| 					const f32 fd2 = KeyB.frame - frame;
 | |
| 					position = ((KeyB.position-KeyA.position)/(fd1+fd2))*fd1 + KeyA.position;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		//------------------------------------------------------------
 | |
| 
 | |
| 		if (ScaleKeys.size())
 | |
| 		{
 | |
| 			foundScaleIndex = -1;
 | |
| 
 | |
| 			//Test the Hints...
 | |
| 			if (scaleHint>=0 && (u32)scaleHint < ScaleKeys.size())
 | |
| 			{
 | |
| 				//check this hint
 | |
| 				if (scaleHint>0 && ScaleKeys[scaleHint].frame>=frame && ScaleKeys[scaleHint-1].frame<frame )
 | |
| 					foundScaleIndex=scaleHint;
 | |
| 				else if (scaleHint+1 < (s32)ScaleKeys.size())
 | |
| 				{
 | |
| 					//check the next index
 | |
| 					if ( ScaleKeys[scaleHint+1].frame>=frame &&
 | |
| 							ScaleKeys[scaleHint+0].frame<frame)
 | |
| 					{
 | |
| 						scaleHint++;
 | |
| 						foundScaleIndex=scaleHint;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 
 | |
| 			//The hint test failed, do a full scan...
 | |
| 			if (foundScaleIndex==-1)
 | |
| 			{
 | |
| 				for (u32 i=0; i<ScaleKeys.size(); ++i)
 | |
| 				{
 | |
| 					if (ScaleKeys[i].frame >= frame) //Keys should to be sorted by frame
 | |
| 					{
 | |
| 						foundScaleIndex=i;
 | |
| 						scaleHint=i;
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			//Do interpolation...
 | |
| 			if (foundScaleIndex!=-1)
 | |
| 			{
 | |
| 				if (InterpolationMode==EIM_CONSTANT || foundScaleIndex==0)
 | |
| 				{
 | |
| 					scale = ScaleKeys[foundScaleIndex].scale;
 | |
| 				}
 | |
| 				else if (InterpolationMode==EIM_LINEAR)
 | |
| 				{
 | |
| 					const SScaleKey& KeyA = ScaleKeys[foundScaleIndex];
 | |
| 					const SScaleKey& KeyB = ScaleKeys[foundScaleIndex-1];
 | |
| 
 | |
| 					const f32 fd1 = frame - KeyA.frame;
 | |
| 					const f32 fd2 = KeyB.frame - frame;
 | |
| 					scale = ((KeyB.scale-KeyA.scale)/(fd1+fd2))*fd1 + KeyA.scale;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		//-------------------------------------------------------------
 | |
| 
 | |
| 		if (RotationKeys.size())
 | |
| 		{
 | |
| 			foundRotationIndex = -1;
 | |
| 
 | |
| 			//Test the Hints...
 | |
| 			if (rotationHint>=0 && (u32)rotationHint < RotationKeys.size())
 | |
| 			{
 | |
| 				//check this hint
 | |
| 				if (rotationHint>0 && RotationKeys[rotationHint].frame>=frame && RotationKeys[rotationHint-1].frame<frame )
 | |
| 					foundRotationIndex=rotationHint;
 | |
| 				else if (rotationHint+1 < (s32)RotationKeys.size())
 | |
| 				{
 | |
| 					//check the next index
 | |
| 					if ( RotationKeys[rotationHint+1].frame>=frame &&
 | |
| 							RotationKeys[rotationHint+0].frame<frame)
 | |
| 					{
 | |
| 						rotationHint++;
 | |
| 						foundRotationIndex=rotationHint;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 
 | |
| 			//The hint test failed, do a full scan...
 | |
| 			if (foundRotationIndex==-1)
 | |
| 			{
 | |
| 				for (u32 i=0; i<RotationKeys.size(); ++i)
 | |
| 				{
 | |
| 					if (RotationKeys[i].frame >= frame) //Keys should be sorted by frame
 | |
| 					{
 | |
| 						foundRotationIndex=i;
 | |
| 						rotationHint=i;
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			//Do interpolation...
 | |
| 			if (foundRotationIndex!=-1)
 | |
| 			{
 | |
| 				if (InterpolationMode==EIM_CONSTANT || foundRotationIndex==0)
 | |
| 				{
 | |
| 					rotation = RotationKeys[foundRotationIndex].rotation;
 | |
| 				}
 | |
| 				else if (InterpolationMode==EIM_LINEAR)
 | |
| 				{
 | |
| 					const SRotationKey& KeyA = RotationKeys[foundRotationIndex];
 | |
| 					const SRotationKey& KeyB = RotationKeys[foundRotationIndex-1];
 | |
| 
 | |
| 					const f32 fd1 = frame - KeyA.frame;
 | |
| 					const f32 fd2 = KeyB.frame - frame;
 | |
| 					const f32 t = fd1/(fd1+fd2);
 | |
| 
 | |
| 					/*
 | |
| 					f32 t = 0;
 | |
| 					if (KeyA.frame!=KeyB.frame)
 | |
| 						t = (frame-KeyA.frame) / (KeyB.frame - KeyA.frame);
 | |
| 					*/
 | |
| 
 | |
| 					rotation.slerp(KeyA.rotation, KeyB.rotation, t);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| //--------------------------------------------------------------------------
 | |
| //				Software Skinning
 | |
| //--------------------------------------------------------------------------
 | |
| 
 | |
| //! Preforms a software skin on this mesh based of joint positions
 | |
| void CSkinnedMesh::skinMesh()
 | |
| {
 | |
| 	if (!HasAnimation || SkinnedLastFrame)
 | |
| 		return;
 | |
| 
 | |
| 	//----------------
 | |
| 	// This is marked as "Temp!".  A shiny dubloon to whomever can tell me why.
 | |
| 	buildAllGlobalAnimatedMatrices();
 | |
| 	//-----------------
 | |
| 
 | |
| 	SkinnedLastFrame=true;
 | |
| 	if (!HardwareSkinning)
 | |
| 	{
 | |
| 		//Software skin....
 | |
| 		u32 i;
 | |
| 
 | |
| 		//rigid animation
 | |
| 		for (i=0; i<AllJoints.size(); ++i)
 | |
| 		{
 | |
| 			for (u32 j=0; j<AllJoints[i]->AttachedMeshes.size(); ++j)
 | |
| 			{
 | |
| 				SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ];
 | |
| 				Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		//clear skinning helper array
 | |
| 		for (i=0; i<Vertices_Moved.size(); ++i)
 | |
| 			for (u32 j=0; j<Vertices_Moved[i].size(); ++j)
 | |
| 				Vertices_Moved[i][j]=false;
 | |
| 
 | |
| 		//skin starting with the root joints
 | |
| 		for (i=0; i<RootJoints.size(); ++i)
 | |
| 			skinJoint(RootJoints[i], 0);
 | |
| 
 | |
| 		for (i=0; i<SkinningBuffers->size(); ++i)
 | |
| 			(*SkinningBuffers)[i]->setDirty(EBT_VERTEX);
 | |
| 	}
 | |
| 	updateBoundingBox();
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::skinJoint(SJoint *joint, SJoint *parentJoint)
 | |
| {
 | |
| 	if (joint->Weights.size())
 | |
| 	{
 | |
| 		//Find this joints pull on vertices...
 | |
| 		core::matrix4 jointVertexPull(core::matrix4::EM4CONST_NOTHING);
 | |
| 		jointVertexPull.setbyproduct(joint->GlobalAnimatedMatrix, joint->GlobalInversedMatrix);
 | |
| 
 | |
| 		core::vector3df thisVertexMove, thisNormalMove;
 | |
| 
 | |
| 		core::array<scene::SSkinMeshBuffer*> &buffersUsed=*SkinningBuffers;
 | |
| 
 | |
| 		//Skin Vertices Positions and Normals...
 | |
| 		for (u32 i=0; i<joint->Weights.size(); ++i)
 | |
| 		{
 | |
| 			SWeight& weight = joint->Weights[i];
 | |
| 
 | |
| 			// Pull this vertex...
 | |
| 			jointVertexPull.transformVect(thisVertexMove, weight.StaticPos);
 | |
| 
 | |
| 			if (AnimateNormals)
 | |
| 				jointVertexPull.rotateVect(thisNormalMove, weight.StaticNormal);
 | |
| 
 | |
| 			if (! (*(weight.Moved)) )
 | |
| 			{
 | |
| 				*(weight.Moved) = true;
 | |
| 
 | |
| 				buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos = thisVertexMove * weight.strength;
 | |
| 
 | |
| 				if (AnimateNormals)
 | |
| 					buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal = thisNormalMove * weight.strength;
 | |
| 
 | |
| 				//*(weight._Pos) = thisVertexMove * weight.strength;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Pos += thisVertexMove * weight.strength;
 | |
| 
 | |
| 				if (AnimateNormals)
 | |
| 					buffersUsed[weight.buffer_id]->getVertex(weight.vertex_id)->Normal += thisNormalMove * weight.strength;
 | |
| 
 | |
| 				//*(weight._Pos) += thisVertexMove * weight.strength;
 | |
| 			}
 | |
| 
 | |
| 			buffersUsed[weight.buffer_id]->boundingBoxNeedsRecalculated();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	//Skin all children
 | |
| 	for (u32 j=0; j<joint->Children.size(); ++j)
 | |
| 		skinJoint(joint->Children[j], joint);
 | |
| }
 | |
| 
 | |
| 
 | |
| E_ANIMATED_MESH_TYPE CSkinnedMesh::getMeshType() const
 | |
| {
 | |
| 	return EAMT_SKINNED;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! Gets joint count.
 | |
| u32 CSkinnedMesh::getJointCount() const
 | |
| {
 | |
| 	return AllJoints.size();
 | |
| }
 | |
| 
 | |
| 
 | |
| //! Gets the name of a joint.
 | |
| const c8* CSkinnedMesh::getJointName(u32 number) const
 | |
| {
 | |
| 	if (number >= AllJoints.size())
 | |
| 		return 0;
 | |
| 	return AllJoints[number]->Name.c_str();
 | |
| }
 | |
| 
 | |
| 
 | |
| //! Gets a joint number from its name
 | |
| s32 CSkinnedMesh::getJointNumber(const c8* name) const
 | |
| {
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		if (AllJoints[i]->Name == name)
 | |
| 			return i;
 | |
| 	}
 | |
| 
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! returns amount of mesh buffers.
 | |
| u32 CSkinnedMesh::getMeshBufferCount() const
 | |
| {
 | |
| 	return LocalBuffers.size();
 | |
| }
 | |
| 
 | |
| 
 | |
| //! returns pointer to a mesh buffer
 | |
| IMeshBuffer* CSkinnedMesh::getMeshBuffer(u32 nr) const
 | |
| {
 | |
| 	if (nr < LocalBuffers.size())
 | |
| 		return LocalBuffers[nr];
 | |
| 	else
 | |
| 		return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! Returns pointer to a mesh buffer which fits a material
 | |
| IMeshBuffer* CSkinnedMesh::getMeshBuffer(const video::SMaterial &material) const
 | |
| {
 | |
| 	for (u32 i=0; i<LocalBuffers.size(); ++i)
 | |
| 	{
 | |
| 		if (LocalBuffers[i]->getMaterial() == material)
 | |
| 			return LocalBuffers[i];
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! returns an axis aligned bounding box
 | |
| const core::aabbox3d<f32>& CSkinnedMesh::getBoundingBox() const
 | |
| {
 | |
| 	return BoundingBox;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! set user axis aligned bounding box
 | |
| void CSkinnedMesh::setBoundingBox( const core::aabbox3df& box)
 | |
| {
 | |
| 	BoundingBox = box;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! set the hardware mapping hint, for driver
 | |
| void CSkinnedMesh::setHardwareMappingHint(E_HARDWARE_MAPPING newMappingHint,
 | |
| 		E_BUFFER_TYPE buffer)
 | |
| {
 | |
| 	for (u32 i=0; i<LocalBuffers.size(); ++i)
 | |
| 		LocalBuffers[i]->setHardwareMappingHint(newMappingHint, buffer);
 | |
| }
 | |
| 
 | |
| 
 | |
| //! flags the meshbuffer as changed, reloads hardware buffers
 | |
| void CSkinnedMesh::setDirty(E_BUFFER_TYPE buffer)
 | |
| {
 | |
| 	for (u32 i=0; i<LocalBuffers.size(); ++i)
 | |
| 		LocalBuffers[i]->setDirty(buffer);
 | |
| }
 | |
| 
 | |
| 
 | |
| //! uses animation from another mesh
 | |
| bool CSkinnedMesh::useAnimationFrom(const ISkinnedMesh *mesh)
 | |
| {
 | |
| 	bool unmatched=false;
 | |
| 
 | |
| 	for(u32 i=0;i<AllJoints.size();++i)
 | |
| 	{
 | |
| 		SJoint *joint=AllJoints[i];
 | |
| 		joint->UseAnimationFrom=0;
 | |
| 
 | |
| 		if (joint->Name=="")
 | |
| 			unmatched=true;
 | |
| 		else
 | |
| 		{
 | |
| 			for(u32 j=0;j<mesh->getAllJoints().size();++j)
 | |
| 			{
 | |
| 				SJoint *otherJoint=mesh->getAllJoints()[j];
 | |
| 				if (joint->Name==otherJoint->Name)
 | |
| 				{
 | |
| 					joint->UseAnimationFrom=otherJoint;
 | |
| 				}
 | |
| 			}
 | |
| 			if (!joint->UseAnimationFrom)
 | |
| 				unmatched=true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	checkForAnimation();
 | |
| 
 | |
| 	return !unmatched;
 | |
| }
 | |
| 
 | |
| 
 | |
| //!Update Normals when Animating
 | |
| //!False= Don't animate them, faster
 | |
| //!True= Update normals (default)
 | |
| void CSkinnedMesh::updateNormalsWhenAnimating(bool on)
 | |
| {
 | |
| 	AnimateNormals = on;
 | |
| }
 | |
| 
 | |
| 
 | |
| //!Sets Interpolation Mode
 | |
| void CSkinnedMesh::setInterpolationMode(E_INTERPOLATION_MODE mode)
 | |
| {
 | |
| 	InterpolationMode = mode;
 | |
| }
 | |
| 
 | |
| 
 | |
| core::array<scene::SSkinMeshBuffer*> &CSkinnedMesh::getMeshBuffers()
 | |
| {
 | |
| 	return LocalBuffers;
 | |
| }
 | |
| 
 | |
| 
 | |
| core::array<CSkinnedMesh::SJoint*> &CSkinnedMesh::getAllJoints()
 | |
| {
 | |
| 	return AllJoints;
 | |
| }
 | |
| 
 | |
| 
 | |
| const core::array<CSkinnedMesh::SJoint*> &CSkinnedMesh::getAllJoints() const
 | |
| {
 | |
| 	return AllJoints;
 | |
| }
 | |
| 
 | |
| 
 | |
| //! (This feature is not implemented in irrlicht yet)
 | |
| bool CSkinnedMesh::setHardwareSkinning(bool on)
 | |
| {
 | |
| 	if (HardwareSkinning!=on)
 | |
| 	{
 | |
| 		if (on)
 | |
| 		{
 | |
| 
 | |
| 			//set mesh to static pose...
 | |
| 			for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 			{
 | |
| 				SJoint *joint=AllJoints[i];
 | |
| 				for (u32 j=0; j<joint->Weights.size(); ++j)
 | |
| 				{
 | |
| 					const u16 buffer_id=joint->Weights[j].buffer_id;
 | |
| 					const u32 vertex_id=joint->Weights[j].vertex_id;
 | |
| 					LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos;
 | |
| 					LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal;
 | |
| 					LocalBuffers[buffer_id]->boundingBoxNeedsRecalculated();
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		HardwareSkinning=on;
 | |
| 	}
 | |
| 	return HardwareSkinning;
 | |
| }
 | |
| 
 | |
| void CSkinnedMesh::refreshJointCache()
 | |
| {
 | |
| 	//copy cache from the mesh...
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		SJoint *joint=AllJoints[i];
 | |
| 		for (u32 j=0; j<joint->Weights.size(); ++j)
 | |
| 		{
 | |
| 			const u16 buffer_id=joint->Weights[j].buffer_id;
 | |
| 			const u32 vertex_id=joint->Weights[j].vertex_id;
 | |
| 			joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
 | |
| 			joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void CSkinnedMesh::resetAnimation()
 | |
| {
 | |
| 	//copy from the cache to the mesh...
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		SJoint *joint=AllJoints[i];
 | |
| 		for (u32 j=0; j<joint->Weights.size(); ++j)
 | |
| 		{
 | |
| 			const u16 buffer_id=joint->Weights[j].buffer_id;
 | |
| 			const u32 vertex_id=joint->Weights[j].vertex_id;
 | |
| 			LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos = joint->Weights[j].StaticPos;
 | |
| 			LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal = joint->Weights[j].StaticNormal;
 | |
| 		}
 | |
| 	}
 | |
| 	SkinnedLastFrame = false;
 | |
| 	LastAnimatedFrame = -1;
 | |
| }
 | |
| 
 | |
| void CSkinnedMesh::calculateGlobalMatrices(SJoint *joint,SJoint *parentJoint)
 | |
| {
 | |
| 	if (!joint && parentJoint) // bit of protection from endless loops
 | |
| 		return;
 | |
| 
 | |
| 	//Go through the root bones
 | |
| 	if (!joint)
 | |
| 	{
 | |
| 		for (u32 i=0; i<RootJoints.size(); ++i)
 | |
| 			calculateGlobalMatrices(RootJoints[i],0);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (!parentJoint)
 | |
| 		joint->GlobalMatrix = joint->LocalMatrix;
 | |
| 	else
 | |
| 		joint->GlobalMatrix = parentJoint->GlobalMatrix * joint->LocalMatrix;
 | |
| 
 | |
| 	joint->LocalAnimatedMatrix=joint->LocalMatrix;
 | |
| 	joint->GlobalAnimatedMatrix=joint->GlobalMatrix;
 | |
| 
 | |
| 	if (joint->GlobalInversedMatrix.isIdentity())//might be pre calculated
 | |
| 	{
 | |
| 		joint->GlobalInversedMatrix = joint->GlobalMatrix;
 | |
| 		joint->GlobalInversedMatrix.makeInverse(); // slow
 | |
| 	}
 | |
| 
 | |
| 	for (u32 j=0; j<joint->Children.size(); ++j)
 | |
| 		calculateGlobalMatrices(joint->Children[j],joint);
 | |
| 	SkinnedLastFrame=false;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::checkForAnimation()
 | |
| {
 | |
| 	u32 i,j;
 | |
| 	//Check for animation...
 | |
| 	HasAnimation = false;
 | |
| 	for(i=0;i<AllJoints.size();++i)
 | |
| 	{
 | |
| 		if (AllJoints[i]->UseAnimationFrom)
 | |
| 		{
 | |
| 			if (AllJoints[i]->UseAnimationFrom->PositionKeys.size() ||
 | |
| 				AllJoints[i]->UseAnimationFrom->ScaleKeys.size() ||
 | |
| 				AllJoints[i]->UseAnimationFrom->RotationKeys.size() )
 | |
| 			{
 | |
| 				HasAnimation = true;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	//meshes with weights, are still counted as animated for ragdolls, etc
 | |
| 	if (!HasAnimation)
 | |
| 	{
 | |
| 		for(i=0;i<AllJoints.size();++i)
 | |
| 		{
 | |
| 			if (AllJoints[i]->Weights.size())
 | |
| 				HasAnimation = true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (HasAnimation)
 | |
| 	{
 | |
| 		//--- Find the length of the animation ---
 | |
| 		EndFrame=0;
 | |
| 		for(i=0;i<AllJoints.size();++i)
 | |
| 		{
 | |
| 			if (AllJoints[i]->UseAnimationFrom)
 | |
| 			{
 | |
| 				if (AllJoints[i]->UseAnimationFrom->PositionKeys.size())
 | |
| 					if (AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame > EndFrame)
 | |
| 						EndFrame=AllJoints[i]->UseAnimationFrom->PositionKeys.getLast().frame;
 | |
| 
 | |
| 				if (AllJoints[i]->UseAnimationFrom->ScaleKeys.size())
 | |
| 					if (AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame > EndFrame)
 | |
| 						EndFrame=AllJoints[i]->UseAnimationFrom->ScaleKeys.getLast().frame;
 | |
| 
 | |
| 				if (AllJoints[i]->UseAnimationFrom->RotationKeys.size())
 | |
| 					if (AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame > EndFrame)
 | |
| 						EndFrame=AllJoints[i]->UseAnimationFrom->RotationKeys.getLast().frame;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (HasAnimation && !PreparedForSkinning)
 | |
| 	{
 | |
| 		PreparedForSkinning=true;
 | |
| 
 | |
| 		//check for bugs:
 | |
| 		for(i=0; i < AllJoints.size(); ++i)
 | |
| 		{
 | |
| 			SJoint *joint = AllJoints[i];
 | |
| 			for (j=0; j<joint->Weights.size(); ++j)
 | |
| 			{
 | |
| 				const u16 buffer_id=joint->Weights[j].buffer_id;
 | |
| 				const u32 vertex_id=joint->Weights[j].vertex_id;
 | |
| 
 | |
| 				//check for invalid ids
 | |
| 				if (buffer_id>=LocalBuffers.size())
 | |
| 				{
 | |
| 					os::Printer::log("Skinned Mesh: Weight buffer id too large", ELL_WARNING);
 | |
| 					joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0;
 | |
| 				}
 | |
| 				else if (vertex_id>=LocalBuffers[buffer_id]->getVertexCount())
 | |
| 				{
 | |
| 					os::Printer::log("Skinned Mesh: Weight vertex id too large", ELL_WARNING);
 | |
| 					joint->Weights[j].buffer_id = joint->Weights[j].vertex_id =0;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		//An array used in skinning
 | |
| 
 | |
| 		for (i=0; i<Vertices_Moved.size(); ++i)
 | |
| 			for (j=0; j<Vertices_Moved[i].size(); ++j)
 | |
| 				Vertices_Moved[i][j] = false;
 | |
| 
 | |
| 		// For skinning: cache weight values for speed
 | |
| 
 | |
| 		for (i=0; i<AllJoints.size(); ++i)
 | |
| 		{
 | |
| 			SJoint *joint = AllJoints[i];
 | |
| 			for (j=0; j<joint->Weights.size(); ++j)
 | |
| 			{
 | |
| 				const u16 buffer_id=joint->Weights[j].buffer_id;
 | |
| 				const u32 vertex_id=joint->Weights[j].vertex_id;
 | |
| 
 | |
| 				joint->Weights[j].Moved = &Vertices_Moved[buffer_id] [vertex_id];
 | |
| 				joint->Weights[j].StaticPos = LocalBuffers[buffer_id]->getVertex(vertex_id)->Pos;
 | |
| 				joint->Weights[j].StaticNormal = LocalBuffers[buffer_id]->getVertex(vertex_id)->Normal;
 | |
| 
 | |
| 				//joint->Weights[j]._Pos=&Buffers[buffer_id]->getVertex(vertex_id)->Pos;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// normalize weights
 | |
| 		normalizeWeights();
 | |
| 	}
 | |
| 	SkinnedLastFrame=false;
 | |
| }
 | |
| 
 | |
| //! called by loader after populating with mesh and bone data
 | |
| void CSkinnedMesh::finalize()
 | |
| {
 | |
| 	os::Printer::log("Skinned Mesh - finalize", ELL_DEBUG);
 | |
| 	u32 i;
 | |
| 
 | |
| 	// Make sure we recalc the next frame
 | |
| 	LastAnimatedFrame=-1;
 | |
| 	SkinnedLastFrame=false;
 | |
| 
 | |
| 	//calculate bounding box
 | |
| 	for (i=0; i<LocalBuffers.size(); ++i)
 | |
| 	{
 | |
| 		LocalBuffers[i]->recalculateBoundingBox();
 | |
| 	}
 | |
| 
 | |
| 	if (AllJoints.size() || RootJoints.size())
 | |
| 	{
 | |
| 		// populate AllJoints or RootJoints, depending on which is empty
 | |
| 		if (!RootJoints.size())
 | |
| 		{
 | |
| 
 | |
| 			for(u32 CheckingIdx=0; CheckingIdx < AllJoints.size(); ++CheckingIdx)
 | |
| 			{
 | |
| 
 | |
| 				bool foundParent=false;
 | |
| 				for(i=0; i < AllJoints.size(); ++i)
 | |
| 				{
 | |
| 					for(u32 n=0; n < AllJoints[i]->Children.size(); ++n)
 | |
| 					{
 | |
| 						if (AllJoints[i]->Children[n] == AllJoints[CheckingIdx])
 | |
| 							foundParent=true;
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				if (!foundParent)
 | |
| 					RootJoints.push_back(AllJoints[CheckingIdx]);
 | |
| 			}
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			AllJoints=RootJoints;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for(i=0; i < AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		AllJoints[i]->UseAnimationFrom=AllJoints[i];
 | |
| 	}
 | |
| 
 | |
| 	//Set array sizes...
 | |
| 
 | |
| 	for (i=0; i<LocalBuffers.size(); ++i)
 | |
| 	{
 | |
| 		Vertices_Moved.push_back( core::array<char>() );
 | |
| 		Vertices_Moved[i].set_used(LocalBuffers[i]->getVertexCount());
 | |
| 	}
 | |
| 
 | |
| 	checkForAnimation();
 | |
| 
 | |
| 	if (HasAnimation)
 | |
| 	{
 | |
| 		irr::u32 redundantPosKeys = 0;
 | |
| 		irr::u32 unorderedPosKeys = 0;
 | |
| 		irr::u32 redundantScaleKeys = 0;
 | |
| 		irr::u32 unorderedScaleKeys = 0;
 | |
| 		irr::u32 redundantRotationKeys = 0;
 | |
| 		irr::u32 unorderedRotationKeys = 0;
 | |
| 
 | |
| 		//--- optimize and check keyframes ---
 | |
| 		for(i=0;i<AllJoints.size();++i)
 | |
| 		{
 | |
| 			core::array<SPositionKey> &PositionKeys =AllJoints[i]->PositionKeys;
 | |
| 			core::array<SScaleKey> &ScaleKeys = AllJoints[i]->ScaleKeys;
 | |
| 			core::array<SRotationKey> &RotationKeys = AllJoints[i]->RotationKeys;
 | |
| 
 | |
| 			// redundant = identical middle keys - we only need the first and last frame
 | |
| 			// unordered = frames which are out of order - we can't handle those
 | |
| 			redundantPosKeys += dropMiddleKeys<SPositionKey>(PositionKeys, identicalPos);
 | |
| 			unorderedPosKeys += dropBadKeys<SPositionKey>(PositionKeys);
 | |
| 			redundantScaleKeys += dropMiddleKeys<SScaleKey>(ScaleKeys, identicalScale);
 | |
| 			unorderedScaleKeys += dropBadKeys<SScaleKey>(ScaleKeys);
 | |
| 			redundantRotationKeys += dropMiddleKeys<SRotationKey>(RotationKeys, identicalRotation);
 | |
| 			unorderedRotationKeys += dropBadKeys<SRotationKey>(RotationKeys);
 | |
| 
 | |
| 			//Fill empty keyframe areas
 | |
| 			if (PositionKeys.size())
 | |
| 			{
 | |
| 				SPositionKey *Key;
 | |
| 				Key=&PositionKeys[0];//getFirst
 | |
| 				if (Key->frame!=0)
 | |
| 				{
 | |
| 					PositionKeys.push_front(*Key);
 | |
| 					Key=&PositionKeys[0];//getFirst
 | |
| 					Key->frame=0;
 | |
| 				}
 | |
| 
 | |
| 				Key=&PositionKeys.getLast();
 | |
| 				if (Key->frame!=EndFrame)
 | |
| 				{
 | |
| 					PositionKeys.push_back(*Key);
 | |
| 					Key=&PositionKeys.getLast();
 | |
| 					Key->frame=EndFrame;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if (ScaleKeys.size())
 | |
| 			{
 | |
| 				SScaleKey *Key;
 | |
| 				Key=&ScaleKeys[0];//getFirst
 | |
| 				if (Key->frame!=0)
 | |
| 				{
 | |
| 					ScaleKeys.push_front(*Key);
 | |
| 					Key=&ScaleKeys[0];//getFirst
 | |
| 					Key->frame=0;
 | |
| 				}
 | |
| 
 | |
| 				Key=&ScaleKeys.getLast();
 | |
| 				if (Key->frame!=EndFrame)
 | |
| 				{
 | |
| 					ScaleKeys.push_back(*Key);
 | |
| 					Key=&ScaleKeys.getLast();
 | |
| 					Key->frame=EndFrame;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if (RotationKeys.size())
 | |
| 			{
 | |
| 				SRotationKey *Key;
 | |
| 				Key=&RotationKeys[0];//getFirst
 | |
| 				if (Key->frame!=0)
 | |
| 				{
 | |
| 					RotationKeys.push_front(*Key);
 | |
| 					Key=&RotationKeys[0];//getFirst
 | |
| 					Key->frame=0;
 | |
| 				}
 | |
| 
 | |
| 				Key=&RotationKeys.getLast();
 | |
| 				if (Key->frame!=EndFrame)
 | |
| 				{
 | |
| 					RotationKeys.push_back(*Key);
 | |
| 					Key=&RotationKeys.getLast();
 | |
| 					Key->frame=EndFrame;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if ( redundantPosKeys > 0 )
 | |
| 		{
 | |
| 			os::Printer::log("Skinned Mesh - redundant position frames kicked", core::stringc(redundantPosKeys).c_str(), ELL_DEBUG);
 | |
| 		}
 | |
| 		if ( unorderedPosKeys > 0 )
 | |
| 		{
 | |
| 			irr::os::Printer::log("Skinned Mesh - unsorted position frames kicked", irr::core::stringc(unorderedPosKeys).c_str(), irr::ELL_DEBUG);
 | |
| 		}
 | |
| 		if ( redundantScaleKeys > 0 )
 | |
| 		{
 | |
| 			os::Printer::log("Skinned Mesh - redundant scale frames kicked", core::stringc(redundantScaleKeys).c_str(), ELL_DEBUG);
 | |
| 		}
 | |
| 		if ( unorderedScaleKeys > 0 )
 | |
| 		{
 | |
| 			irr::os::Printer::log("Skinned Mesh - unsorted scale frames kicked", irr::core::stringc(unorderedScaleKeys).c_str(), irr::ELL_DEBUG);
 | |
| 		}
 | |
| 		if ( redundantRotationKeys > 0 )
 | |
| 		{
 | |
| 			os::Printer::log("Skinned Mesh - redundant rotation frames kicked", core::stringc(redundantRotationKeys).c_str(), ELL_DEBUG);
 | |
| 		}
 | |
| 		if ( unorderedRotationKeys > 0 )
 | |
| 		{
 | |
| 			irr::os::Printer::log("Skinned Mesh - unsorted rotation frames kicked", irr::core::stringc(unorderedRotationKeys).c_str(), irr::ELL_DEBUG);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	//Needed for animation and skinning...
 | |
| 
 | |
| 	calculateGlobalMatrices(0,0);
 | |
| 
 | |
| 	//animateMesh(0, 1);
 | |
| 	//buildAllLocalAnimatedMatrices();
 | |
| 	//buildAllGlobalAnimatedMatrices();
 | |
| 
 | |
| 	//rigid animation for non animated meshes
 | |
| 	for (i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		for (u32 j=0; j<AllJoints[i]->AttachedMeshes.size(); ++j)
 | |
| 		{
 | |
| 			SSkinMeshBuffer* Buffer=(*SkinningBuffers)[ AllJoints[i]->AttachedMeshes[j] ];
 | |
| 			Buffer->Transformation=AllJoints[i]->GlobalAnimatedMatrix;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	//calculate bounding box
 | |
| 	if (LocalBuffers.empty())
 | |
| 		BoundingBox.reset(0,0,0);
 | |
| 	else
 | |
| 	{
 | |
| 		irr::core::aabbox3df bb(LocalBuffers[0]->BoundingBox);
 | |
| 		LocalBuffers[0]->Transformation.transformBoxEx(bb);
 | |
| 		BoundingBox.reset(bb);
 | |
| 
 | |
| 		for (u32 j=1; j<LocalBuffers.size(); ++j)
 | |
| 		{
 | |
| 			bb = LocalBuffers[j]->BoundingBox;
 | |
| 			LocalBuffers[j]->Transformation.transformBoxEx(bb);
 | |
| 
 | |
| 			BoundingBox.addInternalBox(bb);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::updateBoundingBox(void)
 | |
| {
 | |
| 	if(!SkinningBuffers)
 | |
| 		return;
 | |
| 
 | |
| 	core::array<SSkinMeshBuffer*> & buffer = *SkinningBuffers;
 | |
| 	BoundingBox.reset(0,0,0);
 | |
| 
 | |
| 	if (!buffer.empty())
 | |
| 	{
 | |
| 		for (u32 j=0; j<buffer.size(); ++j)
 | |
| 		{
 | |
| 			buffer[j]->recalculateBoundingBox();
 | |
| 			core::aabbox3df bb = buffer[j]->BoundingBox;
 | |
| 			buffer[j]->Transformation.transformBoxEx(bb);
 | |
| 
 | |
| 			BoundingBox.addInternalBox(bb);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| scene::SSkinMeshBuffer *CSkinnedMesh::addMeshBuffer()
 | |
| {
 | |
| 	scene::SSkinMeshBuffer *buffer=new scene::SSkinMeshBuffer();
 | |
| 	LocalBuffers.push_back(buffer);
 | |
| 	return buffer;
 | |
| }
 | |
| 
 | |
| 
 | |
| CSkinnedMesh::SJoint *CSkinnedMesh::addJoint(SJoint *parent)
 | |
| {
 | |
| 	SJoint *joint=new SJoint;
 | |
| 
 | |
| 	AllJoints.push_back(joint);
 | |
| 	if (!parent)
 | |
| 	{
 | |
| 		//Add root joints to array in finalize()
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		//Set parent (Be careful of the mesh loader also setting the parent)
 | |
| 		parent->Children.push_back(joint);
 | |
| 	}
 | |
| 
 | |
| 	return joint;
 | |
| }
 | |
| 
 | |
| 
 | |
| CSkinnedMesh::SPositionKey *CSkinnedMesh::addPositionKey(SJoint *joint)
 | |
| {
 | |
| 	if (!joint)
 | |
| 		return 0;
 | |
| 
 | |
| 	joint->PositionKeys.push_back(SPositionKey());
 | |
| 	return &joint->PositionKeys.getLast();
 | |
| }
 | |
| 
 | |
| 
 | |
| CSkinnedMesh::SScaleKey *CSkinnedMesh::addScaleKey(SJoint *joint)
 | |
| {
 | |
| 	if (!joint)
 | |
| 		return 0;
 | |
| 
 | |
| 	joint->ScaleKeys.push_back(SScaleKey());
 | |
| 	return &joint->ScaleKeys.getLast();
 | |
| }
 | |
| 
 | |
| 
 | |
| CSkinnedMesh::SRotationKey *CSkinnedMesh::addRotationKey(SJoint *joint)
 | |
| {
 | |
| 	if (!joint)
 | |
| 		return 0;
 | |
| 
 | |
| 	joint->RotationKeys.push_back(SRotationKey());
 | |
| 	return &joint->RotationKeys.getLast();
 | |
| }
 | |
| 
 | |
| 
 | |
| CSkinnedMesh::SWeight *CSkinnedMesh::addWeight(SJoint *joint)
 | |
| {
 | |
| 	if (!joint)
 | |
| 		return 0;
 | |
| 
 | |
| 	joint->Weights.push_back(SWeight());
 | |
| 	return &joint->Weights.getLast();
 | |
| }
 | |
| 
 | |
| 
 | |
| bool CSkinnedMesh::isStatic()
 | |
| {
 | |
| 	return !HasAnimation;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::normalizeWeights()
 | |
| {
 | |
| 	// note: unsure if weights ids are going to be used.
 | |
| 
 | |
| 	// Normalise the weights on bones....
 | |
| 
 | |
| 	u32 i,j;
 | |
| 	core::array< core::array<f32> > verticesTotalWeight;
 | |
| 
 | |
| 	verticesTotalWeight.reallocate(LocalBuffers.size());
 | |
| 	for (i=0; i<LocalBuffers.size(); ++i)
 | |
| 	{
 | |
| 		verticesTotalWeight.push_back(core::array<f32>());
 | |
| 		verticesTotalWeight[i].set_used(LocalBuffers[i]->getVertexCount());
 | |
| 	}
 | |
| 
 | |
| 	for (i=0; i<verticesTotalWeight.size(); ++i)
 | |
| 		for (j=0; j<verticesTotalWeight[i].size(); ++j)
 | |
| 			verticesTotalWeight[i][j] = 0;
 | |
| 
 | |
| 	for (i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		SJoint *joint=AllJoints[i];
 | |
| 		for (j=0; j<joint->Weights.size(); ++j)
 | |
| 		{
 | |
| 			if (joint->Weights[j].strength<=0)//Check for invalid weights
 | |
| 			{
 | |
| 				joint->Weights.erase(j);
 | |
| 				--j;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				verticesTotalWeight[joint->Weights[j].buffer_id] [joint->Weights[j].vertex_id] += joint->Weights[j].strength;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for (i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		SJoint *joint=AllJoints[i];
 | |
| 		for (j=0; j< joint->Weights.size(); ++j)
 | |
| 		{
 | |
| 			const f32 total = verticesTotalWeight[joint->Weights[j].buffer_id] [joint->Weights[j].vertex_id];
 | |
| 			if (total != 0 && total != 1)
 | |
| 				joint->Weights[j].strength /= total;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::recoverJointsFromMesh(core::array<IBoneSceneNode*> &jointChildSceneNodes)
 | |
| {
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		IBoneSceneNode* node=jointChildSceneNodes[i];
 | |
| 		SJoint *joint=AllJoints[i];
 | |
| 		node->setPosition(joint->LocalAnimatedMatrix.getTranslation());
 | |
| 		node->setRotation(joint->LocalAnimatedMatrix.getRotationDegrees());
 | |
| 		node->setScale(joint->LocalAnimatedMatrix.getScale());
 | |
| 
 | |
| 		node->positionHint=joint->positionHint;
 | |
| 		node->scaleHint=joint->scaleHint;
 | |
| 		node->rotationHint=joint->rotationHint;
 | |
| 
 | |
| 		node->updateAbsolutePosition();
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::transferJointsToMesh(const core::array<IBoneSceneNode*> &jointChildSceneNodes)
 | |
| {
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		const IBoneSceneNode* const node=jointChildSceneNodes[i];
 | |
| 		SJoint *joint=AllJoints[i];
 | |
| 
 | |
| 		joint->LocalAnimatedMatrix.setRotationDegrees(node->getRotation());
 | |
| 		joint->LocalAnimatedMatrix.setTranslation(node->getPosition());
 | |
| 		joint->LocalAnimatedMatrix *= core::matrix4().setScale(node->getScale());
 | |
| 
 | |
| 		joint->positionHint=node->positionHint;
 | |
| 		joint->scaleHint=node->scaleHint;
 | |
| 		joint->rotationHint=node->rotationHint;
 | |
| 
 | |
| 		joint->GlobalSkinningSpace=(node->getSkinningSpace()==EBSS_GLOBAL);
 | |
| 	}
 | |
| 	// Make sure we recalc the next frame
 | |
| 	LastAnimatedFrame=-1;
 | |
| 	SkinnedLastFrame=false;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::transferOnlyJointsHintsToMesh(const core::array<IBoneSceneNode*> &jointChildSceneNodes)
 | |
| {
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		const IBoneSceneNode* const node=jointChildSceneNodes[i];
 | |
| 		SJoint *joint=AllJoints[i];
 | |
| 
 | |
| 		joint->positionHint=node->positionHint;
 | |
| 		joint->scaleHint=node->scaleHint;
 | |
| 		joint->rotationHint=node->rotationHint;
 | |
| 	}
 | |
| 	SkinnedLastFrame=false;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::addJoints(core::array<IBoneSceneNode*> &jointChildSceneNodes,
 | |
| 		IAnimatedMeshSceneNode* node, ISceneManager* smgr)
 | |
| {
 | |
| 	//Create new joints
 | |
| 	for (u32 i=0; i<AllJoints.size(); ++i)
 | |
| 	{
 | |
| 		jointChildSceneNodes.push_back(new CBoneSceneNode(0, smgr, 0, i, AllJoints[i]->Name.c_str()));
 | |
| 	}
 | |
| 
 | |
| 	//Match up parents
 | |
| 	for (u32 i=0; i<jointChildSceneNodes.size(); ++i)
 | |
| 	{
 | |
| 		const SJoint* const joint=AllJoints[i]; //should be fine
 | |
| 
 | |
| 		s32 parentID=-1;
 | |
| 
 | |
| 		for (u32 j=0;(parentID==-1)&&(j<AllJoints.size());++j)
 | |
| 		{
 | |
| 			if (i!=j)
 | |
| 			{
 | |
| 				const SJoint* const parentTest=AllJoints[j];
 | |
| 				for (u32 n=0; n<parentTest->Children.size(); ++n)
 | |
| 				{
 | |
| 					if (parentTest->Children[n]==joint)
 | |
| 					{
 | |
| 						parentID=j;
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		IBoneSceneNode* bone=jointChildSceneNodes[i];
 | |
| 		if (parentID!=-1)
 | |
| 			bone->setParent(jointChildSceneNodes[parentID]);
 | |
| 		else
 | |
| 			bone->setParent(node);
 | |
| 
 | |
| 		bone->drop();
 | |
| 	}
 | |
| 	SkinnedLastFrame=false;
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::convertMeshToTangents()
 | |
| {
 | |
| 	// now calculate tangents
 | |
| 	for (u32 b=0; b < LocalBuffers.size(); ++b)
 | |
| 	{
 | |
| 		if (LocalBuffers[b])
 | |
| 		{
 | |
| 			LocalBuffers[b]->convertToTangents();
 | |
| 
 | |
| 			const s32 idxCnt = LocalBuffers[b]->getIndexCount();
 | |
| 
 | |
| 			u16* idx = LocalBuffers[b]->getIndices();
 | |
| 			video::S3DVertexTangents* v =
 | |
| 				(video::S3DVertexTangents*)LocalBuffers[b]->getVertices();
 | |
| 
 | |
| 			for (s32 i=0; i<idxCnt; i+=3)
 | |
| 			{
 | |
| 				calculateTangents(
 | |
| 					v[idx[i+0]].Normal,
 | |
| 					v[idx[i+0]].Tangent,
 | |
| 					v[idx[i+0]].Binormal,
 | |
| 					v[idx[i+0]].Pos,
 | |
| 					v[idx[i+1]].Pos,
 | |
| 					v[idx[i+2]].Pos,
 | |
| 					v[idx[i+0]].TCoords,
 | |
| 					v[idx[i+1]].TCoords,
 | |
| 					v[idx[i+2]].TCoords);
 | |
| 
 | |
| 				calculateTangents(
 | |
| 					v[idx[i+1]].Normal,
 | |
| 					v[idx[i+1]].Tangent,
 | |
| 					v[idx[i+1]].Binormal,
 | |
| 					v[idx[i+1]].Pos,
 | |
| 					v[idx[i+2]].Pos,
 | |
| 					v[idx[i+0]].Pos,
 | |
| 					v[idx[i+1]].TCoords,
 | |
| 					v[idx[i+2]].TCoords,
 | |
| 					v[idx[i+0]].TCoords);
 | |
| 
 | |
| 				calculateTangents(
 | |
| 					v[idx[i+2]].Normal,
 | |
| 					v[idx[i+2]].Tangent,
 | |
| 					v[idx[i+2]].Binormal,
 | |
| 					v[idx[i+2]].Pos,
 | |
| 					v[idx[i+0]].Pos,
 | |
| 					v[idx[i+1]].Pos,
 | |
| 					v[idx[i+2]].TCoords,
 | |
| 					v[idx[i+0]].TCoords,
 | |
| 					v[idx[i+1]].TCoords);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| void CSkinnedMesh::calculateTangents(
 | |
| 	core::vector3df& normal,
 | |
| 	core::vector3df& tangent,
 | |
| 	core::vector3df& binormal,
 | |
| 	const core::vector3df& vt1, const core::vector3df& vt2, const core::vector3df& vt3, // vertices
 | |
| 	const core::vector2df& tc1, const core::vector2df& tc2, const core::vector2df& tc3) // texture coords
 | |
| {
 | |
| 	core::vector3df v1 = vt1 - vt2;
 | |
| 	core::vector3df v2 = vt3 - vt1;
 | |
| 	normal = v2.crossProduct(v1);
 | |
| 	normal.normalize();
 | |
| 
 | |
| 	// binormal
 | |
| 
 | |
| 	f32 deltaX1 = tc1.X - tc2.X;
 | |
| 	f32 deltaX2 = tc3.X - tc1.X;
 | |
| 	binormal = (v1 * deltaX2) - (v2 * deltaX1);
 | |
| 	binormal.normalize();
 | |
| 
 | |
| 	// tangent
 | |
| 
 | |
| 	f32 deltaY1 = tc1.Y - tc2.Y;
 | |
| 	f32 deltaY2 = tc3.Y - tc1.Y;
 | |
| 	tangent = (v1 * deltaY2) - (v2 * deltaY1);
 | |
| 	tangent.normalize();
 | |
| 
 | |
| 	// adjust
 | |
| 
 | |
| 	core::vector3df txb = tangent.crossProduct(binormal);
 | |
| 	if (txb.dotProduct(normal) < 0.0f)
 | |
| 	{
 | |
| 		tangent *= -1.0f;
 | |
| 		binormal *= -1.0f;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| } // end namespace scene
 | |
| } // end namespace irr
 |