From 536eef87373b48bc006d24eb34ae56d229fd585a Mon Sep 17 00:00:00 2001 From: David-Vodhanel <39318287+David-Vodhanel@users.noreply.github.com> Date: Sun, 26 Sep 2021 16:55:21 -0700 Subject: [PATCH] Adding AutoJCM feature https://davidvodhanel.com/daz-to-unreal-autojcm/ --- Unreal/DazStudioPlugin/DzUnrealAction.cpp | 37 +- .../DzUnrealMorphSelectionDialog.cpp | 189 ++++++++++ .../DzUnrealMorphSelectionDialog.h | 28 ++ .../DazToUnreal/DazToUnreal.uplugin | 14 +- .../Source/DazToUnreal/DazToUnreal.Build.cs | 3 + .../DazToUnreal/Private/DazToUnreal.cpp | 16 + .../DazToUnreal/Private/DazToUnrealMorphs.cpp | 169 +++++++++ .../DazToUnreal/Public/DazToUnrealMorphs.h | 20 ++ .../DazJointControlledMorphAnimInstance.cpp | 324 ++++++++++++++++++ .../DazJointControlledMorphAnimInstance.h | 131 +++++++ 10 files changed, 922 insertions(+), 9 deletions(-) create mode 100644 Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealMorphs.cpp create mode 100644 Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealMorphs.h create mode 100644 Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/DazJointControlledMorphAnimInstance.cpp create mode 100644 Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/DazJointControlledMorphAnimInstance.h diff --git a/Unreal/DazStudioPlugin/DzUnrealAction.cpp b/Unreal/DazStudioPlugin/DzUnrealAction.cpp index 5a6a3d2..0208b42 100644 --- a/Unreal/DazStudioPlugin/DzUnrealAction.cpp +++ b/Unreal/DazStudioPlugin/DzUnrealAction.cpp @@ -24,6 +24,7 @@ #include "DzUnrealDialog.h" #include "DzUnrealAction.h" +#include "DzUnrealMorphSelectionDialog.h" DzUnrealAction::DzUnrealAction() : DzRuntimePluginAction(tr("&Daz to Unreal"), tr("Send the selected node to Unreal.")) @@ -152,10 +153,42 @@ void DzUnrealAction::WriteConfiguration() writer.finishObject(); } } - writer.finishArray(); - + if (ExportMorphs) + { + DzMainWindow* mw = dzApp->getInterface(); + DzUnrealMorphSelectionDialog* morphDialog = DzUnrealMorphSelectionDialog::Get(mw); + if (morphDialog->IsAutoJCMEnabled()) + { + writer.startMemberArray("JointLinks", true); + QList JointLinks = morphDialog->GetActiveJointControlledMorphs(Selection); + foreach(JointLinkInfo linkInfo, JointLinks) + { + writer.startObject(true); + writer.addMember("Bone", linkInfo.Bone); + writer.addMember("Axis", linkInfo.Axis); + writer.addMember("Morph", linkInfo.Morph); + writer.addMember("Scalar", linkInfo.Scalar); + writer.addMember("Alpha", linkInfo.Alpha); + if (linkInfo.Keys.count() > 0) + { + writer.startMemberArray("Keys", true); + foreach(JointLinkKey key, linkInfo.Keys) + { + writer.startObject(true); + writer.addMember("Angle", key.Angle); + writer.addMember("Value", key.Value); + writer.finishObject(); + } + writer.finishArray(); + } + writer.finishObject(); + } + writer.finishArray(); + } + } + writer.startMemberArray("Subdivisions", true); if (ExportSubdivisions) SubdivisionDialog->WriteSubdivisions(writer); diff --git a/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.cpp b/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.cpp index 6145152..f41177f 100644 --- a/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.cpp +++ b/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.cpp @@ -33,6 +33,10 @@ #include "dzproperty.h" #include "dzsettings.h" #include "dzmorph.h" +#include "dzcontroller.h" +#include "dznumericnodeproperty.h" +#include "dzerclink.h" +#include "dzbone.h" #include "DzUnrealMorphSelectionDialog.h" #include "QtGui/qlayout.h" @@ -65,6 +69,8 @@ class SortingListItem : public QListWidgetItem { DzUnrealMorphSelectionDialog::DzUnrealMorphSelectionDialog(QWidget *parent) : DzBasicDialog(parent, DAZ_TO_UNREAL_PLUGIN_NAME) { + settings = new QSettings("Daz 3D", "DazToUnreal"); + morphListWidget = NULL; morphExportListWidget = NULL; morphTreeWidget = NULL; @@ -132,6 +138,7 @@ DzUnrealMorphSelectionDialog::DzUnrealMorphSelectionDialog(QWidget *parent) : QPushButton* TorsoJCMButton = new QPushButton("Torso"); QPushButton* ARKit81Button = new QPushButton("ARKit (Genesis8.1)"); QPushButton* FaceFX8Button = new QPushButton("FaceFX (Genesis8)"); + autoJCMCheckBox = new QCheckBox("Auto JCM"); ((QGridLayout*)JCMGroupBox->layout())->addWidget(ArmsJCMButton, 0, 0); ((QGridLayout*)JCMGroupBox->layout())->addWidget(LegsJCMButton, 0, 1); ((QGridLayout*)JCMGroupBox->layout())->addWidget(TorsoJCMButton, 0, 2); @@ -139,12 +146,19 @@ DzUnrealMorphSelectionDialog::DzUnrealMorphSelectionDialog(QWidget *parent) : ((QGridLayout*)FaceGroupBox->layout())->addWidget(FaceFX8Button, 0, 2); MorphGroupBox->layout()->addWidget(JCMGroupBox); MorphGroupBox->layout()->addWidget(FaceGroupBox); + MorphGroupBox->layout()->addWidget(autoJCMCheckBox); + + if (!settings->value("AutoJCMEnabled").isNull()) + { + autoJCMCheckBox->setChecked(settings->value("AutoJCMEnabled").toBool()); + } connect(ArmsJCMButton, SIGNAL(released()), this, SLOT(HandleArmJCMMorphsButton())); connect(LegsJCMButton, SIGNAL(released()), this, SLOT(HandleLegJCMMorphsButton())); connect(TorsoJCMButton, SIGNAL(released()), this, SLOT(HandleTorsoJCMMorphsButton())); connect(ARKit81Button, SIGNAL(released()), this, SLOT(HandleARKitGenesis81MorphsButton())); connect(FaceFX8Button, SIGNAL(released()), this, SLOT(HandleFaceFXGenesis8Button())); + connect(autoJCMCheckBox, SIGNAL(clicked(bool)), this, SLOT(HandleAutoJCMCheckBoxChange(bool))); treeLayout->addWidget(MorphGroupBox); morphsLayout->addLayout(treeLayout); @@ -223,6 +237,9 @@ void DzUnrealMorphSelectionDialog::PrepareDialog() DzNode* ChildNode = Selection->getNodeChild(ChildIndex); morphList.append(GetAvailableMorphs(ChildNode)); } + + //GetActiveJointControlledMorphs(Selection); + UpdateMorphsTree(); HandlePresetChanged("LastUsed.csv"); } @@ -337,6 +354,170 @@ QStringList DzUnrealMorphSelectionDialog::GetAvailableMorphs(DzNode* Node) return newMorphList; } +// Recursive function for finding all active JCM morphs for a node +QList DzUnrealMorphSelectionDialog::GetActiveJointControlledMorphs(DzNode* Node) +{ + QList returnMorphs; + if (autoJCMCheckBox->isChecked()) + { + if (Node == nullptr) + { + Node = dzScene->getPrimarySelection(); + + // For items like clothing, create the morph list from the character + DzNode* ParentFigureNode = Node; + while (ParentFigureNode->getNodeParent()) + { + ParentFigureNode = ParentFigureNode->getNodeParent(); + if (DzSkeleton* Skeleton = ParentFigureNode->getSkeleton()) + { + if (DzFigure* Figure = qobject_cast(Skeleton)) + { + Node = ParentFigureNode; + break; + } + } + } + } + + + DzObject* Object = Node->getObject(); + DzShape* Shape = Object ? Object->getCurrentShape() : NULL; + + for (int index = 0; index < Node->getNumProperties(); index++) + { + DzProperty* property = Node->getProperty(index); + returnMorphs.append(GetJointControlledMorphInfo(property)); + } + + if (Object) + { + for (int index = 0; index < Object->getNumModifiers(); index++) + { + DzModifier* modifier = Object->getModifier(index); + QString modName = modifier->getName(); + QString modLabel = modifier->getLabel(); + DzMorph* mod = qobject_cast(modifier); + if (mod) + { + for (int propindex = 0; propindex < modifier->getNumProperties(); propindex++) + { + DzProperty* property = modifier->getProperty(propindex); + returnMorphs.append(GetJointControlledMorphInfo(property)); + } + + } + + } + } + + } + + return returnMorphs; +} + +QList DzUnrealMorphSelectionDialog::GetJointControlledMorphInfo(DzProperty* property) +{ + QList returnMorphs; + + QString propName = property->getName(); + QString propLabel = property->getLabel(); + DzPresentation* presentation = property->getPresentation(); + if (presentation && presentation->getType() == "Modifier/Corrective") + { + QString linkLabel; + QString linkDescription; + QString linkBone; + QString linkAxis; + QString linkBodyType; + double bodyStrength = 0.0f; + double currentBodyScalar = 0.0f; + double linkScalar = 0.0f; + bool isJCM = false; + QList keys; + QList keysValues; + QList linkKeys; + + for (int ControllerIndex = 0; ControllerIndex < property->getNumControllers(); ControllerIndex++) + { + DzController* controller = property->getController(ControllerIndex); + + DzERCLink* link = qobject_cast(controller); + if (link) + { + double value = link->getScalar(); + QString linkProperty = link->getProperty()->getName(); + QString linkObject = link->getProperty()->getOwner()->getName(); + double currentValue = link->getProperty()->getDoubleValue(); + + DzBone* bone = qobject_cast(link->getProperty()->getOwner()); + if (bone) + { + linkLabel = propLabel; + linkDescription = controller->description(); + linkBone = linkObject; + linkAxis = linkProperty; + linkScalar = value; + isJCM = true; + + if (link->getType() == 6) + { + for (int keyIndex = 0; keyIndex < link->getNumKeyValues(); keyIndex++) + { + JointLinkKey newKey; + newKey.Angle = link->getKey(keyIndex); + newKey.Value = link->getKeyValue(keyIndex); + linkKeys.append(newKey); + keys.append(link->getKey(keyIndex)); + keysValues.append(link->getKeyValue(keyIndex)); + } + } + } + else + { + linkBodyType = linkObject; + bodyStrength = value; + currentBodyScalar = currentValue; + } + } + } + + if (isJCM && currentBodyScalar > 0.0f) + { + JointLinkInfo linkInfo; + linkInfo.Bone = linkBone; + linkInfo.Axis = linkAxis; + linkInfo.Morph = linkLabel; + linkInfo.Scalar = linkScalar; + linkInfo.Alpha = currentBodyScalar; + linkInfo.Keys = linkKeys; + qDebug() << "Label " << linkLabel << " Description " << linkDescription << " Bone " << linkBone << " Axis " << linkAxis << " Alpha " << currentBodyScalar << " Scalar " << linkScalar; + if (!keys.isEmpty()) + { + foreach(double key, keys) + { + qDebug() << key; + } + + foreach(double key, keysValues) + { + qDebug() << key; + } + + } + + if (morphs.contains(linkLabel) && !morphsToExport.contains(morphs[linkLabel])) + { + morphsToExport.append(morphs[linkLabel]); + } + + returnMorphs.append(linkInfo); + + } + } + return returnMorphs; +} + // Build out the left tree void DzUnrealMorphSelectionDialog::UpdateMorphsTree() { @@ -718,6 +899,11 @@ void DzUnrealMorphSelectionDialog::HandleFaceFXGenesis8Button() RefreshExportMorphList(); } +void DzUnrealMorphSelectionDialog::HandleAutoJCMCheckBoxChange(bool checked) +{ + settings->setValue("AutoJCMEnabled", checked); +} + // Refresh the Right export list void DzUnrealMorphSelectionDialog::RefreshExportMorphList() { @@ -780,12 +966,15 @@ void DzUnrealMorphSelectionDialog::HandlePresetChanged(const QString& presetName } RefreshExportMorphList(); + GetActiveJointControlledMorphs(); file.close(); } // Get the morph string in the format for the Daz FBX Export QString DzUnrealMorphSelectionDialog::GetMorphString() { + GetActiveJointControlledMorphs(); + if (morphsToExport.length() == 0) { return ""; diff --git a/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.h b/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.h index 11aeb1e..c851c93 100644 --- a/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.h +++ b/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.h @@ -11,6 +11,7 @@ class QTreeWidget; class QTreeWidgetItem; class QLineEdit; class QComboBox; +class QCheckBox; struct MorphInfo { QString Name; @@ -36,6 +37,22 @@ struct MorphInfo { } }; +struct JointLinkKey +{ + int Angle; + int Value; +}; + +struct JointLinkInfo +{ + QString Bone; + QString Axis; + QString Morph; + double Scalar; + double Alpha; + QList Keys; +}; + class DzUnrealMorphSelectionDialog : public DzBasicDialog { Q_OBJECT public: @@ -67,6 +84,10 @@ class DzUnrealMorphSelectionDialog : public DzBasicDialog { // Used to rename them to the friendly name in Unreal QMap GetMorphRenaming(); + bool IsAutoJCMEnabled() { return autoJCMCheckBox->isChecked(); } + + // Recursive function for finding all active JCM morphs for a node + QList GetActiveJointControlledMorphs(DzNode* Node = nullptr); public slots: void FilterChanged(const QString& filter); @@ -80,6 +101,7 @@ public slots: void HandleTorsoJCMMorphsButton(); void HandleARKitGenesis81MorphsButton(); void HandleFaceFXGenesis8Button(); + void HandleAutoJCMCheckBoxChange(bool checked); private: @@ -89,6 +111,9 @@ public slots: // Recursive function for finding all morphs for a node QStringList GetAvailableMorphs(DzNode* Node); + // + QList GetJointControlledMorphInfo(DzProperty* property); + void UpdateMorphsTree(); // Returns the tree node for the morph name (with path) @@ -141,4 +166,7 @@ public slots: QTreeWidgetItem* fullBodyMorphTreeItem; QTreeWidgetItem* charactersTreeItem; + QCheckBox* autoJCMCheckBox; + + QSettings* settings; }; diff --git a/Unreal/UnrealPlugin/DazToUnreal/DazToUnreal.uplugin b/Unreal/UnrealPlugin/DazToUnreal/DazToUnreal.uplugin index fcc4d6d..e455d92 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/DazToUnreal.uplugin +++ b/Unreal/UnrealPlugin/DazToUnreal/DazToUnreal.uplugin @@ -4,15 +4,15 @@ "VersionName": "4.2.0", "FriendlyName": "DazToUnreal", "Description": "", - "Category": "Importers", - "CreatedBy": "Daz 3D", - "CreatedByURL": "http://www.daz3d.com/daz-to-unreal-bridge", - "DocsURL": "http://docs.daz3d.com/doku.php/public/read_me/index/72003/start", - "MarketplaceURL": "", - "SupportURL": "https://www.daz3d.com/help/", + "Category": "Other", + "CreatedBy": "David Vodhanel", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/070a312bc04c48bcb1a4d193aaddf73e", + "SupportURL": "", + "EnabledByDefault": true, "CanContainContent": true, "IsBetaVersion": false, - "IsExperimentalVersion": false, "Installed": true, "Modules": [ { diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/DazToUnreal.Build.cs b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/DazToUnreal.Build.cs index ee6e50c..17aee27 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/DazToUnreal.Build.cs +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/DazToUnreal.Build.cs @@ -24,6 +24,8 @@ public DazToUnreal(ReadOnlyTargetRules Target) : base(Target) PrivateDependencyModuleNames.AddRange( new string[] { + "AnimGraph", + "BlueprintGraph", "Projects", "InputCore", "UnrealEd", @@ -33,6 +35,7 @@ public DazToUnreal(ReadOnlyTargetRules Target) : base(Target) "Slate", "SlateCore", "EditorScriptingUtilities", + "DazToUnrealRuntime", // ... add private dependencies that you statically link with here ... } ); diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnreal.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnreal.cpp index 9767cf8..623865b 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnreal.cpp +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnreal.cpp @@ -8,6 +8,8 @@ #include "DazToUnrealEnvironment.h" #include "DazToUnrealPoses.h" #include "DazToUnrealSubdivision.h" +#include "DazToUnrealMorphs.h" +#include "DazJointControlledMorphAnimInstance.h" #include "LevelEditor.h" #include "Widgets/Docking/SDockTab.h" @@ -1117,6 +1119,20 @@ UObject* FDazToUnrealModule::ImportFromDaz(TSharedPtr JsonObject) } } + // Create and attach the Joint Control Anim + if (AssetType == DazAssetType::SkeletalMesh) + { + if (USkeletalMesh* SkeletalMesh = Cast(NewObject)) + { + if (UDazJointControlledMorphAnimInstance* JointControlAnim = FDazToUnrealMorphs::CreateJointControlAnimation(JsonObject, CharacterFolder, AssetName, SkeletalMesh->Skeleton)) + { + //JointControlAnim->CurrentSkeleton = SkeletalMesh->Skeleton; + SkeletalMesh->PostProcessAnimBlueprint = JointControlAnim->GetClass(); + } + } + + } + return NewObject; } diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealMorphs.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealMorphs.cpp new file mode 100644 index 0000000..0da0412 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealMorphs.cpp @@ -0,0 +1,169 @@ +#include "DazToUnrealMorphs.h" +#include "DazJointControlledMorphAnimInstance.h" +#include "Serialization/JsonReader.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonSerializer.h" +#include "AssetRegistryModule.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Animation/AnimBlueprint.h" +#include "Animation/AnimBlueprintGeneratedClass.h" +#include "EdGraphSchema_K2_Actions.h" +#include "AnimGraphNode_LinkedInputPose.h" +#include "Kismet2/BlueprintEditorUtils.h" + +UDazJointControlledMorphAnimInstance* FDazToUnrealMorphs::CreateJointControlAnimation(TSharedPtr JsonObject, FString Folder, FString CharacterName, USkeleton* Skeleton) +{ + // Only only create the JCM Anim if the JointLinks object exists + const TArray>* JointLinkList;// = JsonObject->GetArrayField(TEXT("JointLinks")); + if (JsonObject->TryGetArrayField(TEXT("JointLinks"), JointLinkList)) + { + const FString AssetName = CharacterName + TEXT("_JCMAnim"); + const FString PackageName = FPackageName::ObjectPathToPackageName(Folder) / AssetName; + const FString ObjectName = FPackageName::ObjectPathToObjectName(PackageName); + + // Create the new blueprint next to the SkeletalMesh and get the AnimInstance from it. + UPackage* NewPackage = CreatePackage(*PackageName); + UAnimBlueprint* AnimBlueprint = CreateBlueprint(NewPackage, FName(AssetName), Skeleton); + UDazJointControlledMorphAnimInstance* AnimInstance = Cast(AnimBlueprint->GetAnimBlueprintGeneratedClass()->ClassDefaultObject); + + // Get the joint link info for each link + for (int i = 0; i < JointLinkList->Num(); i++) + { + FDazJointControlLink NewJointLink; + const TSharedPtr JointLinkValue = (*JointLinkList)[i]; + TSharedPtr JointLink = JointLinkValue->AsObject(); + NewJointLink.BoneName = FName(JointLink->GetStringField(TEXT("Bone"))); + + // Unreal ends up with different axis + FString DazAxis = JointLink->GetStringField(TEXT("Axis")); + if (DazAxis == TEXT("XRotate")) + { + NewJointLink.PrimaryAxis = EDazMorphAnimInstanceDriver::RotationY; + } + if (DazAxis == TEXT("YRotate")) + { + NewJointLink.PrimaryAxis = EDazMorphAnimInstanceDriver::RotationZ; + } + if (DazAxis == TEXT("ZRotate")) + { + NewJointLink.PrimaryAxis = EDazMorphAnimInstanceDriver::RotationX; + } + + NewJointLink.MorphName = FName(JointLink->GetStringField(TEXT("Morph"))); + NewJointLink.Scalar = JointLink->GetNumberField(TEXT("Scalar")); + + // These two are flipped when we get in Unreal + if (NewJointLink.PrimaryAxis == EDazMorphAnimInstanceDriver::RotationY) + { + NewJointLink.Scalar = NewJointLink.Scalar * -1.0f; + } + if (NewJointLink.PrimaryAxis == EDazMorphAnimInstanceDriver::RotationZ) + { + NewJointLink.Scalar = NewJointLink.Scalar * -1.0f; + } + NewJointLink.Alpha = JointLink->GetNumberField(TEXT("Alpha")); + + // Get the Keys if they exist + const TArray>* JointLinkKeys; + if (JointLink->TryGetArrayField(TEXT("Keys"), JointLinkKeys)) + { + for (int KeyIndex = 0; KeyIndex < JointLinkKeys->Num(); KeyIndex++) + { + FDazJointControlLinkKey NewJointLinkKey; + const TSharedPtr JointLinkKeyValue = (*JointLinkKeys)[KeyIndex]; + TSharedPtr JointLinkKey = JointLinkKeyValue->AsObject(); + NewJointLinkKey.BoneRotation = JointLinkKey->GetNumberField(TEXT("Angle")); + if (NewJointLink.PrimaryAxis == EDazMorphAnimInstanceDriver::RotationY) + { + NewJointLinkKey.BoneRotation = NewJointLinkKey.BoneRotation * -1.0f; + } + if (NewJointLink.PrimaryAxis == EDazMorphAnimInstanceDriver::RotationZ) + { + NewJointLinkKey.BoneRotation = NewJointLinkKey.BoneRotation * -1.0f; + } + NewJointLinkKey.MorphAlpha = JointLinkKey->GetNumberField(TEXT("Value")); + NewJointLink.Keys.Add(NewJointLinkKey); + } + } + + AnimInstance->ControlLinks.Add(NewJointLink); + } + + // Setup Secondary Axis. We need to know the primary and secondary axis + // because if we check the rotation in the wrong order the morphs can pop. + for (FDazJointControlLink& PrimaryAxisLink : AnimInstance->ControlLinks) + { + for (FDazJointControlLink& SecondaryAxisLink : AnimInstance->ControlLinks) + { + if (PrimaryAxisLink.BoneName == SecondaryAxisLink.BoneName && + PrimaryAxisLink.PrimaryAxis != SecondaryAxisLink.PrimaryAxis) + { + PrimaryAxisLink.SecondaryAxis = SecondaryAxisLink.PrimaryAxis; + SecondaryAxisLink.SecondaryAxis = PrimaryAxisLink.PrimaryAxis; + break; + } + } + } + + // Recompile the AnimBlueprint and return the updated AnimInstance + FBlueprintEditorUtils::MarkBlueprintAsModified(AnimBlueprint); + FKismetEditorUtilities::CompileBlueprint(AnimBlueprint); + AnimInstance = Cast(AnimBlueprint->GetAnimBlueprintGeneratedClass()->ClassDefaultObject); + return AnimInstance; + } + return nullptr; +} + +// This is a stripped down version of UAnimBlueprintFactory::FactoryCreateNew +UAnimBlueprint* FDazToUnrealMorphs::CreateBlueprint(UObject* InParent, FName Name, USkeleton* Skeleton) +{ + // Create the Blueprint + UClass* ClassToUse = UDazJointControlledMorphAnimInstance::StaticClass(); + UAnimBlueprint* NewBP = CastChecked(FKismetEditorUtilities::CreateBlueprint(ClassToUse, InParent, Name, BPTYPE_Normal, UAnimBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), NAME_None)); + + NewBP->TargetSkeleton = Skeleton; + + // Because the BP itself didn't have the skeleton set when the initial compile occured, it's not set on the generated classes either + if (UAnimBlueprintGeneratedClass* TypedNewClass = Cast(NewBP->GeneratedClass)) + { + TypedNewClass->TargetSkeleton = Skeleton; + } + if (UAnimBlueprintGeneratedClass* TypedNewClass_SKEL = Cast(NewBP->SkeletonGeneratedClass)) + { + TypedNewClass_SKEL->TargetSkeleton = Skeleton; + } + + // Need to add the Pose Input Node and connect it to the Output Node in the AnimGraph + TArray Graphs; + NewBP->GetAllGraphs(Graphs); + for (UEdGraph* Graph : Graphs) + { + if (Graph->GetFName() == UEdGraphSchema_K2::GN_AnimGraph) + { + UEdGraphNode* OutNode = nullptr; + for (UEdGraphNode* Node : Graph->Nodes) + { + OutNode = Node; + } + // If the Blueprint isn't open in an Editor Window adding the UAnimGraphNode_LinkedInputPose node will crash. + GEditor->GetEditorSubsystem()->OpenEditorForAsset(NewBP); + UAnimGraphNode_LinkedInputPose* const InputTemplate = NewObject(); + UEdGraphNode* const NewNode = FEdGraphSchemaAction_NewNode::SpawnNodeFromTemplate(Graph, InputTemplate, FVector2D(0.0f, 0.0f), false); + if (NewNode && OutNode) + { + UEdGraphPin* NextPin = NewNode->FindPin(TEXT("Pose")); + UEdGraphPin* OutPin = OutNode->FindPin(TEXT("Result")); + + NextPin->MakeLinkTo(OutPin); + } + + // Don't need the editor open anymore, so close it now. + // Note: Turns out I can't close it because part of the action happens on a tick + //GEditor->GetEditorSubsystem()->CloseAllEditorsForAsset(NewBP); + } + } + + return NewBP; + + +} \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealMorphs.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealMorphs.h new file mode 100644 index 0000000..a4be935 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealMorphs.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CoreMinimal.h" +#include "DazToUnrealEnums.h" +#include "Dom/JsonObject.h" + +class UDazJointControlledMorphAnimInstance; +class USkeleton; + +class FDazToUnrealMorphs +{ +public: + // Called to create the JCM AnimInstance + static UDazJointControlledMorphAnimInstance* CreateJointControlAnimation(TSharedPtr JsonObject, FString Folder, FString CharacterName, USkeleton* Skeleton); + +private: + + // Internal function for creating the AnimBlueprint for the AnimInstance + static UAnimBlueprint* CreateBlueprint(UObject* InParent, FName Name, USkeleton* Skeleton); +}; \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/DazJointControlledMorphAnimInstance.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/DazJointControlledMorphAnimInstance.cpp new file mode 100644 index 0000000..57313d9 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/DazJointControlledMorphAnimInstance.cpp @@ -0,0 +1,324 @@ +#include "DazJointControlledMorphAnimInstance.h" +#include "Animation/AnimInstanceProxy.h" +#include "Kismet/KismetMathLibrary.h" + +void UDazJointControlledMorphAnimInstance::NativeInitializeAnimation() +{ + Super::NativeInitializeAnimation(); + +} +void UDazJointControlledMorphAnimInstance::NativeUpdateAnimation(float DeltaTime) +{ + Super::NativeUpdateAnimation(DeltaTime); + + +} + +FAnimInstanceProxy* UDazJointControlledMorphAnimInstance::CreateAnimInstanceProxy() +{ + return new FDazJointControlledMorphAnimInstanceProxy(this); +} + +void FDazJointControlledMorphAnimInstanceProxy::Initialize(UAnimInstance* InAnimInstance) +{ + FAnimInstanceProxy::Initialize(InAnimInstance); + if (UDazJointControlledMorphAnimInstance* Instance = Cast(GetAnimInstanceObject())) + { + ControlLinks = Instance->ControlLinks; + } +} + +void FDazJointControlledMorphAnimInstanceProxy::PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) +{ + Super::PreUpdate(InAnimInstance, DeltaSeconds); + + FAnimInstanceProxy::Initialize(InAnimInstance); + if (UDazJointControlledMorphAnimInstance* Instance = Cast(GetAnimInstanceObject())) + { + if (ControlLinks.Num() != Instance->ControlLinks.Num()) + { + ControlLinks = Instance->ControlLinks; + } + } +} + +bool FDazJointControlledMorphAnimInstanceProxy::Evaluate(FPoseContext& Output) +{ + EvaluateAnimationNode(Output); + for (FDazJointControlLink Link : ControlLinks) + { + ProcessLink(Output, Link, Link.PrimaryAxis, Link.SecondaryAxis); + } + + return true; +} + +void FDazJointControlledMorphAnimInstanceProxy::ProcessLink(FPoseContext& Output, FDazJointControlLink Link, EDazMorphAnimInstanceDriver Driver, EDazMorphAnimInstanceDriver SecondaryDriver) +{ + // Get the Local space transform and the ref pose transform to see how the transform for the source bone has changed + const FBoneContainer& BoneContainer = Output.Pose.GetBoneContainer(); + int32 BoneIndex = BoneContainer.GetPoseBoneIndexForBoneName(Link.BoneName); + const FTransform& SourceRefPoseBoneTransform = BoneContainer.GetRefPoseArray()[BoneIndex]; + FCompactPoseBoneIndex CompactBoneIndex = BoneContainer.GetCompactPoseIndexFromSkeletonIndex(BoneIndex); + //Output.Pose. + const FTransform SourceCurrentBoneTransform = Output.Pose[CompactBoneIndex];// .GetLocalSpaceTransform(SourceBone.GetCompactPoseIndex(BoneContainer)); + + float FinalDriverValue = 0.0f; + float BoneBendAngle = ExtractSourceValue(SourceCurrentBoneTransform, SourceRefPoseBoneTransform, Driver, SecondaryDriver); + if (Link.Keys.Num() == 0) + { + FinalDriverValue = BoneBendAngle * Link.Scalar; + FinalDriverValue = FMath::Clamp(FinalDriverValue, 0.0f, 1.0f); + } + else + { + if (Link.Keys.Num() > 1) + { + float FirstAngle = Link.Keys[0].BoneRotation; + float LastAngle = Link.Keys[Link.Keys.Num() - 1].BoneRotation; + float FirstValue = Link.Keys[0].MorphAlpha; + float LastValue = Link.Keys[Link.Keys.Num() - 1].MorphAlpha; + FinalDriverValue = UKismetMathLibrary::MapRangeClamped(BoneBendAngle, FirstAngle, LastAngle, FirstValue, LastValue); + + for (int32 KeyIndex = 1; KeyIndex < Link.Keys.Num(); KeyIndex++) + { + float StartAngle = Link.Keys[KeyIndex - 1].BoneRotation; + float StartValue = Link.Keys[KeyIndex - 1].MorphAlpha; + float StopAngle = Link.Keys[KeyIndex].BoneRotation; + float StopValue = Link.Keys[KeyIndex].MorphAlpha; + float AngleRange = FMath::Abs(StopValue - StartAngle); + + + if (StartAngle < BoneBendAngle && BoneBendAngle < StopAngle) + { + FinalDriverValue = UKismetMathLibrary::MapRangeClamped(BoneBendAngle, StartAngle, StopAngle, StartValue, StopValue); + //FinalDriverValue = FMath::Lerp(StartValue, StopValue, (BoneBendAngle - StartAngle) / AngleRange); + } + if (StartAngle > BoneBendAngle && BoneBendAngle > StopAngle) + { + FinalDriverValue = UKismetMathLibrary::MapRangeClamped(BoneBendAngle, StartAngle, StopAngle, StartValue, StopValue); + //FinalDriverValue = FMath::Lerp(StartValue, StopValue, (BoneBendAngle - StartAngle) / AngleRange); + } + } + } + } + + USkeleton* ProxySkeleton = Output.AnimInstanceProxy->GetSkeleton(); + SmartName::UID_Type NameUID = ProxySkeleton->GetUIDByName(USkeleton::AnimCurveMappingName, FName(Link.MorphName)); + if (NameUID != SmartName::MaxUID) + { + Output.Curve.Set(NameUID, FinalDriverValue); + } +} + + +FDazJointControlledMorphAnimInstanceProxy::~FDazJointControlledMorphAnimInstanceProxy() +{ +} + +const float FDazJointControlledMorphAnimInstanceProxy::ExtractSourceValue(const FTransform& InCurrentBoneTransform, const FTransform& InRefPoseBoneTransform, EDazMorphAnimInstanceDriver Controller, EDazMorphAnimInstanceDriver SecondaryController) +{ + // Resolve source value + float SourceValue = 0.0f; + + + EDazMorphAnimInstanceRotationOrder RotationOrder = EDazMorphAnimInstanceRotationOrder::XYZ; + + if (Controller == EDazMorphAnimInstanceDriver::RotationX) + { + RotationOrder = EDazMorphAnimInstanceRotationOrder::YZX; + if (SecondaryController == EDazMorphAnimInstanceDriver::RotationY) + { + RotationOrder = EDazMorphAnimInstanceRotationOrder::YZX; + } + if (SecondaryController == EDazMorphAnimInstanceDriver::RotationZ) + { + RotationOrder = EDazMorphAnimInstanceRotationOrder::ZYX; + } + } + if (Controller == EDazMorphAnimInstanceDriver::RotationY) + { + RotationOrder = EDazMorphAnimInstanceRotationOrder::XZY; + if (SecondaryController == EDazMorphAnimInstanceDriver::RotationX) + { + RotationOrder = EDazMorphAnimInstanceRotationOrder::XZY; + } + if (SecondaryController == EDazMorphAnimInstanceDriver::RotationZ) + { + RotationOrder = EDazMorphAnimInstanceRotationOrder::ZXY; + } + } + if (Controller == EDazMorphAnimInstanceDriver::RotationZ) + { + RotationOrder = EDazMorphAnimInstanceRotationOrder::YXZ; + if (SecondaryController == EDazMorphAnimInstanceDriver::RotationX) + { + RotationOrder = EDazMorphAnimInstanceRotationOrder::XYZ; + } + if (SecondaryController == EDazMorphAnimInstanceDriver::RotationY) + { + RotationOrder = EDazMorphAnimInstanceRotationOrder::YXZ; + } + } + + + const FVector RotationDiff = EulerFromQuat((InCurrentBoneTransform * InRefPoseBoneTransform.Inverse()).GetRotation(), RotationOrder); + SourceValue = RotationDiff[(int32)Controller - 1]; + if (Controller != EDazMorphAnimInstanceDriver::RotationZ) + { + SourceValue = SourceValue * -1.0f; + } + + + // Determine the resulting value + float FinalDriverValue = SourceValue; + /*if (DrivingCurve != nullptr) + { + // Remap thru the curve if set + FinalDriverValue = DrivingCurve->GetFloatValue(FinalDriverValue); + } + else + { + // Apply the fixed function remapping/clamping + if (bUseRange) + { + const float ClampedAlpha = FMath::Clamp(FMath::GetRangePct(RangeMin, RangeMax, FinalDriverValue), 0.0f, 1.0f); + FinalDriverValue = FMath::Lerp(RemappedMin, RemappedMax, ClampedAlpha); + } + + FinalDriverValue *= Multiplier; + }*/ + + return FinalDriverValue; +} + +// Copied from FControlRigMathLibrary::EulerFromQuat which is in an experimental plugin +FVector FDazJointControlledMorphAnimInstanceProxy::EulerFromQuat(const FQuat& Rotation, EDazMorphAnimInstanceRotationOrder RotationOrder) +{ + float X = Rotation.X; + float Y = Rotation.Y; + float Z = Rotation.Z; + float W = Rotation.W; + float X2 = X * 2.f; + float Y2 = Y * 2.f; + float Z2 = Z * 2.f; + float XX2 = X * X2; + float XY2 = X * Y2; + float XZ2 = X * Z2; + float YX2 = Y * X2; + float YY2 = Y * Y2; + float YZ2 = Y * Z2; + float ZX2 = Z * X2; + float ZY2 = Z * Y2; + float ZZ2 = Z * Z2; + float WX2 = W * X2; + float WY2 = W * Y2; + float WZ2 = W * Z2; + + FVector AxisX, AxisY, AxisZ; + AxisX.X = (1.f - (YY2 + ZZ2)); + AxisY.X = (XY2 + WZ2); + AxisZ.X = (XZ2 - WY2); + AxisX.Y = (XY2 - WZ2); + AxisY.Y = (1.f - (XX2 + ZZ2)); + AxisZ.Y = (YZ2 + WX2); + AxisX.Z = (XZ2 + WY2); + AxisY.Z = (YZ2 - WX2); + AxisZ.Z = (1.f - (XX2 + YY2)); + + FVector Result = FVector::ZeroVector; + + if (RotationOrder == EDazMorphAnimInstanceRotationOrder::XYZ) + { + Result.Y = FMath::Asin(-FMath::Clamp(AxisZ.X, -1.f, 1.f)); + + if (FMath::Abs(AxisZ.X) < 1.f - SMALL_NUMBER) + { + Result.X = FMath::Atan2(AxisZ.Y, AxisZ.Z); + Result.Z = FMath::Atan2(AxisY.X, AxisX.X); + } + else + { + Result.X = 0.f; + Result.Z = FMath::Atan2(-AxisX.Y, AxisY.Y); + } + } + else if (RotationOrder == EDazMorphAnimInstanceRotationOrder::XZY) + { + + Result.Z = FMath::Asin(FMath::Clamp(AxisY.X, -1.f, 1.f)); + + if (FMath::Abs(AxisY.X) < 1.f - SMALL_NUMBER) + { + Result.X = FMath::Atan2(-AxisY.Z, AxisY.Y); + Result.Y = FMath::Atan2(-AxisZ.X, AxisX.X); + } + else + { + Result.X = 0.f; + Result.Y = FMath::Atan2(AxisX.Z, AxisZ.Z); + } + } + else if (RotationOrder == EDazMorphAnimInstanceRotationOrder::YXZ) + { + Result.X = FMath::Asin(FMath::Clamp(AxisZ.Y, -1.f, 1.f)); + + if (FMath::Abs(AxisZ.Y) < 1.f - SMALL_NUMBER) + { + Result.Y = FMath::Atan2(-AxisZ.X, AxisZ.Z); + Result.Z = FMath::Atan2(-AxisX.Y, AxisY.Y); + } + else + { + Result.Y = 0.f; + Result.Z = FMath::Atan2(AxisY.X, AxisX.X); + } + } + else if (RotationOrder == EDazMorphAnimInstanceRotationOrder::YZX) + { + Result.Z = FMath::Asin(-FMath::Clamp(AxisX.Y, -1.f, 1.f)); + + if (FMath::Abs(AxisX.Y) < 1.f - SMALL_NUMBER) + { + Result.X = FMath::Atan2(AxisZ.Y, AxisY.Y); + Result.Y = FMath::Atan2(AxisX.Z, AxisX.X); + } + else + { + Result.X = FMath::Atan2(-AxisY.Z, AxisZ.Z); + Result.Y = 0.f; + } + } + else if (RotationOrder == EDazMorphAnimInstanceRotationOrder::ZXY) + { + Result.X = FMath::Asin(-FMath::Clamp(AxisY.Z, -1.f, 1.f)); + + if (FMath::Abs(AxisY.Z) < 1.f - SMALL_NUMBER) + { + Result.Y = FMath::Atan2(AxisX.Z, AxisZ.Z); + Result.Z = FMath::Atan2(AxisY.X, AxisY.Y); + } + else + { + Result.Y = FMath::Atan2(-AxisZ.X, AxisX.X); + Result.Z = 0.f; + } + } + else if (RotationOrder == EDazMorphAnimInstanceRotationOrder::ZYX) + { + Result.Y = FMath::Asin(FMath::Clamp(AxisX.Z, -1.f, 1.f)); + + if (FMath::Abs(AxisX.Z) < 1.f - SMALL_NUMBER) + { + Result.X = FMath::Atan2(-AxisY.Z, AxisZ.Z); + Result.Z = FMath::Atan2(-AxisX.Y, AxisX.X); + } + else + { + Result.X = FMath::Atan2(AxisZ.Y, AxisY.Y); + Result.Z = 0.f; + } + } + + return Result * 180.f / PI; +} \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/DazJointControlledMorphAnimInstance.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/DazJointControlledMorphAnimInstance.h new file mode 100644 index 0000000..e38316b --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/DazJointControlledMorphAnimInstance.h @@ -0,0 +1,131 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Animation/AnimInstance.h" +#include "Animation/AnimInstanceProxy.h" +#include "DazJointControlledMorphAnimInstance.generated.h" + +UENUM() +enum class EDazMorphAnimInstanceRotationOrder : uint8 +{ + Auto, + XYZ, + XZY, + YXZ, + YZX, + ZXY, + ZYX +}; + +UENUM() +enum class EDazMorphAnimInstanceDriver : uint8 +{ + None, + RotationX, + RotationY, + RotationZ +}; + +USTRUCT(Blueprintable) +struct DAZTOUNREALRUNTIME_API FDazJointControlLinkKey +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + float BoneRotation; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + float MorphAlpha; +}; + +USTRUCT(Blueprintable) +struct DAZTOUNREALRUNTIME_API FDazJointControlLink +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + FName BoneName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + EDazMorphAnimInstanceDriver PrimaryAxis; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + EDazMorphAnimInstanceDriver SecondaryAxis = EDazMorphAnimInstanceDriver::None; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + FName MorphName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + float Scalar; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + float Alpha; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + TArray Keys; +}; + +/** Proxy override for this UAnimInstance-derived class */ +USTRUCT() +struct DAZTOUNREALRUNTIME_API FDazJointControlledMorphAnimInstanceProxy : public FAnimInstanceProxy +{ + GENERATED_BODY() + +public: + FDazJointControlledMorphAnimInstanceProxy() + { + } + + FDazJointControlledMorphAnimInstanceProxy(UAnimInstance* InAnimInstance) + : FAnimInstanceProxy(InAnimInstance) + { + } + + virtual ~FDazJointControlledMorphAnimInstanceProxy(); + + // FAnimInstanceProxy interface + virtual void Initialize(UAnimInstance* InAnimInstance) override; + virtual bool Evaluate(FPoseContext& Output) override; + //virtual void UpdateAnimationNode(const FAnimationUpdateContext& InContext) override; + virtual void PreUpdate(UAnimInstance* InAnimInstance, float DeltaSeconds) override; + + //TMap StoredTransforms; + //TMap StoredCurves; + +private: + //TArray XRotation; + //TArray YRotation; + //TArray ZRotation; + TArray ControlLinks; + + const float ExtractSourceValue(const FTransform& InCurrentBoneTransform, const FTransform& InRefPoseBoneTransform, EDazMorphAnimInstanceDriver Controller, EDazMorphAnimInstanceDriver SecondaryController); + FVector EulerFromQuat(const FQuat& Rotation, EDazMorphAnimInstanceRotationOrder RotationOrder); + void ProcessLink(FPoseContext& Output, FDazJointControlLink Link, EDazMorphAnimInstanceDriver Driver, EDazMorphAnimInstanceDriver SecondaryDriver); +}; + +UCLASS(Blueprintable) +class DAZTOUNREALRUNTIME_API UDazJointControlledMorphAnimInstance : public UAnimInstance +{ + GENERATED_BODY() + +public: + + UDazJointControlledMorphAnimInstance() {}; + virtual void NativeInitializeAnimation() override; + virtual void NativeUpdateAnimation(float DeltaTime) override; + + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation") + TArray ControlLinks; + +private: + //TArray XRotation; + //TArray YRotation; + //TArray ZRotation; + +protected: + + // UAnimInstance interface + virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override; + +}; \ No newline at end of file