Jb win 2207cfbba0 FT - Contact Exclusion list for XML scene creation
+ This feature was missing from the plugin while being necessary for robots to work
+ e.g. SO100 requires the main body and the first arm to have contact exclusion, I guess the piece are too tight in the model?
+ This is not clean, but making it right requires to have a better understanding of how the MuJoCo actor works - problem for future self
2025-04-30 13:24:03 +00:00

1386 lines
49 KiB
C++

#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 <typename T> FString GetAttributeMappingForProperty(FProperty* /*Prop*/)
{
return FString();
}
template <> FString GetAttributeMappingForProperty<FMujocoTendonFixedJoint>(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<FMujocoTendonSpatialSite>(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<FMujocoTendonSpatialGeom>(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<FMujocoTendonPulley>(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<FMujocoSimulationFlags>(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<FMujocoCompileOptions>(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<FMujocoOptions>(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<FMujocoActuatorV2>(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<FMujocoEquality>(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<FMujocoGeom>(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<FMujocoInertial>(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<FMujocoJoint>(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<FMujocoSite>(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<FMujocoTendon>(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 <typename T> FString GetStructToString(FStructProperty* StructProperty, void* Container)
{
if (StructProperty->Struct->GetFName() == T::StaticStruct()->GetFName())
{
T* StructInstance = StructProperty->ContainerPtrToValuePtr<T>(Container);
if (StructInstance)
{
return StructInstance->ToString();
}
}
return FString();
}
const TSet<FString> NotLowerCase = { "Euler", "RK4", "PGS", "CG", "Newton" };
FString GetPropertyValueAsString(FProperty* Property, void* Container)
{
FString ValueStr;
if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property))
{
int64 EnumValue = EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(EnumProperty->ContainerPtrToValuePtr<void>(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<FArrayProperty>(Property))
{
FProperty* InnerProperty = NumericArrayProperty->Inner;
FScriptArrayHelper ArrayHelper(NumericArrayProperty, NumericArrayProperty->ContainerPtrToValuePtr<void>(Container));
if (InnerProperty->IsA<FNumericProperty>() && CastField<FNumericProperty>(InnerProperty)->IsFloatingPoint())
{
TArray<FString> Values;
for (int32 i = 0; i < ArrayHelper.Num(); ++i)
{
double Value = CastField<FNumericProperty>(InnerProperty)->GetFloatingPointPropertyValue(ArrayHelper.GetRawPtr(i));
Values.Add(FString::SanitizeFloat(Value));
}
ValueStr = FString::Join(Values, TEXT(" "));
}
else if (InnerProperty->IsA<FNumericProperty>() && CastField<FNumericProperty>(InnerProperty)->IsInteger())
{
TArray<FString> Values;
for (int32 i = 0; i < ArrayHelper.Num(); ++i)
{
int64 Value = CastField<FNumericProperty>(InnerProperty)->GetSignedIntPropertyValue(ArrayHelper.GetRawPtr(i));
Values.Add(FString::Printf(TEXT("%lld"), Value));
}
ValueStr = FString::Join(Values, TEXT(" "));
}
}
else if (FNumericProperty* NumericProperty = CastField<FNumericProperty>(Property))
{
if (NumericProperty->IsFloatingPoint())
{
double Value = NumericProperty->GetFloatingPointPropertyValue(NumericProperty->ContainerPtrToValuePtr<void>(Container));
ValueStr = FString::SanitizeFloat(Value);
}
else if (NumericProperty->IsInteger())
{
int64 Value = NumericProperty->GetSignedIntPropertyValue(NumericProperty->ContainerPtrToValuePtr<void>(Container));
ValueStr = FString::Printf(TEXT("%lld"), Value);
}
}
else if (FBoolProperty* BoolProperty = CastField<FBoolProperty>(Property))
{
bool Value = BoolProperty->GetPropertyValue(BoolProperty->ContainerPtrToValuePtr<void>(Container));
ValueStr = Value ? TEXT("true") : TEXT("false");
}
else if (FStrProperty* StringProperty = CastField<FStrProperty>(Property))
{
FString* String = StringProperty->ContainerPtrToValuePtr<FString>(Container);
if (String && !String->IsEmpty())
{
ValueStr = StringProperty->GetPropertyValue(String);
}
}
else if (FNameProperty* NameProperty = CastField<FNameProperty>(Property))
{
auto* Name = NameProperty->ContainerPtrToValuePtr<FName>(Container);
if (Name && !Name->IsNone())
{
ValueStr = NameProperty->GetPropertyValue(Name).ToString();
}
}
else if (FOptionalProperty* OptionalProperty = CastField<FOptionalProperty>(Property))
{
void* OptionalPtr = OptionalProperty->ContainerPtrToValuePtr<void>(Container);
if (OptionalPtr && OptionalProperty->IsSet(OptionalPtr))
{
FProperty* InnerProperty = OptionalProperty->GetValueProperty();
if (InnerProperty)
{
ValueStr = GetPropertyValueAsString(InnerProperty, OptionalPtr);
}
}
}
else if (FStructProperty* StructProperty = CastField<FStructProperty>(Property))
{
if (StructProperty->Struct->GetFName() == NAME_Vector)
{
FVector* Vector = StructProperty->ContainerPtrToValuePtr<FVector>(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<FVector2D>(Container);
ValueStr = FString::Printf(TEXT("%f %f"), Vector->X, Vector->Y);
}
else if (StructProperty->Struct->GetFName() == NAME_IntPoint)
{
FIntPoint* IntPoint = StructProperty->ContainerPtrToValuePtr<FIntPoint>(Container);
ValueStr = FString::Printf(TEXT("%d %d"), IntPoint->X, IntPoint->Y);
}
else
{
ValueStr = GetStructToString<FMujocoFriction>(StructProperty, Container);
if (ValueStr.IsEmpty())
ValueStr = GetStructToString<FMujocoSolImp>(StructProperty, Container);
if (ValueStr.IsEmpty())
ValueStr = GetStructToString<FMujocoSolRef>(StructProperty, Container);
if (ValueStr.IsEmpty())
ValueStr = GetStructToString<FMujocoRelPose>(StructProperty, Container);
if (ValueStr.IsEmpty())
ValueStr = GetStructToString<FMujocoPolyCoef>(StructProperty, Container);
if (ValueStr.IsEmpty())
ValueStr = GetStructToString<FMujocoGear>(StructProperty, Container);
}
}
else
{
UE_LOG(LogMujoco, Warning, TEXT("Unsupported property type %s"), *Property->GetClass()->GetName());
}
return ValueStr;
}
template <> FString GetAttributeMappingForProperty<UMujocoBodyComponent>(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 <typename T> FString GetPropertyAttributeName(FProperty* Prop)
{
return GetAttributeMappingForProperty<T>(Prop);
}
template <typename T> void ApplyStructAttributes(tinyxml2::XMLElement* XmlElem, const T& StructData)
{
for (TFieldIterator<FProperty> It(T::StaticStruct()); It; ++It)
{
FProperty* Prop = *It;
const FString AttributeName = GetPropertyAttributeName<T>(Prop);
if (AttributeName.IsEmpty())
{
continue;
}
const FString Value = GetPropertyValueAsString(Prop, reinterpret_cast<void*>(const_cast<T*>(&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<FEnumProperty>(FMujocoActuatorV2::StaticStruct()->FindPropertyByName("Type")))
{
int64 EnumValue = TypeProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(TypeProperty->ContainerPtrToValuePtr<void>(&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<FEnumProperty>(FMujocoEquality::StaticStruct()->FindPropertyByName("Type")))
{
int64 EnumValue = TypeProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(TypeProperty->ContainerPtrToValuePtr<void>(&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<FEnumProperty>(FMujocoTendon::StaticStruct()->FindPropertyByName("Type")))
{
int64 EnumValue = TypeProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(TypeProperty->ContainerPtrToValuePtr<void>(&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<USceneComponent*> CameraComponents;
SpringArmComponent->GetChildrenComponents(true, CameraComponents);
for (USceneComponent* ChildComponent : CameraComponents)
{
if (UCameraComponent* CameraComponent = Cast<UCameraComponent>(ChildComponent))
{
if (!VisitCameraComponent(SCSNode, CameraComponent, Element, SpringArmComponent))
{
return false;
}
break;
}
}
}
else
{
for (USCS_Node* ChildNode : SCSNode->GetChildNodes())
{
if (UCameraComponent* CameraComponent = Cast<UCameraComponent>(ChildNode->GetActualComponentTemplate(SpringArmComponent->GetTypedOuter<UBlueprintGeneratedClass>())))
{
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<FProperty> PropIt(BodyComponent->GetClass()); PropIt; ++PropIt)
{
FProperty* Prop = *PropIt;
const FString AttributeName = GetPropertyAttributeName<UMujocoBodyComponent>(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<UMujocoBodyComponent>(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter<UBlueprintGeneratedClass>())))
{
if (!VisitBodyComponent(ChildNode, ChildBodyComponent, BodyElement))
{
return false;
}
}
else if (UMujocoGeomComponent* GeomComponent = Cast<UMujocoGeomComponent>(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter<UBlueprintGeneratedClass>())))
{
if (!VisitGeomComponent(ChildNode, GeomComponent, BodyElement))
{
return false;
}
}
else if (UMujocoJointComponent* JointComponent = Cast<UMujocoJointComponent>(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter<UBlueprintGeneratedClass>())))
{
if (!VisitJointComponent(ChildNode, JointComponent, BodyElement))
{
return false;
}
}
else if (UMujocoSiteComponent* SiteComponent = Cast<UMujocoSiteComponent>(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter<UBlueprintGeneratedClass>())))
{
if (!VisitSiteComponent(ChildNode, SiteComponent, BodyElement))
{
return false;
}
}
else if (UCameraComponent* CameraComponent = Cast<UCameraComponent>(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter<UBlueprintGeneratedClass>())))
{
if (!VisitCameraComponent(ChildNode, CameraComponent, BodyElement))
{
return false;
}
}
else if (USpringArmComponent* SpringArmComponent = Cast<USpringArmComponent>(ChildNode->GetActualComponentTemplate(BodyComponent->GetTypedOuter<UBlueprintGeneratedClass>())))
{
if (!VisitSpringArm(ChildNode, SpringArmComponent, BodyElement))
{
return false;
}
}
}
}
else
{
TArray<USceneComponent*> ChildComponents;
BodyComponent->GetChildrenComponents(false, ChildComponents);
TSet<USceneComponent*> UniqueChildrens;
for (USceneComponent* ChildComponent : ChildComponents)
{
UniqueChildrens.Add(ChildComponent);
}
for (USceneComponent* ChildComponent : UniqueChildrens)
{
if (UMujocoBodyComponent* ChildBodyComponent = Cast<UMujocoBodyComponent>(ChildComponent))
{
if (!VisitBodyComponent(nullptr, ChildBodyComponent, BodyElement))
{
return false;
}
}
else if (UMujocoGeomComponent* GeomComponent = Cast<UMujocoGeomComponent>(ChildComponent))
{
if (!VisitGeomComponent(nullptr, GeomComponent, BodyElement))
{
return false;
}
}
else if (UMujocoJointComponent* JointComponent = Cast<UMujocoJointComponent>(ChildComponent))
{
if (!VisitJointComponent(nullptr, JointComponent, BodyElement))
{
return false;
}
}
else if (UMujocoSiteComponent* SiteComponent = Cast<UMujocoSiteComponent>(ChildComponent))
{
if (!VisitSiteComponent(nullptr, SiteComponent, BodyElement))
{
return false;
}
}
else if (UCameraComponent* CameraComponent = Cast<UCameraComponent>(ChildComponent))
{
if (!VisitCameraComponent(nullptr, CameraComponent, BodyElement))
{
return false;
}
}
else if (USpringArmComponent* SpringArmComponent = Cast<USpringArmComponent>(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 <typename T> TMap<FString, FString> ProcessProperties(void* StructPtr)
{
TMap<FString, FString> OptionsMap;
UStruct* Struct = T::StaticStruct();
for (TFieldIterator<FProperty> PropIt(Struct); PropIt; ++PropIt)
{
FProperty* Property = *PropIt;
const FString AttributeName = GetAttributeMappingForProperty<T>(Property);
if (AttributeName.IsEmpty())
{
continue;
}
FName OverridePropName(*FString::Printf(TEXT("bOverride%s"), *Property->GetNameCPP()));
if (FProperty* OverrideProperty = Struct->FindPropertyByName(OverridePropName))
{
void* OverridePtr = OverrideProperty->ContainerPtrToValuePtr<void>(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<FString, FString> CompilerOptions = ProcessProperties<FMujocoCompileOptions>(&Data->Compiler);
TMap<FString, FString> MujocoOptions = ProcessProperties<FMujocoOptions>(&Data->Options);
TMap<FString, FString> SimulationFlags = ProcessProperties<FMujocoSimulationFlags>(&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<AActor>(BlueprintAsset->GeneratedClass->GetDefaultObject());
if (!DefaultActor)
{
return false;
}
if (!BlueprintAsset->SimpleConstructionScript)
{
return false;
}
if (UBlueprintGeneratedClass* BPGeneratedClass = Cast<UBlueprintGeneratedClass>(BlueprintAsset->GeneratedClass))
{
for (USCS_Node* SCSNode : BlueprintAsset->SimpleConstructionScript->GetRootNodes())
{
if (!SCSNode)
{
continue;
}
if (UMujocoBodyComponent* BodyComp = Cast<UMujocoBodyComponent>(SCSNode->GetActualComponentTemplate(BPGeneratedClass)))
{
if (!VisitBodyComponent(SCSNode, BodyComp, Root))
{
return false;
}
}
else if (UMujocoActuatorComponent* ActuatorComp = Cast<UMujocoActuatorComponent>(SCSNode->GetActualComponentTemplate(BPGeneratedClass)))
{
if (!VisitActuatorComponent(SCSNode, ActuatorComp, Root))
{
return false;
}
}
else if (UMujocoEqualityComponent* EqualityComp = Cast<UMujocoEqualityComponent>(SCSNode->GetActualComponentTemplate(BPGeneratedClass)))
{
if (!VisitEqualityComponent(SCSNode, EqualityComp, Root))
{
return false;
}
}
else if (UMujocoTendonComponent* TendonComp = Cast<UMujocoTendonComponent>(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<UMujocoBodyComponent>(Component))
{
if (BodyComp->GetAttachParent())
{
continue;
}
if (!VisitBodyComponent(nullptr, BodyComp, Root))
{
return false;
}
}
else if (UMujocoActuatorComponent* ActuatorComp = Cast<UMujocoActuatorComponent>(Component))
{
if (!VisitActuatorComponent(nullptr, ActuatorComp, Root))
{
return false;
}
}
else if (UMujocoEqualityComponent* EqualityComp = Cast<UMujocoEqualityComponent>(Component))
{
if (!VisitEqualityComponent(nullptr, EqualityComp, Root))
{
return false;
}
}
else if (UMujocoTendonComponent* TendonComp = Cast<UMujocoTendonComponent>(Component))
{
if (!VisitTendonComponent(nullptr, TendonComp, Root))
{
return false;
}
}
}
return true;
}
TUniquePtr<tinyxml2::XMLDocument> FMujocoXmlGenerator::GenerateMujocoXml(
const TObjectPtr<UMujocoExportOptions>& ExportOptions,
const TArray<UObject*>& Objects,
const FString& ExportFilename,
TMap<FString, FString> ContactExclusion)
{
TUniquePtr<tinyxml2::XMLDocument> Doc = MakeUnique<tinyxml2::XMLDocument>();
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)
// <exclude body1="Body_Base" body2="Body_Rotation_Pitch"/>
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<UBlueprint>(Object))
{
ParseBlueprintAsset(Blueprint, Root);
}
else if (AActor* Actor = Cast<AActor>(Object))
{
ParseActorAsset(Actor, Root);
}
}
TSet<FString> 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<UMujocoGeomComponent>(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<UMujocoStaticMesh>(GeomObj->MujocoStaticMesh))
{
FFileHelper::SaveStringToFile(MujocoMesh->ObjFileData, *MeshFilename);
}
}
MeshElement->SetAttribute("file", TCHAR_TO_ANSI(*MeshFilename));
}
return MoveTemp(Doc);
}