Skeletale Animationen

Einstiegsfragen, Mathematik, Physik, künstliche Intelligenz, Engine Design
Antworten
DarkD
Beiträge: 8
Registriert: 30.03.2005, 00:01

Skeletale Animationen

Beitrag von DarkD »

Hi,

seit 2 Tagen sitzt ich an einem Problem mit meinen Skeletale Animationen. Ich bin dabei eine Milkshapedatei einzulesen und die Joints zu rendern.
Dabei werden diese leider falsch dargestellt :-(

Bild

Wie man sieht sind die Joints falsch rotiert. Zudem ist die linke und die rechte Hand vertauscht (die rechte Hand hat 1 Joint mehr). Und die Schultern sehen aus der Nähe merkwürdig aus.
Hier einmal das Modell wie es in Milkshape aussieht:

Bild

Es scheint als ob Position und Rotation immer relativ zum Parent-Joint gespeichert werden und ich denke, dass mein Fehler in der Berechnung der Matrizen liegt.
Hier der relevante Code:

Code: Alles auswählen

//Position, Rotation sind die Daten direkt aus der Datei
//m_Relative, m_Absolut, m_Final die Matrizen des jeweiligen Joint

        D3DXMatrixRotationYawPitchRoll(&m_Relative, Rotation.y, Rotation.x, Rotation.z);

	m_Relative._14=Position.x;
	m_Relative._24=Position.y;
	m_Relative._34=Position.z;

	if (pParent)
		m_Absolut=pParent->m_Absolut*m_Relative;
	else
		m_Absolut=m_Relative;

	//Transponiere die relative Matrix
	D3DXMatrixTranspose(&m_Relative, &m_Relative);

	D3DXMatrixTranspose(&m_Final, &m_Absolut);
Beim Rendern zeichne ich einfach Linien zwischen den Positionen der Joints, ich denke der Code sollte fehlerfrei sein, aber vollständigkeitshalber poste ich hin mal trotzdem:

Code: Alles auswählen

for (int i=0;i < m_NumberOfJoints;i++)
	{
		if (m_pJoints[i].m_pParent)
		{
			pVertices[i*6+0]=m_pJoints[i].m_pParent->m_Final._41;
			pVertices[i*6+1]=m_pJoints[i].m_pParent->m_Final._42;
			pVertices[i*6+2]=m_pJoints[i].m_pParent->m_Final._43;
		}
		else
		{
			pVertices[i*6+0]=0.0f;
			pVertices[i*6+1]=0.0f;
			pVertices[i*6+2]=0.0f;
		}

		pVertices[i*6+3]=m_pJoints[i].m_Final._41;
		pVertices[i*6+4]=m_pJoints[i].m_Final._42;
		pVertices[i*6+5]=m_pJoints[i].m_Final._43;
	}
Als Weltmatrix hab ich die Einheitsmatrix gesetzt.
Ich denke, dass der Fehler in der Berechnung der Matrizen liegt. Der Code ist angelehnt an Zerbst 3. Buch. Allerdings konnte ich nicht verifizieren, dass die Berechnung korrekt ist. Ich hab kein vernünftiges Tutorial darüber gefunden. Zudem sehen die vielen Matrixtranspositionen auffällig aus. Allerding sieht es ohne Transpositionen noch verzerrter aus ^^
Ich hoffe jemand von euch hat schonmal so etwas gemacht und kann zumindest überprüfen, ob mein Algorithmus richtig ist.
FredK
Beiträge: 31
Registriert: 06.05.2004, 17:11

Re: Skeletale Animationen

Beitrag von FredK »

Kennst du http://chumbalum.swissquake.ch/files/msViewer2.zip ? Das ist der Milkshape Binary viewer (mit Source). Du kannst ja mal deine Berechnungen mit denen des Viewers vergleichen. Vielleicht findest du den Fehler da.
Benutzeravatar
exploid
Establishment
Beiträge: 146
Registriert: 21.08.2005, 18:33

Re: Skeletale Animationen

Beitrag von exploid »

...
Zuletzt geändert von exploid am 04.11.2010, 12:12, insgesamt 1-mal geändert.
All your base are belong to us! Justice
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Skeletale Animationen

Beitrag von Krishty »

exploid hat geschrieben:Eine Verschiebung am Ellenbogengelenk scheint zunächst keinen Sinn zu machen weil dann der Arm zerrissen wird. Trotzdem sollte das formal
für jedes Gelenk möglich sein. Begründung: Effekte von Waffeneinwirkung mit der Folge das ein Körper zerrissen wird und Körperteile durch die
Luft fliegen.
Sollten die entprechenden Joints in diesem Fall nicht global werden, also die Hierarchie verlassen? Ich sehe nirgendwo den Sinn darin, einen umherfliegenden Arm noch unter Rumpf, Schulter und Oberarm eingeordnet zu haben … kein physikalischer Zusammenhang mehr, also auch kein virtueller. Das dürfte die Implementierung erheblich vereinfachen, vor allem, wenn hinterher noch Physik dazukommt.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
DarkD
Beiträge: 8
Registriert: 30.03.2005, 00:01

Re: Skeletale Animationen

Beitrag von DarkD »

@exploid
Jetzt weiß ich endlich wie man sinnvoll eine Verschiebung der Joints in einem Spiel nutzen kann :-)
Die Struktur der Joints war mir schon klar, aber du hast wohl Recht ich muss mich mal hinsetzen und das mal per Hand durchrechnen, obwohl das Multiplizieren von 4x4 Matrizen extrem langweilig ist ...
Das mit den zerissenen Ellbogengelenk werd ich später mal austesten, sobald alles funktioniert. Wird bestimmt gut aussehen, wenn ich das an eine Physik Engine anbinde.

@FredK
Danke für den Link. Wenn ich aber den Code 1:1 übernehme, dann ist bei mir nur noch links und rechts vertauscht (die Figur wird als zum Linkshänder).
Das wird wohl daran liegen, dass der Viewer OpenGL benutzt und ich DirectX. Daher bin ich jetzt am Konvertieren.
Wenn ich aber beim beim Einlesen jede Z Komponente negiere, um vom Rechtshändersystem zum Linkshändersystem überzugehen, erhalte ich folgendes Ergebnis:

Bild

Die Beine sind merkwürdig nah aneinander und der Nacken sieht ziemlich kaputt aus.

Hier der Code:

Code: Alles auswählen

AngleMatrix(Rotation, &m_RelativeJoint);
m_RelativeJoint._14=Position.x;
m_RelativeJoint._24=Position.y;
m_RelativeJoint._34=Position.z;
		
if (!pParent)
	m_AbsolutJoint=m_RelativeJoint;
else
	R_ConcatTransforms(pParent->m_AbsolutJoint, m_RelativeJoint, &m_AbsolutJoint);
m_RelativeJoint und m_AbsolutJoint sind 3x4 Matritzen. Und die Funktionen R_ConcatTransforms() und AngleMatrix() sind 1:1 aus dem Viewer kopiert.

Code: Alles auswählen

void AngleMatrix(const Vector Rotation, Matrix3x4 *pMatrix)
{
	float Angle;
	float sr, sp, sy, cr, cp, cy;
	
	Angle = Rotation.z;
	sy = sinf(Angle);
	cy = cosf(Angle);
	Angle = Rotation.y;
	sp = sinf(Angle);
	cp = cosf(Angle);
	Angle = Rotation.x;
	sr = sinf(Angle);
	cr = cosf(Angle);

	//Matrix = (Z * Y) * X
	pMatrix->_11 = cp*cy;
	pMatrix->_21 = cp*sy;
	pMatrix->_31 = -sp;
	pMatrix->_12 = sr*sp*cy+cr*-sy;
	pMatrix->_22 = sr*sp*sy+cr*cy;
	pMatrix->_32 = sr*cp;
	pMatrix->_13 = (cr*sp*cy+-sr*-sy);
	pMatrix->_23 = (cr*sp*sy+-sr*cy);
	pMatrix->_33 = cr*cp;
	pMatrix->_14 = 0.0;
	pMatrix->_24 = 0.0;
	pMatrix->_34 = 0.0;
}

void R_ConcatTransforms(const Matrix3x4 In1, const Matrix3x4 In2, Matrix3x4 *pOut)
{
	pOut->_11 = In1._11 * In2._11 + In1._12 * In2._21 + In1._13 * In2._31;
	pOut->_12 = In1._11 * In2._12 + In1._12 * In2._22 + In1._13 * In2._32;
	pOut->_13 = In1._11 * In2._13 + In1._12 * In2._23 + In1._13 * In2._33;
	pOut->_14 = In1._11 * In2._14 + In1._12 * In2._24 + In1._13 * In2._34 + In1._14;
	pOut->_21 = In1._21 * In2._11 + In1._22 * In2._21 + In1._23 * In2._31;
	pOut->_22 = In1._21 * In2._12 + In1._22 * In2._22 + In1._23 * In2._32;
	pOut->_23 = In1._21 * In2._13 + In1._22 * In2._23 + In1._23 * In2._33;
	pOut->_24 = In1._21 * In2._14 + In1._22 * In2._24 + In1._23 * In2._34 + In1._24;
	pOut->_31 = In1._31 * In2._11 + In1._32 * In2._21 + In1._33 * In2._31;
	pOut->_32 = In1._31 * In2._12 + In1._32 * In2._22 + In1._33 * In2._32;
	pOut->_33 = In1._31 * In2._13 + In1._32 * In2._23 + In1._33 * In2._33;
	pOut->_34 = In1._31 * In2._14 + In1._32 * In2._24 + In1._33 * In2._34 + In1._34;
}
Ich glaub statt R_ConcatTransforms() bräuchte ich L_ConcatTransforms(). Aber R_ConcatTransforms() ist mir schon Ungeheurer (3x4 Matrix * 3x4 Matrix = 3x4 Matrix)
Benutzeravatar
exploid
Establishment
Beiträge: 146
Registriert: 21.08.2005, 18:33

Re: Skeletale Animationen

Beitrag von exploid »

...
Zuletzt geändert von exploid am 04.11.2010, 12:12, insgesamt 1-mal geändert.
All your base are belong to us! Justice
FredK
Beiträge: 31
Registriert: 06.05.2004, 17:11

Re: Skeletale Animationen

Beitrag von FredK »

Rechne das Modell doch einfach so (spiegelverkehrt) aus und erst, wenn du das fertige Mesh hast, spiegelst du die z-Koordinate, damit es wieder ordentlich ist.
DarkD
Beiträge: 8
Registriert: 30.03.2005, 00:01

[Gelöst] Skeletale Animationen

Beitrag von DarkD »

Hi,

ich hab es jetzt nach einigen Nächten hingekriegt. Es hat wirklich geholfen sich die Mathematik einmal genau klar zu machen ...
Danke nochmal für den Link FredK das hat wirklich geholfen.
Ich hab es zwar geschafft das Skelett aus einer Milkshapedatei auszulesen. Aber die Animationen krieg ich immer noch nicht raus.
Stattdessen hab ich jetzt die Animationen einfach hart gecoded (das Milkshape Dateiformat ist wirklich nicht das Wahre).

Für all diejenigen, die mal auf das Problem stoßen sollten hier mein Berechnung der Animationsmatrizen:

Code: Alles auswählen

void BBEJOINT::CalculateAnimationMatrix(const float Time,
							  const float StartTime,
							const float EndTime,
							 const BOOL MultiAnimations)
{
	if (MultiAnimations && m_Touched)//Multianimationsmodus und der Joint wurde schon einmal berechnet
		return;

	int LeftRot=-1;
	int RightRot=-1;

	for (int i=0;i < m_NumberOfRotations;i++)
	{
		if (m_pRotations[i].m_Time > EndTime)//Hier muss immer ein echt größer stehen
			break;

		if (m_pRotations[i].m_Time >= Time)//Hier muss immer ein größer gleich stehen
		{
			RightRot=i;
			break;
		}

		if (m_pRotations[i].m_Time >= StartTime)//Hier muss immer ein größer gleich stehen
			LeftRot=i;
	}


	int LeftPos=-1;
	int RightPos=-1;

	for (int i=0;i < m_NumberOfTranslations;i++)
	{
		if (m_pTranslations[i].m_Time > EndTime)//Hier muss immer ein echt größer stehen
			break;

		if (m_pTranslations[i].m_Time >= Time)//Hier muss immer ein größer gleich stehen
		{
			RightPos=i;
			break;
		}

		if (m_pTranslations[i].m_Time >= StartTime)//Hier muss immer ein größer gleich stehen
			LeftPos=i;
	}

	//Rotation
	BBEMATRIX Transformation;

	if (LeftRot != -1 && RightRot == -1)//Wir sind rechts am Ende
	{
		D3DXMatrixRotationYawPitchRoll(&Transformation, m_pRotations[LeftRot].m_Data.y, m_pRotations[LeftRot].m_Data.x, m_pRotations[LeftRot].m_Data.z);
		m_IsIdentity=false;
	}
	else if (LeftRot == -1 && RightRot == -1)//Keine Rotation vorhanden
	{
		D3DXMatrixIdentity(&Transformation);
		m_IsIdentity=true;
	}
	else//Alles normal
	{
		float LeftTime;
		if (LeftRot == -1)//Haben wir links noch eine Keyframe?
			LeftTime=StartTime;
		else
			LeftTime=m_pRotations[LeftRot].m_Time;

		//Berechne Skalierung
		float Scale=(Time - LeftTime) / (m_pRotations[RightRot].m_Time - LeftTime);

		BBEVECTOR Rotation;

		if (LeftRot != -1)//Haben wir links noch eine Keyframe?
			Rotation=(1.0f-Scale)*m_pRotations[LeftRot].m_Data + Scale*m_pRotations[RightRot].m_Data;//Interpolieren
		else//Ansonsten Nullvektor für Links nehmen
			Rotation=Scale*m_pRotations[RightRot].m_Data;//Interpolieren

		if (m_pParent)
		{
			if (!m_pParent->m_IsIdentity)
			{
				BBEVECTOR NewRotation;

				NewRotation.x=m_pParent->m_Final._11*Rotation.x + m_pParent->m_Final._21*Rotation.y + m_pParent->m_Final._31*Rotation.z;
				NewRotation.y=m_pParent->m_Final._12*Rotation.x + m_pParent->m_Final._22*Rotation.y + m_pParent->m_Final._32*Rotation.z;
				NewRotation.z=m_pParent->m_Final._13*Rotation.x + m_pParent->m_Final._23*Rotation.y + m_pParent->m_Final._33*Rotation.z;

				D3DXMatrixRotationYawPitchRoll(&Transformation, NewRotation.y, NewRotation.x, NewRotation.z);
			}
			else
				D3DXMatrixRotationYawPitchRoll(&Transformation, Rotation.y, Rotation.x, Rotation.z);
		}
		else
			D3DXMatrixRotationYawPitchRoll(&Transformation, Rotation.y, Rotation.x, Rotation.z);

		m_IsIdentity=false;
	}

	//Translation

	if (LeftPos != -1 && RightPos == -1)//Wir sind rechts am Ende
	{
		Transformation._41=m_pTranslations[LeftPos].m_Data.x;
		Transformation._42=m_pTranslations[LeftPos].m_Data.y;
		Transformation._43=m_pTranslations[LeftPos].m_Data.z;
		m_IsIdentity=false;
	}
	else if (LeftPos != -1 || RightPos != -1)//Alles normal
	{
		float LeftTime;
		if (LeftPos == -1)//Haben wir links noch eine Keyframe?
			LeftTime=StartTime;
		else
			LeftTime=m_pTranslations[LeftPos].m_Time;

		//Berechne Skalierung
		const float Scale=(Time - LeftTime) / (m_pTranslations[RightPos].m_Time - LeftTime);

		//Interpolieren
		const float rScale=1.0f-Scale;
		Transformation._41=rScale*m_pTranslations[LeftPos].m_Data.x + Scale*m_pTranslations[RightPos].m_Data.x;
		Transformation._42=rScale*m_pTranslations[LeftPos].m_Data.y + Scale*m_pTranslations[RightPos].m_Data.y;
		Transformation._43=rScale*m_pTranslations[LeftPos].m_Data.z + Scale*m_pTranslations[RightPos].m_Data.z;
		m_IsIdentity=false;
	}

	//Den Joint in seine neue Position transformieren
	if (!m_IsIdentity)//Optimierung, falls nichts anliegt können wir uns das sparen
	{
		BBEVECTOR NewPosition=m_Position;

		if (m_pParent)
		{
			if (!m_pParent->m_IsIdentity)//Falls Einheitsmatrix ist das hier überflüssig
			{
				NewPosition.x=m_pParent->m_Final._11*m_Position.x + m_pParent->m_Final._21*m_Position.y + m_pParent->m_Final._31*m_Position.z + m_pParent->m_Final._41;
				NewPosition.y=m_pParent->m_Final._12*m_Position.x + m_pParent->m_Final._22*m_Position.y + m_pParent->m_Final._32*m_Position.z + m_pParent->m_Final._42;
				NewPosition.z=m_pParent->m_Final._13*m_Position.x + m_pParent->m_Final._23*m_Position.y + m_pParent->m_Final._33*m_Position.z + m_pParent->m_Final._43;
			}
		}

		//Um den Joint rotieren und nicht um den Ursprung
		Transformation._41 += - Transformation._11*NewPosition.x - Transformation._21*NewPosition.y - Transformation._31*NewPosition.z + NewPosition.x;
		Transformation._42 += - Transformation._12*NewPosition.x - Transformation._22*NewPosition.y - Transformation._32*NewPosition.z + NewPosition.y;
		Transformation._43 += - Transformation._13*NewPosition.x - Transformation._23*NewPosition.y - Transformation._33*NewPosition.z + NewPosition.z;
	}

	if (!m_IsIdentity)//Der Joint wurde signifikant berührt
		m_Touched=true;//Für Mehrfachanimationen keine weitere Animationen zulassen

	if (m_pParent)
	{
		if (!m_pParent->m_IsIdentity)//Falls Einheitsmatrix ist das hier überflüssig
		{
			m_Final=m_pParent->m_Final*Transformation;
			m_IsIdentity=false;//Dann sind wir wahrscheinlich auch nicht mehr eine Einheitsmatrix
		}
		else
			m_Final=Transformation;
	}
	else//Kein Parent Joint
		m_Final=Transformation;
}
Und hier mein Code zum Einlesen einer Milkshapedatei:

Code: Alles auswählen

//Joints lesen
	WORD Joints;
	fread(&Joints, sizeof(WORD), 1, File);

	if (Joints > 0)
		CreateJoints(Joints);

	BBEMATRIX Fix;
	Fix._11=1.0f;
	Fix._12=0.0f;
	Fix._13=0.0f;
	Fix._14=0.0f;

	Fix._21=0.0f;
	Fix._22=0.0f;
	Fix._23=1.0f;
	Fix._24=0.0f;

	Fix._31=0.0f;
	Fix._32=1.0f;
	Fix._33=0.0f;
	Fix._34=0.0f;

	Fix._41=0.0f;
	Fix._42=0.0f;
	Fix._43=0.0f;
	Fix._44=1.0f;

	char *pJointNames=new char[32*Joints];
	BBEMATRIX *pAbsoluts=new BBEMATRIX[Joints];
	float **ppTranslations=new float*[Joints];
	float **ppRotations=new float*[Joints];

	for (int i=0;i < Joints;i++)
	{
		BYTE Flag;
		fread(&Flag, sizeof(BYTE), 1, File);

		fread(&pJointNames[32*i], sizeof(char), 32, File);

		char Parent[32];
		fread(&Parent, sizeof(char), 32, File);

		//Suche den Vater
		int ParentID=-1;
		for (int j=0;j < i;j++)
		{
			if (strcmp(Parent, &pJointNames[32*j]) == 0)
			{
				ParentID=j;
				break;
			}
		}

		BBEVECTOR Rotation;
		fread(Rotation, sizeof(BBEVECTOR), 1, File);

		BBEVECTOR Position;
		fread(Position, sizeof(BBEVECTOR), 1, File);

		WORD NumRotations;
		fread(&NumRotations, sizeof(WORD), 1, File);

		WORD NumTranslations;
		fread(&NumTranslations, sizeof(WORD), 1, File);

		ppTranslations[i]=new float[NumTranslations*4];
		ppRotations[i]=new float[NumRotations*4];

		const float Epsilon=0.0001f;
		int k=0;

		for (int j=0;j < NumRotations;j++)
		{
			float Time;
			BBEVECTOR Data;

			fread(&Time, sizeof(float), 1, File);
			fread(&Data, sizeof(BBEVECTOR), 1, File);

			if (Data.x*Data.x < Epsilon*Epsilon &&
				Data.y*Data.y < Epsilon*Epsilon &&
				Data.z*Data.z < Epsilon*Epsilon)
				continue;

			ppRotations[i][k*4+0]=Time;
			ppRotations[i][k*4+1]=Data.x;
			ppRotations[i][k*4+2]=Data.y;
			ppRotations[i][k*4+3]=Data.z;
			k++;
		}

		int l=0;

		for (int j=0;j < NumTranslations;j++)
		{
			float Time;
			BBEVECTOR Data;

			fread(&Time, sizeof(float), 1, File);
			fread(&Data, sizeof(BBEVECTOR), 1, File);

			if (Data.x*Data.x < Epsilon*Epsilon &&
				Data.y*Data.y < Epsilon*Epsilon &&
				Data.z*Data.z < Epsilon*Epsilon)
				continue;

			ppTranslations[i][l*4+0]=Time;
			ppTranslations[i][l*4+1]=Data.x;
			ppTranslations[i][l*4+2]=Data.y;
			ppTranslations[i][l*4+3]=Data.z;
			l++;
		}

		//Berechne die Position des Joints aus den Milkshapedaten
		BBEMATRIX Relative;

		float Angle;
		float sr, sp, sy, cr, cp, cy;
	
		Angle = Rotation.z;
		sy = sinf(Angle);
		cy = cosf(Angle);
		Angle = Rotation.y;
		sp = sinf(Angle);
		cp = cosf(Angle);
		Angle = Rotation.x;
		sr = sinf(Angle);
		cr = cosf(Angle);

		//Matrix = (Z * Y) * X
		Relative._11 = cp*cy;
		Relative._21 = cp*sy;
		Relative._31 = -sp;
		Relative._12 = sr*sp*cy+cr*-sy;
		Relative._22 = sr*sp*sy+cr*cy;
		Relative._32 = sr*cp;
		Relative._13 = (cr*sp*cy+-sr*-sy);
		Relative._23 = (cr*sp*sy+-sr*cy);
		Relative._33 = cr*cp;

		Relative._41 = 0.0f;
		Relative._42 = 0.0f;
		Relative._43 = 0.0f;
		Relative._44 = 1.0f;

		Relative._14=Position.x;
		Relative._24=Position.y;
		Relative._34=Position.z;

		if (ParentID != -1)
			pAbsoluts[i]=pAbsoluts[ParentID]*Relative;
		else
			pAbsoluts[i]=Relative;

		BBEMATRIX Final=pAbsoluts[i]*Fix;

		D3DXMatrixTranspose(&Final, &Final);

		Final._41=-Final._41;

		Position.x=Final._41;
		Position.y=Final._42;
		Position.z=Final._43;

		if (!AddJoint(i, ParentID, Position, 0, 0))
			return false;

		for (int j=0;j < k;j++)
		{
			BBEVECTOR Data;
			Data.x=ppRotations[i][j*4+1];
			Data.y=ppRotations[i][j*4+2];
			Data.z=ppRotations[i][j*4+3];

			SetRotationKeyFrame(i, j, ppRotations[i][j*4], Data.x, Data.y, Data.z);
		}

		for (int j=0;j < l;j++)
		{

			BBEVECTOR Data;
			Data.x=ppTranslations[i][j*4+1];
			Data.y=ppTranslations[i][j*4+2];
			Data.z=ppTranslations[i][j*4+3];

			SetTranslationKeyFrame(i, j, ppTranslations[i][j*4], Data.x, Data.y, Data.z);
		}
	}
Die Animationen werden zwar falsch eingelesen, aber immerhin stimmt das Skelett.
Übrigens muss man beim Einlesen die X Koordiante der Vertice negieren und dann natürlich auch die Indize anpassen, damit das richtige gecullt wird.
Danke nochmal an alle, die mir geholfen haben :-)
Antworten