/** * Converts a COLLADA XML document into the PMD mesh format. * * @param input XML document to parse * @param output callback for writing the PMD data; called lots of times * with small strings * @param xmlErrors output - errors reported by the XML parser * @throws ColladaException on failure */ static void ColladaToPMD(const char* input, OutputCB& output, std::string& xmlErrors) { CommonConvert converter(input, xmlErrors); if (converter.GetInstance().GetEntity()->GetType() == FCDEntity::GEOMETRY) { Log(LOG_INFO, "Found static geometry"); FCDGeometryPolygons* polys = GetPolysFromGeometry((FCDGeometry*)converter.GetInstance().GetEntity()); // Convert the geometry into a suitable form for the game ReindexGeometry(polys); std::vector<VertexBlend> boneWeights; // unused std::vector<BoneTransform> boneTransforms; // unused std::vector<PropPoint> propPoints; // Get the raw vertex data FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION); FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL); FCDGeometryPolygonsInput* inputTexcoord = polys->FindInput(FUDaeGeometryInput::TEXCOORD); const uint32* indicesCombined = inputPosition->GetIndices(); size_t indicesCombinedCount = inputPosition->GetIndexCount(); // (ReindexGeometry guarantees position/normal/texcoord have the same indexes) FCDGeometrySource* sourcePosition = inputPosition->GetSource(); FCDGeometrySource* sourceNormal = inputNormal ->GetSource(); FCDGeometrySource* sourceTexcoord = inputTexcoord->GetSource(); float* dataPosition = sourcePosition->GetData(); float* dataNormal = sourceNormal ->GetData(); float* dataTexcoord = sourceTexcoord->GetData(); size_t vertexCount = sourcePosition->GetDataCount() / 3; assert(sourcePosition->GetDataCount() == vertexCount*3); assert(sourceNormal ->GetDataCount() == vertexCount*3); assert(sourceTexcoord->GetDataCount() == vertexCount*2); // Transform mesh coordinate system to game coordinates // (doesn't modify prop points) TransformStaticModel(dataPosition, dataNormal, vertexCount, converter.GetEntityTransform(), converter.IsYUp()); // Add static prop points // which are empty child nodes of the main parent // Default prop points are already given in game coordinates AddDefaultPropPoints(propPoints); // Calculate transform to convert from COLLADA-defined up_axis to Z-up because // it's relatively straightforward to convert that to game coordinates FMMatrix44 upAxisTransform = FMMatrix44_Identity; if (converter.IsYUp()) { // Prop points are rotated -90 degrees about the X-axis, reverse that rotation // (do this once now because it's easier than messing with quaternions later) upAxisTransform = FMMatrix44::XAxisRotationMatrix(1.57f); } AddStaticPropPoints(propPoints, upAxisTransform, converter.GetInstance().GetParent()); WritePMD(output, indicesCombined, indicesCombinedCount, dataPosition, dataNormal, dataTexcoord, vertexCount, boneWeights, boneTransforms, propPoints); } else if (converter.GetInstance().GetType() == FCDEntityInstance::CONTROLLER) { Log(LOG_INFO, "Found skinned geometry"); FCDControllerInstance& controllerInstance = static_cast<FCDControllerInstance&>(converter.GetInstance()); // (NB: GetType is deprecated and should be replaced with HasType, // except that has irritating linker errors when using a DLL, so don't // bother) assert(converter.GetInstance().GetEntity()->GetType() == FCDEntity::CONTROLLER); // assume this is always true? FCDController* controller = static_cast<FCDController*>(converter.GetInstance().GetEntity()); FCDSkinController* skin = controller->GetSkinController(); REQUIRE(skin != NULL, "is skin controller"); FixSkeletonRoots(controllerInstance); // Data for joints is stored in two places - avoid overflows by limiting // to the minimum of the two sizes, and warn if they're different (which // happens in practice for slightly-broken meshes) size_t jointCount = std::min(skin->GetJointCount(), controllerInstance.GetJointCount()); if (skin->GetJointCount() != controllerInstance.GetJointCount()) { Log(LOG_WARNING, "Mismatched bone counts (skin has %d, skeleton has %d)", skin->GetJointCount(), controllerInstance.GetJointCount()); for (size_t i = 0; i < skin->GetJointCount(); ++i) Log(LOG_INFO, "Skin joint %d: %s", i, skin->GetJoint(i)->GetId().c_str()); for (size_t i = 0; i < controllerInstance.GetJointCount(); ++i) Log(LOG_INFO, "Skeleton joint %d: %s", i, controllerInstance.GetJoint(i)->GetName().c_str()); } // Get the skinned mesh for this entity FCDGeometry* baseGeometry = controller->GetBaseGeometry(); REQUIRE(baseGeometry != NULL, "controller has base geometry"); FCDGeometryPolygons* polys = GetPolysFromGeometry(baseGeometry); // Make sure it doesn't use more bones per vertex than the game can handle SkinReduceInfluences(skin, maxInfluences, 0.001f); // Convert the geometry into a suitable form for the game ReindexGeometry(polys, skin); const Skeleton& skeleton = FindSkeleton(controllerInstance); // Convert the bone influences into VertexBlend structures for the PMD: bool hasComplainedAboutNonexistentJoints = false; // because we want to emit a warning only once std::vector<VertexBlend> boneWeights; // one per vertex const FCDSkinControllerVertex* vertexInfluences = skin->GetVertexInfluences(); for (size_t i = 0; i < skin->GetInfluenceCount(); ++i) { VertexBlend influences = defaultInfluences; assert(vertexInfluences[i].GetPairCount() <= maxInfluences); // guaranteed by ReduceInfluences; necessary for avoiding // out-of-bounds writes to the VertexBlend for (size_t j = 0; j < vertexInfluences[i].GetPairCount(); ++j) { uint32 jointIdx = vertexInfluences[i].GetPair(j)->jointIndex; REQUIRE(jointIdx <= 0xFF, "sensible number of joints (<256)"); // because we only have a u8 to store them in // Find the joint on the skeleton, after checking it really exists FCDSceneNode* joint = NULL; if (jointIdx < controllerInstance.GetJointCount()) joint = controllerInstance.GetJoint(jointIdx); // Complain on error if (! joint) { if (! hasComplainedAboutNonexistentJoints) { Log(LOG_WARNING, "Vertexes influenced by nonexistent joint"); hasComplainedAboutNonexistentJoints = true; } continue; } // Store into the VertexBlend int boneId = skeleton.GetBoneID(joint->GetName().c_str()); if (boneId < 0) { // The relevant joint does exist, but it's not a recognised // bone in our chosen skeleton structure Log(LOG_ERROR, "Vertex influenced by unrecognised bone '%s'", joint->GetName().c_str()); continue; } influences.bones[j] = (uint8)boneId; influences.weights[j] = vertexInfluences[i].GetPair(j)->weight; } boneWeights.push_back(influences); } // Convert the bind pose into BoneTransform structures for the PMD: BoneTransform boneDefault = { { 0, 0, 0 }, { 0, 0, 0, 1 } }; // identity transform std::vector<BoneTransform> boneTransforms (skeleton.GetBoneCount(), boneDefault); for (size_t i = 0; i < jointCount; ++i) { FCDSceneNode* joint = controllerInstance.GetJoint(i); int boneId = skeleton.GetRealBoneID(joint->GetName().c_str()); if (boneId < 0) { // unrecognised joint - it's probably just a prop point // or something, so ignore it continue; } FMMatrix44 bindPose = skin->GetJoint(i)->GetBindPoseInverse().Inverted(); HMatrix matrix; memcpy(matrix, bindPose.Transposed().m, sizeof(matrix)); // set matrix = bindPose^T, to match what decomp_affine wants AffineParts parts; decomp_affine(matrix, &parts); BoneTransform b = { { parts.t.x, parts.t.y, parts.t.z }, { parts.q.x, parts.q.y, parts.q.z, parts.q.w } }; boneTransforms[boneId] = b; } // Construct the list of prop points. // Currently takes all objects that are directly attached to a // standard bone, and whose name begins with "prop-" or "prop_". std::vector<PropPoint> propPoints; AddDefaultPropPoints(propPoints); for (size_t i = 0; i < jointCount; ++i) { FCDSceneNode* joint = controllerInstance.GetJoint(i); int boneId = skeleton.GetBoneID(joint->GetName().c_str()); if (boneId < 0) { // unrecognised joint name - ignore, same as before continue; } // Check all the objects attached to this bone for (size_t j = 0; j < joint->GetChildrenCount(); ++j) { FCDSceneNode* child = joint->GetChild(j); if (child->GetName().find("prop-") != 0 && child->GetName().find("prop_") != 0) { // doesn't begin with "prop-", so skip it continue; } // Strip off the "prop-" from the name std::string propPointName (child->GetName().substr(5)); Log(LOG_INFO, "Adding prop point %s", propPointName.c_str()); // Get translation and orientation of local transform FMMatrix44 localTransform = child->ToMatrix(); HMatrix matrix; memcpy(matrix, localTransform.Transposed().m, sizeof(matrix)); AffineParts parts; decomp_affine(matrix, &parts); // Add prop point to list PropPoint p = { propPointName, { parts.t.x, parts.t.y, parts.t.z }, { parts.q.x, parts.q.y, parts.q.z, parts.q.w }, (uint8)boneId }; propPoints.push_back(p); } } // Get the raw vertex data FCDGeometryPolygonsInput* inputPosition = polys->FindInput(FUDaeGeometryInput::POSITION); FCDGeometryPolygonsInput* inputNormal = polys->FindInput(FUDaeGeometryInput::NORMAL); FCDGeometryPolygonsInput* inputTexcoord = polys->FindInput(FUDaeGeometryInput::TEXCOORD); const uint32* indicesCombined = inputPosition->GetIndices(); size_t indicesCombinedCount = inputPosition->GetIndexCount(); // (ReindexGeometry guarantees position/normal/texcoord have the same indexes) FCDGeometrySource* sourcePosition = inputPosition->GetSource(); FCDGeometrySource* sourceNormal = inputNormal ->GetSource(); FCDGeometrySource* sourceTexcoord = inputTexcoord->GetSource(); float* dataPosition = sourcePosition->GetData(); float* dataNormal = sourceNormal ->GetData(); float* dataTexcoord = sourceTexcoord->GetData(); size_t vertexCount = sourcePosition->GetDataCount() / 3; assert(sourcePosition->GetDataCount() == vertexCount*3); assert(sourceNormal ->GetDataCount() == vertexCount*3); assert(sourceTexcoord->GetDataCount() == vertexCount*2); // Transform model coordinate system to game coordinates TransformSkinnedModel(dataPosition, dataNormal, vertexCount, boneTransforms, propPoints, converter.GetEntityTransform(), skin->GetBindShapeTransform(), converter.IsYUp(), converter.IsXSI()); WritePMD(output, indicesCombined, indicesCombinedCount, dataPosition, dataNormal, dataTexcoord, vertexCount, boneWeights, boneTransforms, propPoints); } else { throw ColladaException("Unrecognised object type"); } }
void FFbxExporter::ExportAnimTrack(class AMatineeActor* MatineeActor, USkeletalMeshComponent* SkeletalMeshComponent) { static const float SamplingRate = 1.f / DEFAULT_SAMPLERATE; float MatineeLength = MatineeActor->MatineeData->InterpLength; // show a status update every 1 second worth of samples const float UpdateFrequency = 1.0f; float NextUpdateTime = UpdateFrequency; // find root and find the bone array TArray<FbxNode*> BoneNodes; if ( FindSkeleton(SkeletalMeshComponent, BoneNodes)==false ) { // error return; } float SampleTime; for(SampleTime = 0.f; SampleTime <= MatineeLength; SampleTime += SamplingRate) { // This will call UpdateSkelPose on the skeletal mesh component to move bones based on animations in the matinee group MatineeActor->UpdateInterp( SampleTime, true ); FbxTime ExportTime; ExportTime.SetSecondDouble(SampleTime); NextUpdateTime -= SamplingRate; if( NextUpdateTime <= 0.0f ) { NextUpdateTime = UpdateFrequency; GWarn->StatusUpdate( FMath::RoundToInt( SampleTime ), FMath::RoundToInt(MatineeLength), NSLOCTEXT("FbxExporter", "ExportingToFbxStatus", "Exporting to FBX") ); } // Add the animation data to the bone nodes for(int32 BoneIndex = 0; BoneIndex < BoneNodes.Num(); ++BoneIndex) { FName BoneName = SkeletalMeshComponent->SkeletalMesh->RefSkeleton.GetBoneName(BoneIndex); FbxNode* CurrentBoneNode = BoneNodes[BoneIndex]; // Create the AnimCurves FbxAnimCurve* Curves[6]; Curves[0] = CurrentBoneNode->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); Curves[1] = CurrentBoneNode->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); Curves[2] = CurrentBoneNode->LclTranslation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); Curves[3] = CurrentBoneNode->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_X, true); Curves[4] = CurrentBoneNode->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Y, true); Curves[5] = CurrentBoneNode->LclRotation.GetCurve(AnimLayer, FBXSDK_CURVENODE_COMPONENT_Z, true); for(int32 i = 0; i < 6; ++i) { Curves[i]->KeyModifyBegin(); } FTransform BoneTransform = SkeletalMeshComponent->LocalAtoms[BoneIndex]; FbxVector4 Translation = Converter.ConvertToFbxPos(BoneTransform.GetLocation()); FbxVector4 Rotation = Converter.ConvertToFbxRot(BoneTransform.GetRotation().Euler()); int32 lKeyIndex; for(int32 i = 0, j=3; i < 3; ++i, ++j) { lKeyIndex = Curves[i]->KeyAdd(ExportTime); Curves[i]->KeySetValue(lKeyIndex, Translation[i]); Curves[i]->KeySetInterpolation(lKeyIndex, FbxAnimCurveDef::eInterpolationCubic); lKeyIndex = Curves[j]->KeyAdd(ExportTime); Curves[j]->KeySetValue(lKeyIndex, Rotation[i]); Curves[j]->KeySetInterpolation(lKeyIndex, FbxAnimCurveDef::eInterpolationCubic); } for(int32 i = 0; i < 6; ++i) { Curves[i]->KeyModifyEnd(); } } } CorrectAnimTrackInterpolation(BoneNodes, AnimLayer); }