예제 #1
0
// function is similar to part of CSkelMeshInstance::SetMesh()
static void BuildSkeleton(TArray<CCoords> &Coords, const TArray<RJoint> &Bones, const TArray<AnalogTrack> &Anim)
{
	guard(BuildSkeleton);

	int numBones = Anim.Num();
	Coords.Empty(numBones);
	Coords.AddZeroed(numBones);

	for (int i = 0; i < numBones; i++)
	{
		const AnalogTrack &A = Anim[i];
		const RJoint &B = Bones[i];
		CCoords &BC = Coords[i];
		// compute reference bone coords
		CVec3 BP;
		CQuat BO;
		// get default pose
		BP = CVT(A.KeyPos[0]);
		BO = CVT(A.KeyQuat[0]);
		if (!i) BO.Conjugate();

		BC.origin = BP;
		BO.ToAxis(BC.axis);
		// move bone position to global coordinate space
		if (i)	// do not rotate root bone
		{
			assert(B.parent >= 0);
			Coords[B.parent].UnTransformCoords(BC, BC);
		}
	}

	unguard;
}
예제 #2
0
// function is similar to part of CSkelMeshInstance::SetMesh() and Rune mesh loader
static void BuildSkeleton(TArray<CCoords> &Coords, const TArray<CSkelMeshBone> &Bones)
{
	guard(BuildSkeleton);

	int numBones = Bones.Num();
	Coords.Empty(numBones);
	Coords.AddZeroed(numBones);

	for (int i = 0; i < numBones; i++)
	{
		const CSkelMeshBone &B = Bones[i];
		CCoords &BC = Coords[i];
		// compute reference bone coords
		CVec3 BP;
		CQuat BO;
		// get default pose
		BP = B.Position;
		BO = B.Orientation;
#if MIRROR_MESH
		BP[1] *= -1;							// y
		BO.y  *= -1;
		BO.w  *= -1;
#endif
		if (!i) BO.Conjugate();					// root bone

		BC.origin = BP;
		BO.ToAxis(BC.axis);
		// move bone position to global coordinate space
		if (i)	// do not rotate root bone
		{
			assert(B.ParentIndex < i);
			Coords[B.ParentIndex].UnTransformCoords(BC, BC);
		}
#if 0
	//!!
if (i == 32)
{
	appNotify("Bone %d (%8.3f %8.3f %8.3f) - (%8.3f %8.3f %8.3f %8.3f)", i, VECTOR_ARG(BP), QUAT_ARG(BO));
#define C BC
	appNotify("REF   : o=%8.3f %8.3f %8.3f",    VECTOR_ARG(C.origin ));
	appNotify("        0=%8.3f %8.3f %8.3f",    VECTOR_ARG(C.axis[0]));
	appNotify("        1=%8.3f %8.3f %8.3f",    VECTOR_ARG(C.axis[1]));
	appNotify("        2=%8.3f %8.3f %8.3f",    VECTOR_ARG(C.axis[2]));
#undef C
}
#endif
	}

	unguard;
}
예제 #3
0
void ExportMd5Anim(const CAnimSet *Anim)
{
	guard(ExportMd5Anim);

	int numBones = Anim->TrackBoneNames.Num();
	UObject *OriginalAnim = Anim->OriginalAnim;

	for (int AnimIndex = 0; AnimIndex < Anim->Sequences.Num(); AnimIndex++)
	{
		int i;
		const CAnimSequence &S = Anim->Sequences[AnimIndex];

		FArchive *Ar = CreateExportArchive(OriginalAnim, "%s/%s.md5anim", OriginalAnim->Name, *S.Name);
		if (!Ar) continue;

		Ar->Printf(
			"MD5Version 10\n"
			"commandline \"Created with UE Viewer\"\n"
			"\n"
			"numFrames %d\n"
			"numJoints %d\n"
			"frameRate %g\n"
			"numAnimatedComponents %d\n"
			"\n",
			S.NumFrames,
			numBones,
			S.Rate,
			numBones * 6
		);

		// skeleton
		Ar->Printf("hierarchy {\n");
		for (i = 0; i < numBones; i++)
		{
			Ar->Printf("\t\"%s\" %d %d %d\n", *Anim->TrackBoneNames[i], (i == 0) ? -1 : 0, 63, i * 6);
				// ParentIndex is unknown for UAnimSet, so always write "0"
				// here: 6 is number of components per frame, 63 = (1<<6)-1 -- flags "all components are used"
		}

		// bounds
		Ar->Printf("}\n\nbounds {\n");
		for (i = 0; i < S.NumFrames; i++)
			Ar->Printf("\t( -100 -100 -100 ) ( 100 100 100 )\n");	//!! dummy
		Ar->Printf("}\n\n");

		// baseframe and frames
		for (int Frame = -1; Frame < S.NumFrames; Frame++)
		{
			int t = Frame;
			if (Frame == -1)
			{
				Ar->Printf("baseframe {\n");
				t = 0;
			}
			else
				Ar->Printf("frame %d {\n", Frame);

			for (int b = 0; b < numBones; b++)
			{
				CVec3 BP;
				CQuat BO;
				S.Tracks[b].GetBonePosition(t, S.NumFrames, false, BP, BO);
				if (!b) BO.Conjugate();			// root bone
#if MIRROR_MESH
				BO.y  *= -1;
				BO.w  *= -1;
				BP[1] *= -1;					// y
#endif
				if (BO.w < 0) BO.Negate();		// W-component of quaternion will be removed ...
				if (Frame < 0)
					Ar->Printf("\t( %f %f %f ) ( %.10f %.10f %.10f )\n", VECTOR_ARG(BP), BO.x, BO.y, BO.z);
				else
					Ar->Printf("\t%f %f %f %.10f %.10f %.10f\n", VECTOR_ARG(BP), BO.x, BO.y, BO.z);
			}
			Ar->Printf("}\n\n");
		}

		delete Ar;
	}

	unguard;
}
예제 #4
0
static void ExportAnimations(ExportContext& Context, FArchive& Ar)
{
	guard(ExportAnimations);

	const CAnimSet* Anim = Context.SkelMesh->Anim;
	int NumBones = Context.SkelMesh->RefSkeleton.Num();

	// Build mesh to anim bone map

	TArray<int> BoneMap;
	BoneMap.Init(-1, NumBones);
	TArray<int> AnimBones;
	AnimBones.Empty(NumBones);

	for (int i = 0; i < NumBones; i++)
	{
		const CSkelMeshBone &B = Context.SkelMesh->RefSkeleton[i];
		for (int j = 0; j < Anim->TrackBoneNames.Num(); j++)
		{
			if (!stricmp(B.Name, Anim->TrackBoneNames[j]))
			{
				BoneMap[i] = j;			// lookup CAnimSet bone by mesh bone index
				AnimBones.Add(i);		// indicate that the bone has animation
				break;
			}
		}
	}

	Ar.Printf(
		"  \"animations\" : [\n"
	);

	int FirstDataIndex = Context.Data.Num();

	// Iterate over all animations
	for (int SeqIndex = 0; SeqIndex < Anim->Sequences.Num(); SeqIndex++)
	{
		const CAnimSequence &Seq = *Anim->Sequences[SeqIndex];

		Ar.Printf(
			"    {\n"
			"      \"name\" : \"%s\",\n",
			*Seq.Name
		);

		struct AnimSampler
		{
			enum ChannelType
			{
				TRANSLATION,
				ROTATION
			};

			int BoneNodeIndex;
			ChannelType Type;
			const CAnimTrack* Track;
		};

		TArray<AnimSampler> Samplers;
		Samplers.Empty(AnimBones.Num() * 2);

		//!! Optimization:
		//!! 1. there will be missing tracks (AnimRotationOnly etc) - drop such samplers
		//!! 2. store all time tracks in a single BufferView, all rotation tracks in another, and all position track in 3rd one - this
		//!!    will reduce amount of BufferViews in json text (combine them by data type)

		// Prepare channels array
		Ar.Printf("      \"channels\" : [\n");
		for (int BoneIndex = 0; BoneIndex < AnimBones.Num(); BoneIndex++)
		{
			int MeshBoneIndex = AnimBones[BoneIndex];
			int AnimBoneIndex = BoneMap[MeshBoneIndex];

			const CAnimTrack* Track = Seq.Tracks[AnimBoneIndex];

			int TranslationSamplerIndex = Samplers.Num();
			AnimSampler* Sampler = new (Samplers) AnimSampler;
			Sampler->Type = AnimSampler::TRANSLATION;
			Sampler->BoneNodeIndex = MeshBoneIndex + FIRST_BONE_NODE;
			Sampler->Track = Track;

			int RotationSamplerIndex = Samplers.Num();
			Sampler = new (Samplers) AnimSampler;
			Sampler->Type = AnimSampler::ROTATION;
			Sampler->BoneNodeIndex = MeshBoneIndex + FIRST_BONE_NODE;
			Sampler->Track = Track;

			// Print glTF information. Not using usual formatting here to make output a little bit more compact.
			Ar.Printf(
				"        { \"sampler\" : %d, \"target\" : { \"node\" : %d, \"path\" : \"%s\" } },\n",
				TranslationSamplerIndex, MeshBoneIndex + FIRST_BONE_NODE, "translation"
			);
			Ar.Printf(
				"        { \"sampler\" : %d, \"target\" : { \"node\" : %d, \"path\" : \"%s\" } }%s\n",
				RotationSamplerIndex, MeshBoneIndex + FIRST_BONE_NODE, "rotation", BoneIndex == AnimBones.Num()-1 ? "" : ","
			);
		}
		Ar.Printf("      ],\n");

		// Prepare samplers
		Ar.Printf("      \"samplers\" : [\n");
		for (int SamplerIndex = 0; SamplerIndex < Samplers.Num(); SamplerIndex++)
		{
			const AnimSampler& Sampler = Samplers[SamplerIndex];

			// Prepare time array
			const TArray<float>* TimeArray = (Sampler.Type == AnimSampler::TRANSLATION) ? &Sampler.Track->KeyPosTime : &Sampler.Track->KeyQuatTime;
			if (TimeArray->Num() == 0)
			{
				// For this situation, use track's time array
				TimeArray = &Sampler.Track->KeyTime;
			}
			int NumKeys = Sampler.Type == (AnimSampler::TRANSLATION) ? Sampler.Track->KeyPos.Num() : Sampler.Track->KeyQuat.Num();

			int TimeBufIndex = Context.Data.AddZeroed();
			BufferData& TimeBuf = Context.Data[TimeBufIndex];
			TimeBuf.Setup(NumKeys, "SCALAR", BufferData::FLOAT, sizeof(float));

			float RateScale = 1.0f / Seq.Rate;
			float LastFrameTime = 0;
			if (TimeArray->Num() == 0 || NumKeys == 1)
			{
				// Fill with equally spaced values
				for (int i = 0; i < NumKeys; i++)
				{
					TimeBuf.Put(i * RateScale);
				}
				LastFrameTime = NumKeys-1;
			}
			else
			{
				for (int i = 0; i < TimeArray->Num(); i++)
				{
					TimeBuf.Put((*TimeArray)[i] * RateScale);
				}
				LastFrameTime = (*TimeArray)[TimeArray->Num()-1];
			}
			// Prepare min/max values for time track, it's required by glTF standard
			TimeBuf.BoundsMin = "[ 0 ]";
			char buf[64];
			appSprintf(ARRAY_ARG(buf), "[ %g ]", LastFrameTime * RateScale);
			TimeBuf.BoundsMax = buf;

			// Try to reuse TimeBuf from previous tracks
			TimeBufIndex = Context.GetFinalIndexForLastBlock(FirstDataIndex);

			// Prepare data
			int DataBufIndex = Context.Data.AddZeroed();
			BufferData& DataBuf = Context.Data[DataBufIndex];
			if (Sampler.Type == AnimSampler::TRANSLATION)
			{
				// Translation track
				DataBuf.Setup(NumKeys, "VEC3", BufferData::FLOAT, sizeof(CVec3));
				for (int i = 0; i < NumKeys; i++)
				{
					CVec3 Pos = Sampler.Track->KeyPos[i];
					TransformPosition(Pos);
					DataBuf.Put(Pos);
				}
			}
			else
			{
				// Rotation track
				DataBuf.Setup(NumKeys, "VEC4", BufferData::FLOAT, sizeof(CQuat));
				for (int i = 0; i < NumKeys; i++)
				{
					CQuat Rot = Sampler.Track->KeyQuat[i];
					TransformRotation(Rot);
					if (Sampler.BoneNodeIndex - FIRST_BONE_NODE == 0)
					{
						Rot.Conjugate();
					}
					DataBuf.Put(Rot);
				}
			}

			// Try to reuse data block as well
			DataBufIndex = Context.GetFinalIndexForLastBlock(FirstDataIndex);

			// Write glTF info
			Ar.Printf(
				"        { \"input\" : %d, \"output\" : %d }%s\n",
				TimeBufIndex, DataBufIndex, SamplerIndex == Samplers.Num()-1 ? "" : ","
			);
		}
		Ar.Printf("      ]\n");

		Ar.Printf("    }%s\n", SeqIndex == Anim->Sequences.Num()-1 ? "" : ",");
	}

	Ar.Printf("  ],\n");

	unguard;
}
예제 #5
0
static void ExportSkinData(ExportContext& Context, const CSkelMeshLod& Lod, FArchive& Ar)
{
	guard(ExportSkinData);

	int numBones = Context.SkelMesh->RefSkeleton.Num();

	int MatrixBufIndex = Context.Data.AddZeroed();
	BufferData& MatrixBuf = Context.Data[MatrixBufIndex];
	MatrixBuf.Setup(numBones, "MAT4", BufferData::FLOAT, sizeof(CMat4));

	Ar.Printf(
		"  \"nodes\" : [\n"
		"    {\n"
		"      \"name\" : \"%s\",\n"
		"      \"mesh\" : 0,\n"
		"      \"skin\" : 0,\n"
		"      \"children\" : [ 1 ]\n"
		"    },\n",
		Context.MeshName);

	TArray<CCoords> BoneCoords;
	BoneCoords.AddZeroed(numBones);

	for (int boneIndex = 0; boneIndex < numBones; boneIndex++)
	{
		const CSkelMeshBone& B = Context.SkelMesh->RefSkeleton[boneIndex];

		// Find all children
		TStaticArray<int, 32> children;
		for (int j = 0; j < numBones; j++)
		{
			if (boneIndex == j) continue;
			const CSkelMeshBone& B2 = Context.SkelMesh->RefSkeleton[j];
			if (B2.ParentIndex == boneIndex)
			{
				children.Add(j);
			}
		}

		Ar.Printf(
			"    {\n"
			"      \"name\" : \"%s\",\n",
			*B.Name
		);

		// Write children
		if (children.Num())
		{
			Ar.Printf("      \"children\" : [ %d", children[0]+FIRST_BONE_NODE);
			for (int j = 1; j < children.Num(); j++)
			{
				Ar.Printf(", %d", children[j]+FIRST_BONE_NODE);
			}
			Ar.Printf(" ],\n");
		}

		// Bone transform
		CVec3 bonePos = B.Position;
		CQuat boneRot = B.Orientation;
		if (boneIndex == 0)
		{
			boneRot.Conjugate();
		}

		TransformPosition(bonePos);
		TransformRotation(boneRot);

		Ar.Printf(
			"      \"translation\" : [ %g, %g, %g ],\n"
			"      \"rotation\" : [ %g, %g, %g, %g ]\n",
			bonePos[0], bonePos[1], bonePos[2],
			boneRot.x, boneRot.y, boneRot.z, boneRot.w
		);

		boneRot.w *= -1;

		CCoords& BC = BoneCoords[boneIndex];
		BC.origin = bonePos;
		boneRot.ToAxis(BC.axis);
		if (boneIndex)
		{
			// World coordinate
			BoneCoords[B.ParentIndex].UnTransformCoords(BC, BC);
		}
		CCoords InvCoords;
		InvertCoords(BC, InvCoords);

		CMat4 BC4x4(InvCoords);
		MatrixBuf.Put(BC4x4);

		// Closing brace
		Ar.Printf(
			"    }%s\n",
			boneIndex == (numBones-1) ? "" : ","
		);
	}

	// Close "nodes" array
	Ar.Printf("  ],\n");

	// Make "skins"
	Ar.Printf(
		"  \"skins\" : [\n"
		"    {\n"
		"      \"inverseBindMatrices\" : %d,\n"
		"      \"skeleton\" : 1,\n"
		"      \"joints\" : [",
		MatrixBufIndex
	);
	for (int i = 0; i < numBones; i++)
	{
		if ((i & 31) == 0) Ar.Printf("\n        ");
		Ar.Printf("%d%s", i+FIRST_BONE_NODE, (i == numBones-1) ? "" : ",");
	}
	Ar.Printf(
		"\n"
		"      ]\n"
		"    }\n"
		"  ],\n"
	);

	unguard;
}