#include "Misc/MujocoXmlGenerator.h" #include "Components/SceneComponent.h" #include "Components/StaticMeshComponent.h" #include "Components/SkeletalMeshComponent.h" #include "Components/PrimitiveComponent.h" #include "tinyxml2.h" #include "Misc/FileHelper.h" #include "Misc/Paths.h" #include "Components/MujocoBodyComponent.h" #include "Engine/SCS_Node.h" #include "StaticMeshOperations.h" #include "functional" #include "Actors/MujocoStaticMeshActor.h" #include "Engine/LevelScriptActor.h" #include "Components/MujocoGeomComponent.h" #include "Components/MujocoJointComponent.h" #include "Components/MujocoSiteComponent.h" #include "Components/MujocoActuatorComponent.h" #include "Components/MujocoEqualityComponent.h" #include "Components/MujocoTendonComponent.h" #include "UObject/PropertyOptional.h" #include "Structs/MujocoInertial.h" #include "Structs/MujocoSite.h" #include "Structs/MujocoGeom.h" #include "Enums/MujocoEnums.h" #include "Camera/CameraComponent.h" #include "GameFramework/SpringArmComponent.h" #include "Misc/MujocoOptions.h" #include "array" #include "LuckyMujoco.h" template FString GetAttributeMappingForProperty(FProperty* /*Prop*/) { return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Joint"), TEXT("joint") }, std::pair{ TEXT("Coef"), TEXT("coef") }, }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Site"), TEXT("site") }, }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Geom"), TEXT("geom") }, std::pair{ TEXT("SideSite"), TEXT("sidesite") }, }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Divisor"), TEXT("divisor") }, }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Constraint"), TEXT("constraint") }, std::pair{ TEXT("Equality"), TEXT("equality") }, std::pair{ TEXT("FrictionLoss"), TEXT("frictionloss") }, std::pair{ TEXT("Limit"), TEXT("limit") }, std::pair{ TEXT("Contact"), TEXT("contact") }, std::pair{ TEXT("Passive"), TEXT("passive") }, std::pair{ TEXT("Gravity"), TEXT("gravity") }, std::pair{ TEXT("Actuation"), TEXT("actuation") }, std::pair{ TEXT("RefSafe"), TEXT("refsafe") }, std::pair{ TEXT("Sensor"), TEXT("sensor") }, std::pair{ TEXT("Energy"), TEXT("energy") }, std::pair{ TEXT("FwdInv"), TEXT("fwdinv") }, std::pair{ TEXT("MultiCCD"), TEXT("multiccd") }, std::pair{ TEXT("Island"), TEXT("island") }, std::pair{ TEXT("NativeCCD"), TEXT("nativeccd") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("AutoLimits"), TEXT("autolimits") }, std::pair{ TEXT("BoundMass"), TEXT("boundmass") }, std::pair{ TEXT("BoundInertia"), TEXT("boundinertia") }, std::pair{ TEXT("SetTotalMass"), TEXT("settotalmass") }, std::pair{ TEXT("BalanceInertia"), TEXT("balanceinertia") }, std::pair{ TEXT("StripPath"), TEXT("strippath") }, std::pair{ TEXT("AngleUnits"), TEXT("angle") }, std::pair{ TEXT("FitAABB"), TEXT("fitaabb") }, std::pair{ TEXT("EulerSequence"), TEXT("eulerseq") }, std::pair{ TEXT("MeshDir"), TEXT("meshdir") }, std::pair{ TEXT("TextureDir"), TEXT("texturedir") }, std::pair{ TEXT("AssetDir"), TEXT("assetdir") }, std::pair{ TEXT("DiscardVisual"), TEXT("discardvisual") }, std::pair{ TEXT("UseThread"), TEXT("usethread") }, std::pair{ TEXT("FuseStatic"), TEXT("fusestatic") }, std::pair{ TEXT("InertiaFromGeom"), TEXT("inertiafromgeom") }, std::pair{ TEXT("AlignFree"), TEXT("alignfree") }, std::pair{ TEXT("InertiaGroupRange"), TEXT("inertiagrouprange") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("TimeStep"), TEXT("timestep") }, std::pair{ TEXT("ApiRate"), TEXT("apirate") }, std::pair{ TEXT("ImpRatio"), TEXT("impratio") }, std::pair{ TEXT("Gravity"), TEXT("gravity") }, std::pair{ TEXT("Wind"), TEXT("wind") }, std::pair{ TEXT("Magnetic"), TEXT("magnetic") }, std::pair{ TEXT("Density"), TEXT("density") }, std::pair{ TEXT("Viscosity"), TEXT("viscosity") }, std::pair{ TEXT("MarginOverride"), TEXT("o_margin") }, std::pair{ TEXT("Integrator"), TEXT("integrator") }, std::pair{ TEXT("ConeType"), TEXT("cone") }, std::pair{ TEXT("JacobianType"), TEXT("jacobian") }, std::pair{ TEXT("Solver"), TEXT("solver") }, std::pair{ TEXT("MaxIterations"), TEXT("iterations") }, std::pair{ TEXT("SolverTolerance"), TEXT("tolerance") }, std::pair{ TEXT("LineSearchIterations"), TEXT("ls_iterations") }, std::pair{ TEXT("LineSearchTolerance"), TEXT("ls_tolerance") }, std::pair{ TEXT("NoSlipIterations"), TEXT("noslip_iterations") }, std::pair{ TEXT("NoSlipTolerance"), TEXT("noslip_tolerance") }, std::pair{ TEXT("CCDIterations"), TEXT("ccd_iterations") }, std::pair{ TEXT("CCDTolerance"), TEXT("ccd_tolerance") }, std::pair{ TEXT("SDFIterations"), TEXT("sdf_iterations") }, std::pair{ TEXT("SDFInitPoints"), TEXT("sdf_initpoints") }, std::pair{ TEXT("ActuatorGroupDisable"), TEXT("actuatorgroupdisable") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Group"), TEXT("group") }, std::pair{ TEXT("CtrlLimited"), TEXT("ctrllimited") }, std::pair{ TEXT("ForceLimited"), TEXT("forcelimited") }, std::pair{ TEXT("ActLimited"), TEXT("actlimited") }, std::pair{ TEXT("CtrlRange"), TEXT("ctrlrange") }, std::pair{ TEXT("ForceRange"), TEXT("forcerange") }, std::pair{ TEXT("ActRange"), TEXT("actrange") }, std::pair{ TEXT("LengthRange"), TEXT("lengthrange") }, std::pair{ TEXT("Gear"), TEXT("gear") }, std::pair{ TEXT("CrankLength"), TEXT("cranklength") }, std::pair{ TEXT("Joint"), TEXT("joint") }, std::pair{ TEXT("JointInParent"), TEXT("jointinparent") }, std::pair{ TEXT("Site"), TEXT("site") }, std::pair{ TEXT("RefSite"), TEXT("refsite") }, std::pair{ TEXT("Body"), TEXT("body") }, std::pair{ TEXT("Tendon"), TEXT("tendon") }, std::pair{ TEXT("CrankSite"), TEXT("cranksite") }, std::pair{ TEXT("SliderSite"), TEXT("slidersite") }, std::pair{ TEXT("Kp"), TEXT("kp") }, std::pair{ TEXT("Kv"), TEXT("kv") }, std::pair{ TEXT("DampingRatio"), TEXT("dampratio") }, std::pair{ TEXT("TimeConst"), TEXT("timeconst") }, std::pair{ TEXT("InheritRange"), TEXT("inheritrange") }, std::pair{ TEXT("Area"), TEXT("area") }, std::pair{ TEXT("Diameter"), TEXT("diameter") }, std::pair{ TEXT("Bias"), TEXT("bias") }, std::pair{ TEXT("MuscleTimeConsts"), TEXT("timeconst") }, std::pair{ TEXT("TauSmooth"), TEXT("tausmooth") }, std::pair{ TEXT("Range"), TEXT("range") }, std::pair{ TEXT("Force"), TEXT("force") }, std::pair{ TEXT("Scale"), TEXT("scale") }, std::pair{ TEXT("VMax"), TEXT("vmax") }, std::pair{ TEXT("FpMax"), TEXT("fpmax") }, std::pair{ TEXT("FvMax"), TEXT("fvmax") }, std::pair{ TEXT("LMin"), TEXT("lmin") }, std::pair{ TEXT("LMax"), TEXT("lmax") }, std::pair{ TEXT("AdhesionGain"), TEXT("gain") }, std::pair{ TEXT("DynType"), TEXT("dyntype") }, std::pair{ TEXT("GainType"), TEXT("gaintype") }, std::pair{ TEXT("BiasType"), TEXT("biastype") }, std::pair{ TEXT("DynPrm"), TEXT("dynprm") }, std::pair{ TEXT("GainPrm"), TEXT("gainprm") }, std::pair{ TEXT("BiasPrm"), TEXT("biasprm") }, std::pair{ TEXT("ActEarly"), TEXT("actearly") }, }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Active"), TEXT("active") }, std::pair{ TEXT("Body1"), TEXT("body1") }, std::pair{ TEXT("Body2"), TEXT("body2") }, std::pair{ TEXT("Site1"), TEXT("site1") }, std::pair{ TEXT("Site2"), TEXT("site2") }, std::pair{ TEXT("Joint1"), TEXT("joint1") }, std::pair{ TEXT("Joint2"), TEXT("joint2") }, std::pair{ TEXT("Tendon1"), TEXT("tendon1") }, std::pair{ TEXT("Tendon2"), TEXT("tendon2") }, std::pair{ TEXT("Flex"), TEXT("flex") }, std::pair{ TEXT("SolRef"), TEXT("solref") }, std::pair{ TEXT("SolImp"), TEXT("solimp") }, std::pair{ TEXT("Anchor"), TEXT("anchor") }, std::pair{ TEXT("RelPose"), TEXT("relpose") }, std::pair{ TEXT("TorqueScale"), TEXT("torquescale") }, std::pair{ TEXT("PolyCoef"), TEXT("polycoef") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("ConType"), TEXT("contype") }, std::pair{ TEXT("ConAffinity"), TEXT("conaffinity") }, std::pair{ TEXT("ConDim"), TEXT("condim") }, std::pair{ TEXT("Group"), TEXT("group") }, std::pair{ TEXT("Priority"), TEXT("priority") }, std::pair{ TEXT("Friction"), TEXT("friction") }, std::pair{ TEXT("Mass"), TEXT("mass") }, std::pair{ TEXT("Density"), TEXT("density") }, std::pair{ TEXT("ShellInertia"), TEXT("shellinertia") }, std::pair{ TEXT("Solmix"), TEXT("solmix") }, std::pair{ TEXT("SolImp"), TEXT("solimp") }, std::pair{ TEXT("SolRef"), TEXT("solref") }, std::pair{ TEXT("Margin"), TEXT("margin") }, std::pair{ TEXT("Gap"), TEXT("gap") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Pos"), TEXT("pos") }, std::pair{ TEXT("Mass"), TEXT("mass") }, std::pair{ TEXT("DiagInertia"), TEXT("diaginertia") }, std::pair{ TEXT("FullInertia"), TEXT("fullinertia") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Type"), TEXT("type") }, std::pair{ TEXT("Group"), TEXT("group") }, std::pair{ TEXT("Position"), TEXT("pos") }, std::pair{ TEXT("Axis"), TEXT("axis") }, std::pair{ TEXT("SpringDamper"), TEXT("springdamper") }, std::pair{ TEXT("Stiffness"), TEXT("stiffness") }, std::pair{ TEXT("Range"), TEXT("range") }, std::pair{ TEXT("LimitType"), TEXT("limited") }, std::pair{ TEXT("ActuatorForceRange"), TEXT("actuatorfrcrange") }, std::pair{ TEXT("ActuatorForceLimitType"), TEXT("actuatorfrclimited") }, std::pair{ TEXT("bActuatorGravityCompensation"), TEXT("actuatorgravcomp") }, std::pair{ TEXT("Margin"), TEXT("margin") }, std::pair{ TEXT("Reference"), TEXT("ref") }, std::pair{ TEXT("SpringReference"), TEXT("springref") }, std::pair{ TEXT("Armature"), TEXT("armature") }, std::pair{ TEXT("Damping"), TEXT("damping") }, std::pair{ TEXT("FrictionLoss"), TEXT("frictionloss") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Type"), TEXT("type") }, std::pair{ TEXT("Group"), TEXT("group") }, std::pair{ TEXT("Rgba"), TEXT("rgba") }, std::pair{ TEXT("Size"), TEXT("size") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Name"), TEXT("name") }, std::pair{ TEXT("Group"), TEXT("group") }, std::pair{ TEXT("Limited"), TEXT("limited") }, std::pair{ TEXT("Range"), TEXT("range") }, std::pair{ TEXT("SolRefLimit"), TEXT("solreflimit") }, std::pair{ TEXT("SolImpLimit"), TEXT("solimplimit") }, std::pair{ TEXT("SolRefFriction"), TEXT("solreffriction") }, std::pair{ TEXT("SolImpFriction"), TEXT("solimpfriction") }, std::pair{ TEXT("Margin"), TEXT("margin") }, std::pair{ TEXT("FrictionLoss"), TEXT("frictionloss") }, std::pair{ TEXT("Width"), TEXT("width") }, std::pair{ TEXT("SpringLength"), TEXT("springlength") }, std::pair{ TEXT("Stiffness"), TEXT("stiffness") }, std::pair{ TEXT("Damping"), TEXT("damping") }, std::pair{ TEXT("SpatialSite"), TEXT("site") }, std::pair{ TEXT("SpatialGeom"), TEXT("geom") }, std::pair{ TEXT("Pulley"), TEXT("pulley") }, std::pair{ TEXT("FixedJoint"), TEXT("fixed") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template FString GetStructToString(FStructProperty* StructProperty, void* Container) { if (StructProperty->Struct->GetFName() == T::StaticStruct()->GetFName()) { T* StructInstance = StructProperty->ContainerPtrToValuePtr(Container); if (StructInstance) { return StructInstance->ToString(); } } return FString(); } const TSet NotLowerCase = { "Euler", "RK4", "PGS", "CG", "Newton" }; FString GetPropertyValueAsString(FProperty* Property, void* Container) { FString ValueStr; if (FEnumProperty* EnumProperty = CastField(Property)) { int64 EnumValue = EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(EnumProperty->ContainerPtrToValuePtr(Container)); auto DisplayText = EnumProperty->GetEnum()->GetNameStringByValue(EnumValue); if (!NotLowerCase.Contains(DisplayText)) { ValueStr = DisplayText.ToLower(); } else { ValueStr = DisplayText; } if (Property->GetFName() == GET_MEMBER_NAME_CHECKED(FMujocoGeom, ConDim)) { ValueStr = FString::FromInt(EnumValue); } else if (ValueStr.EndsWith("_")) { ValueStr = ValueStr.LeftChop(1); } } else if (FArrayProperty* NumericArrayProperty = CastField(Property)) { FProperty* InnerProperty = NumericArrayProperty->Inner; FScriptArrayHelper ArrayHelper(NumericArrayProperty, NumericArrayProperty->ContainerPtrToValuePtr(Container)); if (InnerProperty->IsA() && CastField(InnerProperty)->IsFloatingPoint()) { TArray Values; for (int32 i = 0; i < ArrayHelper.Num(); ++i) { double Value = CastField(InnerProperty)->GetFloatingPointPropertyValue(ArrayHelper.GetRawPtr(i)); Values.Add(FString::SanitizeFloat(Value)); } ValueStr = FString::Join(Values, TEXT(" ")); } else if (InnerProperty->IsA() && CastField(InnerProperty)->IsInteger()) { TArray Values; for (int32 i = 0; i < ArrayHelper.Num(); ++i) { int64 Value = CastField(InnerProperty)->GetSignedIntPropertyValue(ArrayHelper.GetRawPtr(i)); Values.Add(FString::Printf(TEXT("%lld"), Value)); } ValueStr = FString::Join(Values, TEXT(" ")); } } else if (FNumericProperty* NumericProperty = CastField(Property)) { if (NumericProperty->IsFloatingPoint()) { double Value = NumericProperty->GetFloatingPointPropertyValue(NumericProperty->ContainerPtrToValuePtr(Container)); ValueStr = FString::SanitizeFloat(Value); } else if (NumericProperty->IsInteger()) { int64 Value = NumericProperty->GetSignedIntPropertyValue(NumericProperty->ContainerPtrToValuePtr(Container)); ValueStr = FString::Printf(TEXT("%lld"), Value); } } else if (FBoolProperty* BoolProperty = CastField(Property)) { bool Value = BoolProperty->GetPropertyValue(BoolProperty->ContainerPtrToValuePtr(Container)); ValueStr = Value ? TEXT("true") : TEXT("false"); } else if (FStrProperty* StringProperty = CastField(Property)) { FString* String = StringProperty->ContainerPtrToValuePtr(Container); if (String && !String->IsEmpty()) { ValueStr = StringProperty->GetPropertyValue(String); } } else if (FNameProperty* NameProperty = CastField(Property)) { auto* Name = NameProperty->ContainerPtrToValuePtr(Container); if (Name && !Name->IsNone()) { ValueStr = NameProperty->GetPropertyValue(Name).ToString(); } } else if (FOptionalProperty* OptionalProperty = CastField(Property)) { void* OptionalPtr = OptionalProperty->ContainerPtrToValuePtr(Container); if (OptionalPtr && OptionalProperty->IsSet(OptionalPtr)) { FProperty* InnerProperty = OptionalProperty->GetValueProperty(); if (InnerProperty) { ValueStr = GetPropertyValueAsString(InnerProperty, OptionalPtr); } } } else if (FStructProperty* StructProperty = CastField(Property)) { if (StructProperty->Struct->GetFName() == NAME_Vector) { FVector* Vector = StructProperty->ContainerPtrToValuePtr(Container); ValueStr = FString::Printf(TEXT("%f %f %f"), Vector->X, Vector->Y, Vector->Z); } else if (StructProperty->Struct->GetFName() == NAME_Vector2D) { FVector2D* Vector = StructProperty->ContainerPtrToValuePtr(Container); ValueStr = FString::Printf(TEXT("%f %f"), Vector->X, Vector->Y); } else if (StructProperty->Struct->GetFName() == NAME_IntPoint) { FIntPoint* IntPoint = StructProperty->ContainerPtrToValuePtr(Container); ValueStr = FString::Printf(TEXT("%d %d"), IntPoint->X, IntPoint->Y); } else { ValueStr = GetStructToString(StructProperty, Container); if (ValueStr.IsEmpty()) ValueStr = GetStructToString(StructProperty, Container); if (ValueStr.IsEmpty()) ValueStr = GetStructToString(StructProperty, Container); if (ValueStr.IsEmpty()) ValueStr = GetStructToString(StructProperty, Container); if (ValueStr.IsEmpty()) ValueStr = GetStructToString(StructProperty, Container); if (ValueStr.IsEmpty()) ValueStr = GetStructToString(StructProperty, Container); } } else { UE_LOG(LogMujoco, Warning, TEXT("Unsupported property type %s"), *Property->GetClass()->GetName()); } return ValueStr; } template <> FString GetAttributeMappingForProperty(FProperty* Prop) { // clang-format off constexpr std::array Mapping{ std::pair{ TEXT("Mocap"), TEXT("mocap") }, std::pair{ TEXT("GravComp"), TEXT("gravcomp") } }; // clang-format on FString PropName = Prop->GetNameCPP(); for (const auto& Pair : Mapping) { if (PropName.Equals(Pair.first, ESearchCase::IgnoreCase)) { return FString(Pair.second); } } return FString(); } template FString GetPropertyAttributeName(FProperty* Prop) { return GetAttributeMappingForProperty(Prop); } template void ApplyStructAttributes(tinyxml2::XMLElement* XmlElem, const T& StructData) { for (TFieldIterator It(T::StaticStruct()); It; ++It) { FProperty* Prop = *It; const FString AttributeName = GetPropertyAttributeName(Prop); if (AttributeName.IsEmpty()) { continue; } const FString Value = GetPropertyValueAsString(Prop, reinterpret_cast(const_cast(&StructData))); if (Value.IsEmpty()) { continue; } XmlElem->SetAttribute(TCHAR_TO_ANSI(*AttributeName), TCHAR_TO_ANSI(*Value)); } } bool FMujocoXmlGenerator::VisitGeomComponent(USCS_Node* SCSNode, UMujocoGeomComponent* GeomComponent, tinyxml2::XMLElement* Element) { FString Name = SCSNode ? SCSNode->GetVariableName().ToString() : GeomComponent->GetName(); tinyxml2::XMLElement* GeomElement = Element->GetDocument()->NewElement("geom"); const FMujocoGeom& GeomStruct = GeomComponent->Geom; GeomElement->SetAttribute("name", TCHAR_TO_ANSI(*Name)); GeomElement->SetAttribute("type", "mesh"); FTransform BodyTransform = GeomComponent->GetRelativeTransform(); FVector Position = BodyTransform.GetLocation() * 0.01f; FQuat Rotation = BodyTransform.Rotator().Quaternion(); FVector Scale = BodyTransform.GetScale3D(); if (!Position.IsZero()) { GeomElement->SetAttribute("pos", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f"), Position.X, -Position.Y, Position.Z))); } if (!Rotation.IsIdentity()) { GeomElement->SetAttribute("quat", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f %f"), -Rotation.W, Rotation.X, -Rotation.Y, Rotation.Z))); } ApplyStructAttributes(GeomElement, GeomStruct); if (UStaticMesh* StaticMesh = GeomComponent->GetStaticMesh()) { tinyxml2::XMLElement* Root = Element->GetDocument()->FirstChildElement("mujoco"); ensureMsgf(Root, TEXT("No mujoco root element found in xml document")); tinyxml2::XMLElement* AssetRoot = Root->FirstChildElement("asset"); ensureMsgf(AssetRoot, TEXT("No asset element found in mujoco root element")); tinyxml2::XMLElement* MeshElement = Element->GetDocument()->NewElement("mesh"); FString MeshPath = GeomComponent->GetFName().ToString(); ObjectMap.FindOrAdd(MeshPath, GeomComponent); FString MeshName = FString::Printf(TEXT("%s_%s_%d"), *Name, *StaticMesh->GetName(), StaticMesh->GetUniqueID()); MeshElement->SetAttribute("file", TCHAR_TO_ANSI(*MeshPath)); MeshElement->SetAttribute("name", TCHAR_TO_ANSI(*MeshName)); if (Scale.X != 1.0f || Scale.Y != 1.0f || Scale.Z != 1.0f) { MeshElement->SetAttribute("scale", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f"), Scale.X, Scale.Y, Scale.Z))); } AssetRoot->InsertEndChild(MeshElement); GeomElement->SetAttribute("mesh", TCHAR_TO_ANSI(*MeshName)); } Element->InsertEndChild(GeomElement); return true; } bool FMujocoXmlGenerator::VisitJointComponent(USCS_Node* SCSNode, UMujocoJointComponent* JointComponent, tinyxml2::XMLElement* Element) { FString Name = SCSNode ? SCSNode->GetVariableName().ToString() : JointComponent->GetName(); tinyxml2::XMLElement* JointElement = Element->GetDocument()->NewElement("joint"); const FMujocoJoint& JointStruct = JointComponent->Joint; JointElement->SetAttribute("name", TCHAR_TO_ANSI(*Name)); ApplyStructAttributes(JointElement, JointStruct); Element->InsertEndChild(JointElement); return true; } bool FMujocoXmlGenerator::VisitSiteComponent(USCS_Node* SCSNode, UMujocoSiteComponent* SiteComponent, tinyxml2::XMLElement* Element) { FString Name = SCSNode ? SCSNode->GetVariableName().ToString() : SiteComponent->GetName(); tinyxml2::XMLElement* SiteElement = Element->GetDocument()->NewElement("site"); const FMujocoSite& SiteStruct = SiteComponent->Site; SiteElement->SetAttribute("name", TCHAR_TO_ANSI(*Name)); FTransform SiteTransform = SiteComponent->GetRelativeTransform(); FVector Position = SiteTransform.GetLocation() * 0.01f; FQuat Rotation = SiteTransform.Rotator().Quaternion(); if (!Position.IsZero()) { SiteElement->SetAttribute("pos", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f"), Position.X, -Position.Y, Position.Z))); } if (!Rotation.IsIdentity()) { SiteElement->SetAttribute("quat", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f %f"), -Rotation.W, Rotation.X, -Rotation.Y, Rotation.Z))); } ApplyStructAttributes(SiteElement, SiteStruct); Element->InsertEndChild(SiteElement); return true; } bool FMujocoXmlGenerator::VisitActuatorComponent(USCS_Node* SCSNode, UMujocoActuatorComponent* ActuatorComponent, tinyxml2::XMLElement* Element) { FString Name = SCSNode ? SCSNode->GetVariableName().ToString() : ActuatorComponent->GetName(); tinyxml2::XMLElement* ActuatorElement = nullptr; const FMujocoActuatorV2& ActuatorStruct = ActuatorComponent->Actuator; if (FEnumProperty* TypeProperty = CastField(FMujocoActuatorV2::StaticStruct()->FindPropertyByName("Type"))) { int64 EnumValue = TypeProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(TypeProperty->ContainerPtrToValuePtr(&ActuatorStruct)); auto DisplayText = TypeProperty->GetEnum()->GetNameStringByValue(EnumValue); auto ValueStr = DisplayText.ToLower(); ActuatorElement = Element->GetDocument()->NewElement(TCHAR_TO_ANSI(*ValueStr)); } else { return false; } ActuatorElement->SetAttribute("name", TCHAR_TO_ANSI(*Name)); ApplyStructAttributes(ActuatorElement, ActuatorStruct); tinyxml2::XMLElement* ActuatorRoot = Element->FirstChildElement("actuator"); ensureMsgf(ActuatorRoot, TEXT("No actuator element found in mujoco root element")); ActuatorRoot->InsertEndChild(ActuatorElement); return true; } bool FMujocoXmlGenerator::VisitEqualityComponent(USCS_Node* SCSNode, UMujocoEqualityComponent* EqualityComponent, tinyxml2::XMLElement* Element) { FString Name = SCSNode ? SCSNode->GetVariableName().ToString() : EqualityComponent->GetName(); tinyxml2::XMLElement* EqualityElement = nullptr; const FMujocoEquality& EqualityStruct = EqualityComponent->Equality; if (FEnumProperty* TypeProperty = CastField(FMujocoEquality::StaticStruct()->FindPropertyByName("Type"))) { int64 EnumValue = TypeProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(TypeProperty->ContainerPtrToValuePtr(&EqualityStruct)); auto DisplayText = TypeProperty->GetEnum()->GetNameStringByValue(EnumValue); auto ValueStr = DisplayText.ToLower(); EqualityElement = Element->GetDocument()->NewElement(TCHAR_TO_ANSI(*ValueStr)); } else { return false; } ApplyStructAttributes(EqualityElement, EqualityStruct); tinyxml2::XMLElement* EqualityRoot = Element->FirstChildElement("equality"); ensureMsgf(EqualityRoot, TEXT("No equality element found in mujoco root element")); EqualityRoot->InsertEndChild(EqualityElement); return true; } bool FMujocoXmlGenerator::VisitTendonComponent(USCS_Node* SCSNode, UMujocoTendonComponent* TendonComponent, tinyxml2::XMLElement* Element) { FString Name = SCSNode ? SCSNode->GetVariableName().ToString() : TendonComponent->GetName(); tinyxml2::XMLElement* TendonElement = nullptr; const FMujocoTendon& TendonStruct = TendonComponent->Tendon; if (FEnumProperty* TypeProperty = CastField(FMujocoTendon::StaticStruct()->FindPropertyByName("Type"))) { int64 EnumValue = TypeProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(TypeProperty->ContainerPtrToValuePtr(&TendonStruct)); auto DisplayText = TypeProperty->GetEnum()->GetNameStringByValue(EnumValue); auto ValueStr = DisplayText.ToLower(); TendonElement = Element->GetDocument()->NewElement(TCHAR_TO_ANSI(*ValueStr)); } else { return false; } ApplyStructAttributes(TendonElement, TendonStruct); for (const auto& SpatialSite : TendonComponent->Tendon.SpatialSite) { tinyxml2::XMLElement* SpatialElement = TendonElement->GetDocument()->NewElement("site"); ApplyStructAttributes(SpatialElement, SpatialSite); TendonElement->InsertEndChild(SpatialElement); } for (const auto& SpatialGeom : TendonComponent->Tendon.SpatialGeom) { tinyxml2::XMLElement* SpatialElement = TendonElement->GetDocument()->NewElement("geom"); ApplyStructAttributes(SpatialElement, SpatialGeom); TendonElement->InsertEndChild(SpatialElement); } for (const auto& Pulley : TendonComponent->Tendon.Pulley) { tinyxml2::XMLElement* PulleyElement = TendonElement->GetDocument()->NewElement("pulley"); ApplyStructAttributes(PulleyElement, Pulley); TendonElement->InsertEndChild(PulleyElement); } for (const auto& FixedJoint : TendonComponent->Tendon.FixedJoint) { tinyxml2::XMLElement* FixedElement = TendonElement->GetDocument()->NewElement("joint"); ApplyStructAttributes(FixedElement, FixedJoint); TendonElement->InsertEndChild(FixedElement); } tinyxml2::XMLElement* TendonRoot = Element->FirstChildElement("tendon"); ensureMsgf(TendonRoot, TEXT("No tendon element found in mujoco root element")); TendonRoot->InsertEndChild(TendonElement); return true; } bool FMujocoXmlGenerator::VisitCameraComponent(USCS_Node* SCSNode, UCameraComponent* CameraComponent, tinyxml2::XMLElement* Element, USpringArmComponent* SpringArmComponent) { FString Name = SCSNode ? SCSNode->GetVariableName().ToString() : CameraComponent->GetName(); if (SpringArmComponent) { FTransform SpringArmTransform = SpringArmComponent->GetRelativeTransform(); float SpringArmLength = SpringArmComponent->TargetArmLength * 0.01f; FVector Position = SpringArmTransform.GetLocation() * 0.1f - SpringArmTransform.GetRotation().GetForwardVector() * SpringArmLength; FQuat Rotation = SpringArmTransform.GetRotation().Rotator().Quaternion(); tinyxml2::XMLElement* FrameElement = Element->GetDocument()->NewElement("frame"); tinyxml2::XMLElement* CameraElement = Element->GetDocument()->NewElement("camera"); CameraElement->SetAttribute("name", TCHAR_TO_ANSI(*Name)); if (!Position.IsZero()) { FrameElement->SetAttribute("pos", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f"), Position.X, -Position.Y, Position.Z))); } if (!Rotation.IsIdentity()) { FrameElement->SetAttribute("quat", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f %f"), -Rotation.W, Rotation.X, -Rotation.Y, Rotation.Z))); } auto ElementName = Element->Name(); if (std::strcmp(ElementName, "body") == 0) { auto BodyName = Element->Attribute("name"); CameraElement->SetAttribute("mode", "targetbody"); CameraElement->SetAttribute("target", BodyName); } else { CameraElement->SetAttribute("mode", "track"); } FrameElement->InsertEndChild(CameraElement); Element->InsertEndChild(FrameElement); } else { tinyxml2::XMLElement* FrameElement = Element->GetDocument()->NewElement("frame"); tinyxml2::XMLElement* CameraElement = Element->GetDocument()->NewElement("camera"); CameraElement->SetAttribute("name", TCHAR_TO_ANSI(*Name)); FTransform CameraTransform = CameraComponent->GetRelativeTransform(); FVector Position = CameraTransform.GetLocation() * 0.01f; const FQuat CamExtra = FQuat(FRotator(0, -90, -90)); CameraElement->SetAttribute("quat", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f %f"), CamExtra.W, CamExtra.X, CamExtra.Y, CamExtra.Z))); FQuat Rotation = CameraTransform.Rotator().Quaternion(); if (!Position.IsZero()) { FrameElement->SetAttribute("pos", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f"), Position.X, -Position.Y, Position.Z))); } if (!Rotation.IsIdentity()) { FrameElement->SetAttribute("quat", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f %f"), -Rotation.W, Rotation.X, -Rotation.Y, Rotation.Z))); } FrameElement->InsertEndChild(CameraElement); Element->InsertEndChild(FrameElement); } return true; } bool FMujocoXmlGenerator::VisitSpringArm(USCS_Node* SCSNode, USpringArmComponent* SpringArmComponent, tinyxml2::XMLElement* Element) { FString Name = SCSNode ? SCSNode->GetVariableName().ToString() : SpringArmComponent->GetName(); if (!SCSNode) { TArray CameraComponents; SpringArmComponent->GetChildrenComponents(true, CameraComponents); for (USceneComponent* ChildComponent : CameraComponents) { if (UCameraComponent* CameraComponent = Cast(ChildComponent)) { if (!VisitCameraComponent(SCSNode, CameraComponent, Element, SpringArmComponent)) { return false; } break; } } } else { for (USCS_Node* ChildNode : SCSNode->GetChildNodes()) { if (UCameraComponent* CameraComponent = Cast(ChildNode->GetActualComponentTemplate(SpringArmComponent->GetTypedOuter()))) { if (!VisitCameraComponent(ChildNode, CameraComponent, Element, SpringArmComponent)) { return false; } break; } } } return true; } bool FMujocoXmlGenerator::VisitBodyComponent(USCS_Node* SCSNode, UMujocoBodyComponent* BodyComponent, tinyxml2::XMLElement* Element) { FString Name = SCSNode ? SCSNode->GetVariableName().ToString() : BodyComponent->GetName(); tinyxml2::XMLElement* BodyElement = Element->GetDocument()->NewElement("body"); BodyElement->SetAttribute("name", TCHAR_TO_ANSI(*Name)); FTransform BodyTransform = BodyComponent->GetRelativeTransform(); FVector Position = BodyTransform.GetLocation() * 0.01f; FQuat Rotation = BodyTransform.Rotator().Quaternion(); if (!Position.IsZero()) { BodyElement->SetAttribute("pos", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f"), Position.X, -Position.Y, Position.Z))); } if (!Rotation.IsIdentity()) { BodyElement->SetAttribute("quat", TCHAR_TO_ANSI(*FString::Printf(TEXT("%f %f %f %f"), -Rotation.W, Rotation.X, -Rotation.Y, Rotation.Z))); } for (TFieldIterator PropIt(BodyComponent->GetClass()); PropIt; ++PropIt) { FProperty* Prop = *PropIt; const FString AttributeName = GetPropertyAttributeName(Prop); if (AttributeName.IsEmpty()) { continue; } const FString Value = GetPropertyValueAsString(Prop, BodyComponent); if (Value.IsEmpty()) { continue; } BodyElement->SetAttribute(TCHAR_TO_ANSI(*AttributeName), TCHAR_TO_ANSI(*Value)); } if (BodyComponent->Inertial.IsSet()) { tinyxml2::XMLElement* InertialElement = BodyElement->GetDocument()->NewElement("inertial"); const FMujocoInertial& InertialStruct = BodyComponent->Inertial.GetValue(); ApplyStructAttributes(InertialElement, InertialStruct); BodyElement->InsertEndChild(InertialElement); } if (SCSNode) { for (USCS_Node* ChildNode : SCSNode->GetChildNodes()) { if (UMujocoBodyComponent* ChildBodyComponent = Cast(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter()))) { if (!VisitBodyComponent(ChildNode, ChildBodyComponent, BodyElement)) { return false; } } else if (UMujocoGeomComponent* GeomComponent = Cast(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter()))) { if (!VisitGeomComponent(ChildNode, GeomComponent, BodyElement)) { return false; } } else if (UMujocoJointComponent* JointComponent = Cast(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter()))) { if (!VisitJointComponent(ChildNode, JointComponent, BodyElement)) { return false; } } else if (UMujocoSiteComponent* SiteComponent = Cast(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter()))) { if (!VisitSiteComponent(ChildNode, SiteComponent, BodyElement)) { return false; } } else if (UCameraComponent* CameraComponent = Cast(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter()))) { if (!VisitCameraComponent(ChildNode, CameraComponent, BodyElement)) { return false; } } else if (USpringArmComponent* SpringArmComponent = Cast(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter()))) { if (!VisitSpringArm(ChildNode, SpringArmComponent, BodyElement)) { return false; } } } } else { TArray ChildComponents; BodyComponent->GetChildrenComponents(false, ChildComponents); TSet UniqueChildrens; for (USceneComponent* ChildComponent : ChildComponents) { UniqueChildrens.Add(ChildComponent); } for (USceneComponent* ChildComponent : UniqueChildrens) { if (UMujocoBodyComponent* ChildBodyComponent = Cast(ChildComponent)) { if (!VisitBodyComponent(nullptr, ChildBodyComponent, BodyElement)) { return false; } } else if (UMujocoGeomComponent* GeomComponent = Cast(ChildComponent)) { if (!VisitGeomComponent(nullptr, GeomComponent, BodyElement)) { return false; } } else if (UMujocoJointComponent* JointComponent = Cast(ChildComponent)) { if (!VisitJointComponent(nullptr, JointComponent, BodyElement)) { return false; } } else if (UMujocoSiteComponent* SiteComponent = Cast(ChildComponent)) { if (!VisitSiteComponent(nullptr, SiteComponent, BodyElement)) { return false; } } else if (UCameraComponent* CameraComponent = Cast(ChildComponent)) { if (!VisitCameraComponent(nullptr, CameraComponent, BodyElement)) { return false; } } else if (USpringArmComponent* SpringArmComponent = Cast(ChildComponent)) { if (!VisitSpringArm(nullptr, SpringArmComponent, BodyElement)) { return false; } } } } if (std::strcmp(Element->Name(), "mujoco") == 0) { tinyxml2::XMLElement* WorldBodyElement = Element->FirstChildElement("worldbody"); ensureMsgf(WorldBodyElement, TEXT("No worldbody element found in mujoco root element")); WorldBodyElement->InsertEndChild(BodyElement); } else if (std::strcmp(Element->Name(), "body") == 0) { Element->InsertEndChild(BodyElement); } else { ensureMsgf(false, TEXT("Unexpected element type")); return false; } return true; } template TMap ProcessProperties(void* StructPtr) { TMap OptionsMap; UStruct* Struct = T::StaticStruct(); for (TFieldIterator PropIt(Struct); PropIt; ++PropIt) { FProperty* Property = *PropIt; const FString AttributeName = GetAttributeMappingForProperty(Property); if (AttributeName.IsEmpty()) { continue; } FName OverridePropName(*FString::Printf(TEXT("bOverride%s"), *Property->GetNameCPP())); if (FProperty* OverrideProperty = Struct->FindPropertyByName(OverridePropName)) { void* OverridePtr = OverrideProperty->ContainerPtrToValuePtr(StructPtr); bool bOverride = false; OverrideProperty->GetValue_InContainer(StructPtr, &bOverride); if (!bOverride) { continue; } } const FString ValueStr = GetPropertyValueAsString(Property, StructPtr); OptionsMap.Add(AttributeName, ValueStr); } return OptionsMap; } bool FMujocoXmlGenerator::ParseExportOptions(UMujocoExportOptions* Data, tinyxml2::XMLElement* Element) { if (!Data || !Element) { return false; } Element->SetAttribute("model", TCHAR_TO_ANSI(*Data->ModelName)); TMap CompilerOptions = ProcessProperties(&Data->Compiler); TMap MujocoOptions = ProcessProperties(&Data->Options); TMap SimulationFlags = ProcessProperties(&Data->SimulationFlags); if (CompilerOptions.Num() > 0) { tinyxml2::XMLElement* CompilerElement = Element->InsertNewChildElement("compiler"); for (const auto& [Key, Value] : CompilerOptions) { CompilerElement->SetAttribute(TCHAR_TO_ANSI(*Key), TCHAR_TO_ANSI(*Value)); } } if (MujocoOptions.Num() > 0 || SimulationFlags.Num() > 0) { tinyxml2::XMLElement* MujocoElement = Element->InsertNewChildElement("option"); for (const auto& [Key, Value] : MujocoOptions) { MujocoElement->SetAttribute(TCHAR_TO_ANSI(*Key), TCHAR_TO_ANSI(*Value)); } if (SimulationFlags.Num() > 0) { tinyxml2::XMLElement* FlagsElement = MujocoElement->InsertNewChildElement("flag"); for (const auto& [Key, Value] : SimulationFlags) { FlagsElement->SetAttribute(TCHAR_TO_ANSI(*Key), TCHAR_TO_ANSI(*Value)); } } } return true; } bool FMujocoXmlGenerator::ParseBlueprintAsset(UBlueprint* BlueprintAsset, tinyxml2::XMLElement* Root) { if (!BlueprintAsset || !BlueprintAsset->GeneratedClass) { return false; } AActor* DefaultActor = Cast(BlueprintAsset->GeneratedClass->GetDefaultObject()); if (!DefaultActor) { return false; } if (!BlueprintAsset->SimpleConstructionScript) { return false; } if (UBlueprintGeneratedClass* BPGeneratedClass = Cast(BlueprintAsset->GeneratedClass)) { for (USCS_Node* SCSNode : BlueprintAsset->SimpleConstructionScript->GetRootNodes()) { if (!SCSNode) { continue; } if (UMujocoBodyComponent* BodyComp = Cast(SCSNode->GetActualComponentTemplate(BPGeneratedClass))) { if (!VisitBodyComponent(SCSNode, BodyComp, Root)) { return false; } } else if (UMujocoActuatorComponent* ActuatorComp = Cast(SCSNode->GetActualComponentTemplate(BPGeneratedClass))) { if (!VisitActuatorComponent(SCSNode, ActuatorComp, Root)) { return false; } } else if (UMujocoEqualityComponent* EqualityComp = Cast(SCSNode->GetActualComponentTemplate(BPGeneratedClass))) { if (!VisitEqualityComponent(SCSNode, EqualityComp, Root)) { return false; } } else if (UMujocoTendonComponent* TendonComp = Cast(SCSNode->GetActualComponentTemplate(BPGeneratedClass))) { if (!VisitTendonComponent(SCSNode, TendonComp, Root)) { return false; } } } } return true; } bool FMujocoXmlGenerator::ParseActorAsset(AActor* Actor, tinyxml2::XMLElement* Root) { if (!Actor) { return false; } for (UActorComponent* Component : Actor->GetComponents()) { if (UMujocoBodyComponent* BodyComp = Cast(Component)) { if (BodyComp->GetAttachParent()) { continue; } if (!VisitBodyComponent(nullptr, BodyComp, Root)) { return false; } } else if (UMujocoActuatorComponent* ActuatorComp = Cast(Component)) { if (!VisitActuatorComponent(nullptr, ActuatorComp, Root)) { return false; } } else if (UMujocoEqualityComponent* EqualityComp = Cast(Component)) { if (!VisitEqualityComponent(nullptr, EqualityComp, Root)) { return false; } } else if (UMujocoTendonComponent* TendonComp = Cast(Component)) { if (!VisitTendonComponent(nullptr, TendonComp, Root)) { return false; } } } return true; } TUniquePtr FMujocoXmlGenerator::GenerateMujocoXml( const TObjectPtr& ExportOptions, const TArray& Objects, const FString& ExportFilename, TMap ContactExclusion) { TUniquePtr Doc = MakeUnique(); Doc->InsertFirstChild(Doc->NewDeclaration("xml version=\"1.0\" encoding=\"utf-8\"")); tinyxml2::XMLElement* Root = Doc->NewElement("mujoco"); tinyxml2::XMLElement* AssetRoot = Doc->NewElement("asset"); tinyxml2::XMLElement* WorldBodyRoot = Doc->NewElement("worldbody"); ParseExportOptions(ExportOptions, Root); Root->InsertEndChild(AssetRoot); Root->InsertEndChild(WorldBodyRoot); Root->InsertEndChild(Doc->NewElement("equality")); Root->InsertEndChild(Doc->NewElement("tendon")); Root->InsertEndChild(Doc->NewElement("actuator")); // TODO Refacto using property from the MujocoActor aka the robot // TODO Maybe using a ParentObject as parameter which holds that information? // TODO Need actor refactoring anyway - Merge Pawn and Mujoco Actor? if (!ContactExclusion.IsEmpty()) { Root->InsertEndChild(Doc->NewElement("contact")); for (auto& [Body1, Body2] : ContactExclusion) { // Don't add empty strings to the map please! if (Body1.IsEmpty() || Body2.IsEmpty()) continue; // Create the contact exclusion - formatted as (SO100 example) // const auto Exclude = Doc->NewElement("exclude"); Exclude->SetAttribute("body1", TCHAR_TO_ANSI(*Body1)); Exclude->SetAttribute("body2", TCHAR_TO_ANSI(*Body2)); // Add exclusion to the contact tag Root->FirstChildElement("contact")->InsertEndChild(Exclude); } } if (ExportOptions->bAddSkyBox) { tinyxml2::XMLElement* TextureElement = AssetRoot->GetDocument()->NewElement("texture"); TextureElement->SetAttribute("type", "skybox"); TextureElement->SetAttribute("builtin", "gradient"); TextureElement->SetAttribute("rgb1", "0.3 0.5 0.7"); TextureElement->SetAttribute("rgb2", "0 0 0"); TextureElement->SetAttribute("width", "512"); TextureElement->SetAttribute("height", "3072"); AssetRoot->InsertEndChild(TextureElement); tinyxml2::XMLElement* LightElement = WorldBodyRoot->GetDocument()->NewElement("light"); LightElement->SetAttribute("pos", "0 0 1.5"); LightElement->SetAttribute("dir", "0 0 -1"); LightElement->SetAttribute("directional", "true"); WorldBodyRoot->InsertEndChild(LightElement); } if (ExportOptions->bAddGroundPlane) { tinyxml2::XMLElement* TextureElement = AssetRoot->GetDocument()->NewElement("texture"); TextureElement->SetAttribute("type", "2d"); TextureElement->SetAttribute("name", "groundplane"); TextureElement->SetAttribute("builtin", "checker"); TextureElement->SetAttribute("mark", "edge"); TextureElement->SetAttribute("rgb1", "0.2 0.3 0.4"); TextureElement->SetAttribute("rgb2", "0.1 0.2 0.3"); TextureElement->SetAttribute("markrgb", "0.8 0.8 0.8"); TextureElement->SetAttribute("width", "300"); TextureElement->SetAttribute("height", "300"); AssetRoot->InsertEndChild(TextureElement); tinyxml2::XMLElement* MaterialElement = AssetRoot->GetDocument()->NewElement("material"); MaterialElement->SetAttribute("name", "groundplane"); MaterialElement->SetAttribute("texture", "groundplane"); MaterialElement->SetAttribute("texuniform", "true"); MaterialElement->SetAttribute("texrepeat", "100 100"); MaterialElement->SetAttribute("reflectance", "0.2"); AssetRoot->InsertEndChild(MaterialElement); tinyxml2::XMLElement* GroundPlaneElement = WorldBodyRoot->GetDocument()->NewElement("geom"); GroundPlaneElement->SetAttribute("name", "floor"); GroundPlaneElement->SetAttribute("size", "0 0 0.05"); GroundPlaneElement->SetAttribute("type", "plane"); GroundPlaneElement->SetAttribute("material", "groundplane"); WorldBodyRoot->InsertEndChild(GroundPlaneElement); } Doc->InsertEndChild(Root); for (UObject* Object : Objects) { if (UBlueprint* Blueprint = Cast(Object)) { ParseBlueprintAsset(Blueprint, Root); } else if (AActor* Actor = Cast(Object)) { ParseActorAsset(Actor, Root); } } TSet ExportedMeshes; for (tinyxml2::XMLElement* MeshElement = AssetRoot->FirstChildElement("mesh"); MeshElement; MeshElement = MeshElement->NextSiblingElement("mesh")) { FString MeshPath = MeshElement->Attribute("file"); const auto& GeomIter = ObjectMap.Find(MeshPath); if (!GeomIter->IsValid()) { UE_LOG(LogTemp, Warning, TEXT("Failed to load geom %s not found"), *MeshPath); continue; } auto* GeomObj = Cast(GeomIter->Get()); if (!GeomObj) { UE_LOG(LogTemp, Warning, TEXT("Failed to load geom %s invalid ptr"), *MeshPath); continue; } FString MeshName = FPaths::GetBaseFilename(MeshPath) + TEXT("_") + FString::FromInt(GeomObj->GetUniqueID()); FString MeshFilename = FPaths::Combine(FPaths::GetPath(ExportFilename), ExportOptions->Compiler.AssetDir, MeshName + TEXT(".obj")); MeshFilename = FPaths::ConvertRelativePathToFull(MeshFilename); if (!ExportedMeshes.Contains(MeshName)) { ExportedMeshes.Add(MeshName); if (UMujocoStaticMesh* MujocoMesh = Cast(GeomObj->MujocoStaticMesh)) { FFileHelper::SaveStringToFile(MujocoMesh->ObjFileData, *MeshFilename); } } MeshElement->SetAttribute("file", TCHAR_TO_ANSI(*MeshFilename)); } return MoveTemp(Doc); }