implementation of drecon in unity 2022 lts
forked from:
https://github.com/joanllobera/marathon-envs
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1271 lines
62 KiB
1271 lines
62 KiB
9 months ago
|
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using System.Text;
|
||
|
using System.Xml.Linq;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace Unity.MLAgents
|
||
|
{
|
||
|
public class MarathonSpawner : MonoBehaviour
|
||
|
{
|
||
|
[Tooltip("The MuJoCo xml file to parse")]
|
||
|
/**< \brief The MuJoCo xml file to parse*/
|
||
|
public TextAsset Xml;
|
||
|
|
||
|
public Material Material;
|
||
|
public PhysicMaterial PhysicMaterial;
|
||
|
|
||
|
[Tooltip("When True, UnityEngine.Time.fixedDeltaTime is set by <option timestep=xxx>")]
|
||
|
/**< \brief When True, UnityEngine.Time.fixedDeltaTime is set by <option timestep=xxx>*/
|
||
|
public bool UseXmlTimestep = true;
|
||
|
|
||
|
[Tooltip("During XML parsing, debug messages are sent to the console")]
|
||
|
/**< \brief During XML parsing, debug messages are sent to the console*/
|
||
|
public bool DebugOutput;
|
||
|
|
||
|
[Tooltip("Used for 2D MuJoCo objects (hopper, walker, etc)")]
|
||
|
/**< \brief Used for 2D MuJoCo objects (hopper, walker, etc)*/
|
||
|
public bool Force2D;
|
||
|
|
||
|
[Tooltip("The random noise applied at the start of each episode to improve training variance")]
|
||
|
/**< \brief The random noise applied at the start of each episode to improve training variance"*/
|
||
|
public float OnGenerateApplyRandom = 0.005f;
|
||
|
|
||
|
[Tooltip("The default density (Mujoco's default value is 1000)")]
|
||
|
/**< \brief The default density (Mujoco's default value is 1000)"*/
|
||
|
public float DefaultDensity = 1000f;
|
||
|
|
||
|
[Tooltip("Use to scale the power of the motors (default is 1)")]
|
||
|
/**< \brief Use to scale the power of the motors (default is 1)"*/
|
||
|
public float MotorScale = 1f;
|
||
|
|
||
|
|
||
|
XElement _root;
|
||
|
Stack<XElement> _childClassStack;
|
||
|
Dictionary<string, XElement> _jointXDocs;
|
||
|
|
||
|
bool _hasParsed;
|
||
|
bool _useWorldSpace = false;
|
||
|
Quaternion _orginalTransformRotation;
|
||
|
Vector3 _orginalTransformPosition;
|
||
|
|
||
|
public void SpawnFromXml()
|
||
|
{
|
||
|
LoadXml(Xml.text);
|
||
|
Parse();
|
||
|
}
|
||
|
|
||
|
void LoadXml(string str)
|
||
|
{
|
||
|
_root = XElement.Parse(str);
|
||
|
}
|
||
|
|
||
|
void DebugPrint(string str)
|
||
|
{
|
||
|
if (DebugOutput)
|
||
|
print(str);
|
||
|
}
|
||
|
|
||
|
void Parse()
|
||
|
{
|
||
|
XElement element = _root;
|
||
|
var name = element.Name.LocalName;
|
||
|
DebugPrint($"- Begin");
|
||
|
|
||
|
_jointXDocs = new Dictionary<string, XElement>();
|
||
|
ParseCompilerOptions(_root);
|
||
|
|
||
|
_childClassStack = new Stack<XElement>();
|
||
|
|
||
|
foreach (var attribute in element.Attributes())
|
||
|
{
|
||
|
switch (attribute.Name.LocalName)
|
||
|
{
|
||
|
case "model":
|
||
|
gameObject.name = attribute.Value;
|
||
|
break;
|
||
|
default:
|
||
|
throw new NotImplementedException();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// when using world space, geoms will be created in global space
|
||
|
// so setting the parent object to 0,0,0 allows us to fix that
|
||
|
_orginalTransformRotation = this.gameObject.transform.rotation;
|
||
|
_orginalTransformPosition = this.gameObject.transform.position;
|
||
|
this.gameObject.transform.rotation = new Quaternion();
|
||
|
this.gameObject.transform.position = new Vector3();
|
||
|
|
||
|
var joints = ParseBody(element.Element("worldbody"), this.gameObject);
|
||
|
var mujocoJoints = ParseGears(element.Element("actuator"), joints);
|
||
|
var mujocoSensors = ParseSensors(element.Element("sensor"), GetComponentsInChildren<Collider>());
|
||
|
|
||
|
if (Material != null)
|
||
|
foreach (var item in GetComponentsInChildren<Renderer>())
|
||
|
{
|
||
|
item.material = Material;
|
||
|
}
|
||
|
|
||
|
if (PhysicMaterial != null)
|
||
|
foreach (var item in GetComponentsInChildren<Collider>())
|
||
|
{
|
||
|
item.material = PhysicMaterial;
|
||
|
}
|
||
|
|
||
|
if (Force2D)
|
||
|
{
|
||
|
foreach (var item in GetComponentsInChildren<Rigidbody>())
|
||
|
item.constraints = RigidbodyConstraints.FreezePositionZ;
|
||
|
}
|
||
|
|
||
|
if (this.gameObject.layer != 0)
|
||
|
{
|
||
|
foreach (var item in GetComponentsInChildren<Collider>())
|
||
|
item.gameObject.layer = this.gameObject.layer;
|
||
|
}
|
||
|
|
||
|
// restore positions and orientation
|
||
|
gameObject.transform.rotation = _orginalTransformRotation;
|
||
|
gameObject.transform.position = _orginalTransformPosition;
|
||
|
|
||
|
GetComponent<MarathonAgent>().SetMarathonJoints(mujocoJoints);
|
||
|
GetComponent<MarathonAgent>().SetMarathonSensors(mujocoSensors);
|
||
|
}
|
||
|
|
||
|
public void ApplyRandom()
|
||
|
{
|
||
|
if (OnGenerateApplyRandom != 0f)
|
||
|
{
|
||
|
float velocityScaler = 5000f;
|
||
|
foreach (var item in GetComponent<MarathonAgent>().MarathonJoints)
|
||
|
{
|
||
|
var r = ((UnityEngine.Random.value * (OnGenerateApplyRandom * 2)) - OnGenerateApplyRandom);
|
||
|
// float r = 0f;
|
||
|
var childRb = item.Joint.GetComponent<Rigidbody>();
|
||
|
if (childRb != null)
|
||
|
{
|
||
|
ConfigurableJoint configurableJoint = item.Joint as ConfigurableJoint;
|
||
|
var t = Vector3.zero;
|
||
|
t.x = r * velocityScaler;
|
||
|
configurableJoint.targetAngularVelocity = t;
|
||
|
childRb.angularVelocity = t;
|
||
|
t = Vector3.zero;
|
||
|
t.x = ((UnityEngine.Random.value * (OnGenerateApplyRandom * 2)) - OnGenerateApplyRandom) * 5;
|
||
|
t.y = ((UnityEngine.Random.value * (OnGenerateApplyRandom * 2)) - OnGenerateApplyRandom) * 5 +
|
||
|
1;
|
||
|
t.z = ((UnityEngine.Random.value * (OnGenerateApplyRandom * 2)) - OnGenerateApplyRandom) * 5;
|
||
|
childRb.velocity = t;
|
||
|
var angX = configurableJoint.angularXDrive;
|
||
|
angX.positionSpring = 1f;
|
||
|
var scale = item.MaximumForce * Mathf.Pow(Mathf.Abs(r), 3);
|
||
|
angX.positionDamper = Mathf.Max(1f, scale);
|
||
|
angX.maximumForce = Mathf.Max(1f, scale);
|
||
|
configurableJoint.angularXDrive = angX;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ParseCompilerOptions(XElement xdoc)
|
||
|
{
|
||
|
foreach (var element in xdoc.Elements("option"))
|
||
|
{
|
||
|
foreach (var attribute in element.Attributes())
|
||
|
{
|
||
|
switch (attribute.Name.LocalName)
|
||
|
{
|
||
|
case "integrator":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "iterations":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "solver":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "timestep":
|
||
|
if (UseXmlTimestep)
|
||
|
{
|
||
|
var timestep = (float)Convert.ToDouble(attribute.Value, System.Globalization.CultureInfo.InvariantCulture);
|
||
|
Time.fixedDeltaTime = timestep;
|
||
|
}
|
||
|
else
|
||
|
DebugPrint($"--*** IGNORING timestep=\"{attribute.Value}\" as UseXmlTimestep == false");
|
||
|
|
||
|
break;
|
||
|
case "gravity":
|
||
|
Physics.gravity = MarathonHelper.ParsePosition(attribute.Value);
|
||
|
break;
|
||
|
default:
|
||
|
DebugPrint($"*** MISSING --> {name}.{attribute.Name.LocalName}");
|
||
|
throw new NotImplementedException(attribute.Name.LocalName);
|
||
|
#pragma warning disable
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach (var element in xdoc.Elements("compiler"))
|
||
|
{
|
||
|
foreach (var attribute in element.Attributes())
|
||
|
{
|
||
|
switch (attribute.Name.LocalName)
|
||
|
{
|
||
|
case "angle":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "coordinate":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
if (attribute.Value.ToLower() == "global")
|
||
|
_useWorldSpace = true;
|
||
|
else if (attribute.Value.ToLower() == "local")
|
||
|
_useWorldSpace = false;
|
||
|
break;
|
||
|
case "inertiafromgeom":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "settotalmass":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
default:
|
||
|
DebugPrint($"*** MISSING --> {name}.{attribute.Name.LocalName}");
|
||
|
throw new NotImplementedException(attribute.Name.LocalName);
|
||
|
#pragma warning disable
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class JointDocQueueItem
|
||
|
{
|
||
|
public XElement JointXDoc { get; set; }
|
||
|
public GeomItem ParentGeom { get; set; }
|
||
|
public GameObject ParentBody { get; set; }
|
||
|
}
|
||
|
|
||
|
private class GeomItem
|
||
|
{
|
||
|
public GameObject Geom;
|
||
|
public float? Lenght;
|
||
|
public float? Size;
|
||
|
public Vector3 Lenght3D;
|
||
|
public Vector3 Start;
|
||
|
public Vector3 End;
|
||
|
public List<GameObject> Bones;
|
||
|
|
||
|
public GeomItem()
|
||
|
{
|
||
|
Bones = new List<GameObject>();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
List<KeyValuePair<string, Joint>> ParseBody(XElement xdoc, GameObject parentBody, GeomItem geom = null,
|
||
|
GeomItem parentGeom = null, List<JointDocQueueItem> jointDocsQueue = null)
|
||
|
{
|
||
|
var joints = new List<KeyValuePair<string, Joint>>();
|
||
|
jointDocsQueue = jointDocsQueue ?? new List<JointDocQueueItem>();
|
||
|
var bodies = new List<GameObject>();
|
||
|
|
||
|
var childClass = xdoc.Attribute("childclass");
|
||
|
if (childClass != null)
|
||
|
{
|
||
|
var childDoc = _root.Element("default")
|
||
|
?.Elements("default")
|
||
|
.FirstOrDefault(x => x.Attribute("class")?.Value == childClass.Value);
|
||
|
_childClassStack.Push(childDoc);
|
||
|
}
|
||
|
|
||
|
foreach (var element in xdoc.Elements("light"))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
foreach (var element in xdoc.Elements("camera"))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
foreach (var element in xdoc.Elements("joint"))
|
||
|
{
|
||
|
jointDocsQueue.Add(new JointDocQueueItem
|
||
|
{
|
||
|
JointXDoc = element,
|
||
|
ParentGeom = geom,
|
||
|
ParentBody = parentBody,
|
||
|
});
|
||
|
}
|
||
|
|
||
|
foreach (var element in xdoc.Elements("geom"))
|
||
|
{
|
||
|
geom = ParseGeom(element, parentBody);
|
||
|
|
||
|
if (parentGeom != null && jointDocsQueue?.Count > 0)
|
||
|
{
|
||
|
foreach (var jointDocQueueItem in jointDocsQueue)
|
||
|
{
|
||
|
var js = ParseJoint(
|
||
|
jointDocQueueItem.JointXDoc,
|
||
|
jointDocQueueItem.ParentGeom,
|
||
|
geom,
|
||
|
jointDocQueueItem.ParentBody);
|
||
|
if (js != null) joints.AddRange(js);
|
||
|
}
|
||
|
}
|
||
|
else if (parentGeom != null)
|
||
|
{
|
||
|
var fixedJoint = parentGeom.Geom.AddComponent<FixedJoint>();
|
||
|
fixedJoint.connectedBody = geom.Geom.GetComponent<Rigidbody>();
|
||
|
}
|
||
|
|
||
|
jointDocsQueue.Clear();
|
||
|
parentGeom = geom;
|
||
|
}
|
||
|
|
||
|
foreach (var element in xdoc.Elements("body"))
|
||
|
{
|
||
|
var body = new GameObject();
|
||
|
bodies.Add(body);
|
||
|
body.transform.parent = this.transform;
|
||
|
ApplyClassToBody(element, body, parentBody);
|
||
|
var newJoints = ParseBody(element, body, geom, parentGeom, jointDocsQueue);
|
||
|
if (newJoints != null) joints.AddRange(newJoints);
|
||
|
}
|
||
|
|
||
|
foreach (var item in bodies)
|
||
|
GameObject.Destroy(item);
|
||
|
|
||
|
if (childClass != null)
|
||
|
_childClassStack.Pop();
|
||
|
|
||
|
return joints;
|
||
|
}
|
||
|
|
||
|
void ApplyClassToBody(XElement classElement, GameObject body, GameObject parentBody)
|
||
|
{
|
||
|
foreach (var attribute in classElement.Attributes())
|
||
|
{
|
||
|
switch (attribute.Name.LocalName)
|
||
|
{
|
||
|
case "name":
|
||
|
body.name = attribute.Value;
|
||
|
break;
|
||
|
case "pos":
|
||
|
if (_useWorldSpace)
|
||
|
body.transform.position = MarathonHelper.ParsePosition(attribute.Value);
|
||
|
else
|
||
|
{
|
||
|
body.transform.position =
|
||
|
MarathonHelper.ParsePosition(attribute.Value) + parentBody.transform.position;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
case "quat":
|
||
|
if (_useWorldSpace)
|
||
|
body.transform.rotation = MarathonHelper.ParseQuaternion(attribute.Value);
|
||
|
else
|
||
|
{
|
||
|
body.transform.rotation = MarathonHelper.ParseQuaternion(attribute.Value) *
|
||
|
parentBody.transform.rotation;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
case "childclass":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "euler":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
default:
|
||
|
DebugPrint($"*** MISSING --> {name}.{attribute.Name.LocalName}");
|
||
|
throw new NotImplementedException(attribute.Name.LocalName);
|
||
|
#pragma warning disable
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
GeomItem ParseGeom(XElement xdoc, GameObject parent)
|
||
|
{
|
||
|
GeomItem geom = null;
|
||
|
|
||
|
if (xdoc == null)
|
||
|
return null;
|
||
|
XElement element = BuildFromClasses("geom", xdoc);
|
||
|
|
||
|
var type = element.Attribute("type")?.Value;
|
||
|
if (type == null)
|
||
|
{
|
||
|
DebugPrint($"--- WARNING: ParseGeom: no type found in geom. Ignoring ({element.ToString()}");
|
||
|
return geom;
|
||
|
}
|
||
|
|
||
|
float size;
|
||
|
float? size2 = null;
|
||
|
DebugPrint($"ParseGeom: Creating type:{type} name:{element.Attribute("name")?.Value}");
|
||
|
geom = new GeomItem();
|
||
|
Vector3 start;
|
||
|
Vector3 end;
|
||
|
Vector3 offset;
|
||
|
switch (type)
|
||
|
{
|
||
|
case "capsule":
|
||
|
if (element.Attribute("size")?.Value?.Split()?.Length > 1)
|
||
|
{
|
||
|
size = (float)Convert.ToDouble(element.Attribute("size")?.Value.Split()[0], System.Globalization.CultureInfo.InvariantCulture);
|
||
|
size2 = (float)Convert.ToDouble(element.Attribute("size")?.Value.Split()[1], System.Globalization.CultureInfo.InvariantCulture);
|
||
|
}
|
||
|
else
|
||
|
size = (float)Convert.ToDouble(element.Attribute("size")?.Value, System.Globalization.CultureInfo.InvariantCulture);
|
||
|
|
||
|
var fromto = element.Attribute("fromto")?.Value;
|
||
|
if (fromto == null)
|
||
|
{
|
||
|
var posAttribute = element.Attribute("pos")?.Value;
|
||
|
Vector3 centerPos = Vector3.zero;
|
||
|
if (posAttribute != null)
|
||
|
{
|
||
|
var rawPos = MarathonHelper.ParsePosition(posAttribute);
|
||
|
centerPos = centerPos - rawPos;
|
||
|
}
|
||
|
|
||
|
start = centerPos;
|
||
|
end = centerPos;
|
||
|
var zaxisAttribute = element.Attribute("zaxis")?.Value;
|
||
|
Vector3 zaxis = Vector3.up;
|
||
|
if (zaxisAttribute != null)
|
||
|
zaxis = MarathonHelper.ParseAxis(zaxisAttribute);
|
||
|
var zaxisScaled = zaxis * size2.Value;
|
||
|
start -= zaxisScaled;
|
||
|
end += zaxisScaled;
|
||
|
// end += zaxisScaled * 2;
|
||
|
start = MarathonHelper.RightToLeft(start);
|
||
|
end = MarathonHelper.RightToLeft(end);
|
||
|
DebugPrint($"ParseGeom: Creating type:{type} size:{size}");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DebugPrint($"ParseGeom: Creating type:{type} fromto:{fromto} size:{size}");
|
||
|
start = MarathonHelper.ParseFrom(fromto);
|
||
|
end = MarathonHelper.ParseTo(fromto);
|
||
|
}
|
||
|
|
||
|
geom.Geom = parent.CreateBetweenPoints(start, end, size, _useWorldSpace, this.gameObject);
|
||
|
offset = end - start;
|
||
|
geom.Lenght = offset.magnitude; //
|
||
|
geom.Size = size;
|
||
|
geom.Lenght3D = offset;
|
||
|
geom.Start = start;
|
||
|
geom.End = end;
|
||
|
|
||
|
break;
|
||
|
case "sphere":
|
||
|
size = (float)Convert.ToDouble(element.Attribute("size")?.Value, System.Globalization.CultureInfo.InvariantCulture);
|
||
|
var pos = element.Attribute("pos")?.Value ?? "0 0 0";
|
||
|
DebugPrint($"ParseGeom: Creating type:{type} pos:{pos} size:{size}");
|
||
|
geom.Geom = parent.CreateAtPoint(MarathonHelper.ParsePosition(pos), size, _useWorldSpace, this.gameObject);
|
||
|
geom.Size = size;
|
||
|
break;
|
||
|
default:
|
||
|
DebugPrint(
|
||
|
$"--- WARNING: ParseGeom: {type} geom is not implemented. Ignoring ({element.ToString()}");
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
var rb = geom.Geom.AddComponent<Rigidbody>();
|
||
|
rb.useGravity = true;
|
||
|
rb.SetDensity(DefaultDensity);
|
||
|
rb.mass = rb.mass; // ref: https://forum.unity.com/threads/rigidbody-setdensity-doesnt-work.322911/
|
||
|
|
||
|
ApplyClassToGeom(element, geom.Geom, parent);
|
||
|
|
||
|
return geom;
|
||
|
}
|
||
|
|
||
|
void ApplyClassToGeom(XElement classElement, GameObject geom, GameObject parentBody)
|
||
|
{
|
||
|
foreach (var attribute in classElement.Attributes())
|
||
|
{
|
||
|
switch (attribute.Name.LocalName)
|
||
|
{
|
||
|
case "name": // optional
|
||
|
// Name of the geom.
|
||
|
geom.name = attribute.Value;
|
||
|
break;
|
||
|
case "class": // optional
|
||
|
// Defaults class for setting unspecified attributes.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "type": // [plane, hfield, sphere, capsule, ellipsoid, cylinder, box, mesh], "sphere"
|
||
|
// Type of geometric shape.
|
||
|
// Handled in object init
|
||
|
break;
|
||
|
case "contype": // int, "1"
|
||
|
// This attribute and the next specify 32-bit integer bitmasks used for contact
|
||
|
// filtering of dynamically generated contact pairs. See Collision detection in
|
||
|
// the Computation chapter. Two geoms can collide if the contype of one geom is
|
||
|
// compatible with the conaffinity of the other geom or vice versa.
|
||
|
// Compatible means that the two bitmasks have a common bit set to 1.
|
||
|
// Note: contype="0" conaffinity="0" disables physics contacts
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "conaffinity": // int, "1"
|
||
|
// Bitmask for contact filtering; see contype above.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "condim": // int, "3"
|
||
|
// The dimensionality of the contact space for a dynamically generated contact
|
||
|
// pair is set to the maximum of the condim values of the two participating geoms.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "group": // int, "0"
|
||
|
// This attribute specifies an integer group to which the geom belongs.
|
||
|
// The only effect on the physics is at compile time, when body masses and inertias are
|
||
|
// inferred from geoms selected based on their group; see inertiagrouprange attribute of compiler.
|
||
|
// At runtime this attribute is used by the visualizer to enable and disable the rendering of
|
||
|
// entire geom groups. It can also be used as a tag for custom computations.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "size": // real(3), "0 0 0"
|
||
|
// Geom size parameters. The number of required parameters and their meaning depends on the
|
||
|
// geom type as documented under the type attribute. Here we only provide a summary.
|
||
|
// All required size parameters must be positive; the internal defaults correspond to invalid
|
||
|
// settings. Note that when a non-mesh geom type references a mesh, a geometric primitive of
|
||
|
// that type is fitted to the mesh. In that case the sizes are obtained from the mesh, and
|
||
|
// the geom size parameters are ignored. Thus the number and description of required size
|
||
|
// parameters in the table below only apply to geoms that do not reference meshes.
|
||
|
// Type Number Description
|
||
|
// plane 3 X half-size; Y half-size; spacing between square grid lines for rendering.
|
||
|
// hfield 0 The geom sizes are ignored and the height field sizes are used instead.
|
||
|
// sphere 1 Radius of the sphere.
|
||
|
// capsule 1 or 2 Radius of the capsule; half-length of the cylinder part when not using the fromto specification.
|
||
|
// ellipsoid 3 X radius; Y radius; Z radius.
|
||
|
// cylinder 1 or 2 Radius of the cylinder; half-length of the cylinder when not using the fromto specification.
|
||
|
// box 3 X half-size; Y half-size; Z half-size.
|
||
|
// mesh 0 The geom sizes are ignored and the mesh sizes are used instead.
|
||
|
// Handled at object init
|
||
|
break;
|
||
|
case "material": // optional
|
||
|
// If specified, this attribute applies a material to the geom. The material determines the visual properties of
|
||
|
// the geom. The only exception is color: if the rgba attribute below is different from its internal default, it takes
|
||
|
// precedence while the remaining material properties are still applied. Note that if the same material is referenced
|
||
|
// from multiple geoms (as well as sites and tendons) and the user changes some of its properties at runtime,
|
||
|
// these changes will take effect immediately for all model elements referencing the material. This is because the
|
||
|
// compiler saves the material and its properties as a separate element in mjModel, and the elements using this
|
||
|
// material only keep a reference to it.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "rgba": // real(4), "0.5 0.5 0.5 1"
|
||
|
// Instead of creating material assets and referencing them, this attribute can be used
|
||
|
// to set color and transparency only. This is not as flexible as the material mechanism,
|
||
|
// but is more convenient and is often sufficient. If the value of this attribute is
|
||
|
// different from the internal default, it takes precedence over the material.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "friction": //real(3), "1 0.005 0.0001"
|
||
|
// Contact friction parameters for dynamically generated contact pairs.
|
||
|
// The first number is the sliding friction, acting along both axes of the tangent plane.
|
||
|
// The second number is the torsional friction, acting around the contact normal.
|
||
|
// The third number is the rolling friction, acting around both axes of the tangent plane.
|
||
|
// The friction parameters for the contact pair are computed as the element-wise maximum of
|
||
|
// the geom-specific parameters. See also Parameters section in the Computation chapter.
|
||
|
float? slidingFriction = null;
|
||
|
float? torsionalFriction = null;
|
||
|
float? rollingFriction = null;
|
||
|
var frictionSplit = attribute.Value.Split(' ');
|
||
|
if (frictionSplit?.Length >= 3)
|
||
|
rollingFriction = (float)Convert.ToDouble(frictionSplit[2], System.Globalization.CultureInfo.InvariantCulture);
|
||
|
if (frictionSplit?.Length >= 2)
|
||
|
torsionalFriction = (float)Convert.ToDouble(frictionSplit[1], System.Globalization.CultureInfo.InvariantCulture);
|
||
|
if (frictionSplit?.Length >= 1)
|
||
|
slidingFriction = (float)Convert.ToDouble(frictionSplit[0], System.Globalization.CultureInfo.InvariantCulture);
|
||
|
var physicMaterial = geom.GetComponent<Collider>()?.material;
|
||
|
physicMaterial.staticFriction = slidingFriction.Value;
|
||
|
if (rollingFriction.HasValue)
|
||
|
physicMaterial.dynamicFriction = rollingFriction.Value;
|
||
|
else if (torsionalFriction.HasValue)
|
||
|
physicMaterial.dynamicFriction = torsionalFriction.Value;
|
||
|
else
|
||
|
physicMaterial.dynamicFriction = slidingFriction.Value;
|
||
|
break;
|
||
|
case "mass": // optional
|
||
|
// If this attribute is specified, the density attribute below is ignored and the geom density
|
||
|
// is computed from the given mass, using the geom shape and the assumption of uniform density.
|
||
|
// The computed density is then used to obtain the geom inertia. Recall that the geom mass and
|
||
|
// inerta are only used during compilation, to infer the body mass and inertia if necessary.
|
||
|
// At runtime only the body inertial properties affect the simulation;
|
||
|
// the geom mass and inertia are not even saved in mjModel.
|
||
|
// DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
geom.GetComponent<Rigidbody>().mass = (float)Convert.ToDouble(attribute.Value, System.Globalization.CultureInfo.InvariantCulture);
|
||
|
break;
|
||
|
case "density": // "1000"
|
||
|
// Material density used to compute the geom mass and inertia. The computation is based on the
|
||
|
// geom shape and the assumption of uniform density. The internal default of 1000 is the density
|
||
|
// of water in SI units. This attribute is used only when the mass attribute above is unspecified.
|
||
|
var density = (float)Convert.ToDouble(attribute.Value, System.Globalization.CultureInfo.InvariantCulture);
|
||
|
var rb = geom.GetComponent<Rigidbody>();
|
||
|
rb.SetDensity(density);
|
||
|
rb.mass = rb
|
||
|
.mass; // ref: https://forum.unity.com/threads/rigidbody-setdensity-doesnt-work.322911/
|
||
|
break;
|
||
|
case "solmix": // "1"
|
||
|
// This attribute specifies the weight used for averaging of constraint solver parameters.
|
||
|
// Recall that the solver parameters for a dynamically generated geom pair are obtained as a
|
||
|
// weighted average of the geom-specific parameters.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "solref":
|
||
|
// Constraint solver parameters for contact simulation. See Solver parameters.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "solimp":
|
||
|
// Constraint solver parameters for contact simulation. See Solver parameters.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "margin": // "0"
|
||
|
// Distance threshold below which contacts are detected and included in the global array mjData.contact.
|
||
|
// This however does not mean that contact force will be generated. A contact is considered active only
|
||
|
// if the distance between the two geom surfaces is below margin-gap. Recall that constraint impedance
|
||
|
// can be a function of distance, as explained in Solver parameters. The quantity this function is
|
||
|
// applied to is the distance between the two geoms minus the margin plus the gap.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "gap": // "0"
|
||
|
// This attribute is used to enable the generation of inactive contacts, i.e. contacts that are ignored
|
||
|
//by the constraint solver but are included in mjData.contact for the purpose of custom computations.
|
||
|
// When this value is positive, geom distances between margin and margin-gap correspond to such
|
||
|
// inactive contacts.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "fromto": // optional
|
||
|
// This attribute can only be used with capsule and cylinder geoms. It provides an alternative specification
|
||
|
// of the geom length as well as the frame position and orientation. The six numbers are the 3D coordinates
|
||
|
// of one point followed by the 3D coordinates of another point. The cylinder geom (or cylinder part of the
|
||
|
// capsule geom) connects these two points, with the +Z axis of the geom's frame oriented from the first
|
||
|
// towards the second point. The frame orientation is obtained with the same procedure as the zaxis
|
||
|
// attribute described in Frame orientations. The frame position is in the middle between the two points.
|
||
|
// If this attribute is specified, the remaining position and orientation-related attributes are ignored.
|
||
|
// Handled at object init
|
||
|
break;
|
||
|
case "pos": // "0 0 0"
|
||
|
// Position of the geom frame, in local or global coordinates as determined by the coordinate
|
||
|
// attribute of compiler.
|
||
|
// Handled at object init
|
||
|
break;
|
||
|
case "hfield": // optional
|
||
|
// This attribute must be specified if and only if the geom type is "hfield".
|
||
|
// It references the height field asset to be instantiated at the position and orientation of the geom frame.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "mesh": // optional
|
||
|
// If the geom type is "mesh", this attribute is required. It references the mesh asset to be instantiated.
|
||
|
// This attribute can also be specified if the geom type corresponds to a geometric primitive, namely one
|
||
|
// of "sphere", "capsule", "cylinder", "ellipsoid", "box". In that case the primitive is automatically
|
||
|
// fitted to the mesh asset referenced here. The fitting procedure uses either the equivalent
|
||
|
// inertia box or the axis-aligned bounding box of the mesh, as determined by the attribute fitaabb
|
||
|
// of compiler. The resulting size of the fitted geom is usually what one would expect, but if not,
|
||
|
// it can be further adjusted with the fitscale attribute below. In the compiled mjModel the geom is
|
||
|
// represented as a regular geom of the specified primitive type, and there is no reference to the mesh
|
||
|
// used for fitting.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "quat": // "1 0 0 0"
|
||
|
// If the quaternion is known, this is the preferred was to specify the frame orientation because it does
|
||
|
// not involve conversions. Instead it is normalized to unit length and copied into mjModel during compilation.
|
||
|
// When a model is saved as MJCF, all frame orientations are expressed as quaternions using this attribute.
|
||
|
if (_useWorldSpace)
|
||
|
geom.transform.rotation = MarathonHelper.ParseQuaternion(attribute.Value);
|
||
|
else
|
||
|
geom.transform.localRotation =
|
||
|
MarathonHelper.ParseQuaternion(attribute.Value) * parentBody.transform.rotation;
|
||
|
break;
|
||
|
case "axisangle": // optional
|
||
|
// These are the quantities (x, y, z, a) mentioned above. The last number is the angle of rotation,
|
||
|
// in degrees or radians as specified by the angle attribute of compiler. The first three numbers determine
|
||
|
// a 3D vector which is the rotation axis. This vector is normalized to unit length during compilation,
|
||
|
// so the user can specify a vector of any non-zero length. Keep in mind that the rotation is right-handed;
|
||
|
// if the direction of the vector (x, y, z) is reversed this will result in the opposite rotation.
|
||
|
// Changing the sign of a can also be used to specify the opposite rotation.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "xyaxes": // optional
|
||
|
// The first 3 numbers are the X axis of the frame. The next 3 numbers are the Y axis of the frame,
|
||
|
// which is automatically made orthogonal to the X axis. The Z axis is then defined as the
|
||
|
// cross-product of the X and Y axes.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "zaxis": // optional
|
||
|
// The Z axis of the frame. The compiler finds the minimal rotation that maps the vector (0,0,1)
|
||
|
// into the vector specified here. This determines the X and Y axes of the frame implicitly.
|
||
|
// This is useful for geoms with rotational symmetry around the Z axis, as well as lights - which
|
||
|
// are oriented along the Z axis of their frame.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "euler": // optional
|
||
|
// Rotation angles around three coordinate axes. The sequence of axes around which these rotations are applied
|
||
|
// is determined by the eulerseq attribute of compiler and is the same for the entire model.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "fitscale": // "1"
|
||
|
// This attribute is used only when a primitive geometric type is being fitted to a mesh asset.
|
||
|
// The scale specified here is relative to the output of the automated fitting procedure. The default value
|
||
|
// of 1 leaves the result unchanged, a value of 2 makes all sizes of the fitted geom two times larger.
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "user":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
default:
|
||
|
{
|
||
|
DebugPrint($"*** MISSING --> {name}.{attribute.Name.LocalName}");
|
||
|
throw new NotImplementedException(attribute.Name.LocalName);
|
||
|
#pragma warning disable
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Joint FixedJoint(GameObject parent)
|
||
|
{
|
||
|
parent.gameObject.AddComponent<FixedJoint>();
|
||
|
var joint = parent.GetComponent<Joint>();
|
||
|
return joint;
|
||
|
}
|
||
|
|
||
|
XElement BuildFromClasses(string type, XElement xdoc)
|
||
|
{
|
||
|
XElement subClass = new XElement(type);
|
||
|
if (_childClassStack.Count > 0 && _childClassStack.Peek()?.Element(type) != null)
|
||
|
subClass = _childClassStack.Peek()?.Element(type);
|
||
|
var defaultClass = _root.Element("default")?.Element(type) ?? new XElement(type);
|
||
|
xdoc = xdoc ?? new XElement(type);
|
||
|
var attributes =
|
||
|
defaultClass.Attributes()
|
||
|
.Concat(subClass.Attributes())
|
||
|
.Concat(xdoc.Attributes())
|
||
|
.GroupBy(x => x.Name)
|
||
|
.Select(x => x.Last());
|
||
|
XElement element = new XElement(type, attributes);
|
||
|
if (element.Attribute("class") != null)
|
||
|
element = AddClass(type, element.Attribute("class").Value, element);
|
||
|
return element;
|
||
|
}
|
||
|
|
||
|
XElement AddClass(string type, string subClassName, XElement xdoc, XElement nestingRef = null)
|
||
|
{
|
||
|
if (nestingRef == null)
|
||
|
nestingRef = _root.Element("default");
|
||
|
foreach (var item in nestingRef.Attributes("class"))
|
||
|
{
|
||
|
if (item.Value == subClassName)
|
||
|
{
|
||
|
// found class
|
||
|
var subClass = nestingRef.Element(type) ?? new XElement(type);
|
||
|
var attributes =
|
||
|
xdoc.Attributes()
|
||
|
.Concat(subClass.Attributes())
|
||
|
.GroupBy(x => x.Name)
|
||
|
.Select(x => x.Last());
|
||
|
XElement element = new XElement(type, attributes);
|
||
|
return element;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
foreach (var item in nestingRef.Elements("default"))
|
||
|
{
|
||
|
if (nestingRef != null)
|
||
|
xdoc = AddClass(type, subClassName, xdoc, item);
|
||
|
}
|
||
|
|
||
|
return xdoc;
|
||
|
}
|
||
|
|
||
|
List<KeyValuePair<string, Joint>> ParseJoint(XElement xdoc, GeomItem parentGeom, GeomItem childGeom,
|
||
|
GameObject body)
|
||
|
{
|
||
|
_jointXDocs.Add(xdoc.Attribute("name").Value, xdoc);
|
||
|
var joints = new List<KeyValuePair<string, Joint>>();
|
||
|
|
||
|
GameObject bone = null;
|
||
|
var childRidgedBody = childGeom.Geom.GetComponent<Rigidbody>();
|
||
|
var parentRidgedBody = parentGeom.Geom.GetComponent<Rigidbody>();
|
||
|
|
||
|
if (xdoc == null)
|
||
|
return joints;
|
||
|
XElement element = BuildFromClasses("joint", xdoc);
|
||
|
|
||
|
var type = element.Attribute("type")?.Value;
|
||
|
if (type == null)
|
||
|
{
|
||
|
DebugPrint($"--- WARNING: ParseJoint: no type found. Assuming Hinge: ({element.ToString()}");
|
||
|
type = "hinge";
|
||
|
}
|
||
|
|
||
|
Joint joint = null;
|
||
|
Type jointType;
|
||
|
string jointName = element.Attribute("name")?.Value;
|
||
|
switch (type)
|
||
|
{
|
||
|
case "hinge":
|
||
|
DebugPrint($"ParseJoint: Creating type:{type} ");
|
||
|
jointType = typeof(HingeJoint);
|
||
|
break;
|
||
|
case "free":
|
||
|
DebugPrint($"ParseJoint: Creating type:{type} ");
|
||
|
jointType = typeof(FixedJoint);
|
||
|
break;
|
||
|
default:
|
||
|
DebugPrint(
|
||
|
$"--- WARNING: ParseJoint: joint type '{type}' is not implemented. Ignoring ({element.ToString()}");
|
||
|
return joints;
|
||
|
}
|
||
|
|
||
|
Joint existingJoint = childGeom.Bones
|
||
|
.SelectMany(x => x.GetComponents<Joint>())
|
||
|
.FirstOrDefault(y => y.connectedBody == parentRidgedBody);
|
||
|
if (existingJoint)
|
||
|
{
|
||
|
bone = new GameObject();
|
||
|
bone.transform.SetPositionAndRotation(childGeom.Geom.transform.position,
|
||
|
childGeom.Geom.transform.rotation);
|
||
|
bone.transform.localScale = childGeom.Geom.transform.localScale;
|
||
|
bone.transform.parent = childGeom.Geom.transform;
|
||
|
bone.name = jointName;
|
||
|
var boneRidgedBody = bone.AddComponent<Rigidbody>();
|
||
|
boneRidgedBody.useGravity = false;
|
||
|
joint = bone.AddComponent(jointType) as Joint;
|
||
|
existingJoint.connectedBody = boneRidgedBody;
|
||
|
childGeom.Bones.Add(bone);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
joint = childGeom.Geom.AddComponent(jointType) as Joint;
|
||
|
if (!childGeom.Bones.Contains(childGeom.Geom))
|
||
|
childGeom.Bones.Add(childGeom.Geom);
|
||
|
}
|
||
|
|
||
|
Collider boneCollider = null;
|
||
|
if (bone != null)
|
||
|
boneCollider = CopyCollider(bone, childGeom.Geom);
|
||
|
joint.connectedBody = parentRidgedBody;
|
||
|
|
||
|
ApplyClassToJoint(element, joint, childGeom, body, bone ?? childGeom.Geom);
|
||
|
|
||
|
if (boneCollider != null)
|
||
|
Destroy(boneCollider);
|
||
|
|
||
|
// force as configurable
|
||
|
if (jointType == typeof(HingeJoint))
|
||
|
joint = ToConfigurable(joint as HingeJoint);
|
||
|
|
||
|
joints.Add(new KeyValuePair<string, Joint>(jointName, joint));
|
||
|
return joints;
|
||
|
}
|
||
|
|
||
|
Collider CopyCollider(GameObject target, GameObject source)
|
||
|
{
|
||
|
var sourceCollider = source.GetComponent<Collider>();
|
||
|
var sourceCapsule = sourceCollider as CapsuleCollider;
|
||
|
var sphereCollider = sourceCollider as SphereCollider;
|
||
|
Collider targetCollider = null;
|
||
|
if (sourceCapsule != null)
|
||
|
{
|
||
|
var targetCapsule = target.AddComponent<CapsuleCollider>();
|
||
|
targetCollider = targetCapsule as Collider;
|
||
|
targetCapsule.center = sourceCapsule.center;
|
||
|
targetCapsule.radius = sourceCapsule.radius;
|
||
|
targetCapsule.height = sourceCapsule.height;
|
||
|
targetCapsule.direction = sourceCapsule.direction;
|
||
|
}
|
||
|
else if (sphereCollider != null)
|
||
|
{
|
||
|
var targetSphere = target.AddComponent<SphereCollider>();
|
||
|
targetCollider = targetSphere as Collider;
|
||
|
targetSphere.center = sphereCollider.center;
|
||
|
targetSphere.radius = sphereCollider.radius;
|
||
|
}
|
||
|
else
|
||
|
throw new NotImplementedException();
|
||
|
|
||
|
if (sourceCollider != null)
|
||
|
{
|
||
|
targetCollider.isTrigger = sourceCollider.isTrigger;
|
||
|
targetCollider.material = sourceCollider.material;
|
||
|
}
|
||
|
|
||
|
return targetCollider;
|
||
|
}
|
||
|
|
||
|
|
||
|
void ApplyClassToJoint(XElement classElement, Joint joint, GeomItem baseGeom, GameObject body, GameObject bone)
|
||
|
{
|
||
|
HingeJoint hingeJoint = joint as HingeJoint;
|
||
|
FixedJoint fixedJoint = joint as FixedJoint;
|
||
|
ConfigurableJoint configurableJoint = joint as ConfigurableJoint;
|
||
|
JointSpring spring = hingeJoint?.spring ?? new JointSpring();
|
||
|
JointMotor motor = hingeJoint?.motor ?? new JointMotor();
|
||
|
JointLimits limits = hingeJoint?.limits ?? new JointLimits();
|
||
|
Vector3 jointOffset = Vector3.zero;
|
||
|
foreach (var attribute in classElement.Attributes())
|
||
|
{
|
||
|
switch (attribute.Name.LocalName)
|
||
|
{
|
||
|
case "armature":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "damping":
|
||
|
spring.damper = (float)Convert.ToDouble(attribute.Value, System.Globalization.CultureInfo.InvariantCulture);
|
||
|
break;
|
||
|
case "limited":
|
||
|
if (hingeJoint != null)
|
||
|
hingeJoint.useLimits = Convert.ToBoolean(attribute.Value, System.Globalization.CultureInfo.InvariantCulture);
|
||
|
break;
|
||
|
case "axis":
|
||
|
var axis = MarathonHelper.ParseAxis(attribute.Value);
|
||
|
axis = baseGeom.Geom.transform.InverseTransformDirection(axis);
|
||
|
joint.axis = axis;
|
||
|
break;
|
||
|
case "name":
|
||
|
if (bone != null && string.IsNullOrEmpty(bone.name))
|
||
|
bone.name = attribute.Value;
|
||
|
break;
|
||
|
case "pos":
|
||
|
jointOffset = MarathonHelper.ParsePosition(attribute.Value);
|
||
|
break;
|
||
|
|
||
|
case "range":
|
||
|
if (hingeJoint != null)
|
||
|
{
|
||
|
limits.min = MarathonHelper.ParseGetMin(attribute.Value);
|
||
|
limits.max = MarathonHelper.ParseGetMax(attribute.Value);
|
||
|
limits.bounceMinVelocity = 0f;
|
||
|
hingeJoint.useLimits = true;
|
||
|
}
|
||
|
else if (configurableJoint != null)
|
||
|
{
|
||
|
var low = configurableJoint.lowAngularXLimit;
|
||
|
low.limit = MarathonHelper.ParseGetMin(attribute.Value);
|
||
|
configurableJoint.lowAngularXLimit = low;
|
||
|
var high = configurableJoint.highAngularXLimit;
|
||
|
high.limit = MarathonHelper.ParseGetMax(attribute.Value);
|
||
|
configurableJoint.highAngularXLimit = high;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
case "class":
|
||
|
break;
|
||
|
case "type":
|
||
|
// NOTE: handle in setup
|
||
|
break;
|
||
|
case "solimplimit":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "solreflimit":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "stiffness":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
case "margin":
|
||
|
DebugPrint($"{name} {attribute.Name.LocalName}={attribute.Value}");
|
||
|
break;
|
||
|
default:
|
||
|
DebugPrint($"*** MISSING --> {name}.{attribute.Name.LocalName}");
|
||
|
throw new NotImplementedException(attribute.Name.LocalName);
|
||
|
#pragma warning disable
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_useWorldSpace)
|
||
|
{
|
||
|
var jointPos = jointOffset;
|
||
|
var localAxis = joint.transform.InverseTransformPoint(jointPos);
|
||
|
joint.anchor = localAxis;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var jointPos = body.transform.position;
|
||
|
jointPos -= bone.transform.position;
|
||
|
jointPos += jointOffset;
|
||
|
var localAxis = bone.transform.InverseTransformDirection(jointPos);
|
||
|
joint.anchor = localAxis;
|
||
|
}
|
||
|
|
||
|
if (hingeJoint != null)
|
||
|
{
|
||
|
hingeJoint.spring = spring;
|
||
|
hingeJoint.motor = motor;
|
||
|
hingeJoint.limits = limits;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static Vector3 GetLocalOrthoDirection(Transform transform, Vector3 worldDir)
|
||
|
{
|
||
|
worldDir = worldDir.normalized;
|
||
|
|
||
|
float dotX = Vector3.Dot(worldDir, transform.right);
|
||
|
float dotY = Vector3.Dot(worldDir, transform.up);
|
||
|
float dotZ = Vector3.Dot(worldDir, transform.forward);
|
||
|
|
||
|
float absDotX = Mathf.Abs(dotX);
|
||
|
float absDotY = Mathf.Abs(dotY);
|
||
|
float absDotZ = Mathf.Abs(dotZ);
|
||
|
|
||
|
Vector3 orthoDirection = Vector3.right;
|
||
|
if (absDotY > absDotX && absDotY > absDotZ) orthoDirection = Vector3.up;
|
||
|
if (absDotZ > absDotX && absDotZ > absDotY) orthoDirection = Vector3.forward;
|
||
|
|
||
|
if (Vector3.Dot(worldDir, transform.rotation * orthoDirection) < 0f) orthoDirection = -orthoDirection;
|
||
|
|
||
|
return orthoDirection;
|
||
|
}
|
||
|
|
||
|
|
||
|
List<MarathonJoint> ParseGears(XElement xdoc, List<KeyValuePair<string, Joint>> joints)
|
||
|
{
|
||
|
var mujocoJoints = new List<MarathonJoint>();
|
||
|
var name = "motor";
|
||
|
|
||
|
var elements = xdoc?.Elements(name);
|
||
|
if (elements == null)
|
||
|
return mujocoJoints;
|
||
|
foreach (var element in elements)
|
||
|
{
|
||
|
mujocoJoints.AddRange(ParseGear(element, joints));
|
||
|
}
|
||
|
|
||
|
foreach (var mujocoJoint in mujocoJoints)
|
||
|
{
|
||
|
mujocoJoint.TrueBase = FindTrueBase(mujocoJoint.Joint, mujocoJoints);
|
||
|
mujocoJoint.TrueTarget = FindTrueTarget(mujocoJoint.Joint, mujocoJoints);
|
||
|
mujocoJoint.MaximumForce = (mujocoJoint.Joint as ConfigurableJoint).angularXDrive.maximumForce;
|
||
|
|
||
|
Vector3 worldSwingAxis = mujocoJoint.Joint.axis;
|
||
|
Vector3 axis2 = GetLocalOrthoDirection(mujocoJoint.TrueTarget.transform, worldSwingAxis);
|
||
|
Vector3 twistAxis = GetLocalOrthoDirection(mujocoJoint.TrueTarget.transform,
|
||
|
mujocoJoint.TrueTarget.transform.position - mujocoJoint.TrueBase.connectedBody.transform.position);
|
||
|
Vector3 secondaryAxis = Vector3.Cross(axis2, twistAxis);
|
||
|
(mujocoJoint.Joint as ConfigurableJoint).secondaryAxis = secondaryAxis;
|
||
|
}
|
||
|
|
||
|
return mujocoJoints;
|
||
|
}
|
||
|
|
||
|
ConfigurableJoint FindTrueBase(Joint joint, List<MarathonJoint> mJoints)
|
||
|
{
|
||
|
ConfigurableJoint configurableJoint = joint as ConfigurableJoint;
|
||
|
var rb = configurableJoint.GetComponent<Rigidbody>();
|
||
|
if (rb.useGravity)
|
||
|
return configurableJoint;
|
||
|
ConfigurableJoint parentRb = mJoints
|
||
|
.Select(x => x.Joint)
|
||
|
.First(x => x.connectedBody == rb)
|
||
|
as ConfigurableJoint;
|
||
|
return FindTrueBase(parentRb, mJoints);
|
||
|
}
|
||
|
|
||
|
Transform FindTrueTarget(Joint joint, List<MarathonJoint> mJoints)
|
||
|
{
|
||
|
var targetRB = joint.connectedBody;
|
||
|
var rb = joint.GetComponent<Rigidbody>();
|
||
|
if (targetRB.useGravity)
|
||
|
return targetRB.transform;
|
||
|
var target = targetRB.GetComponent<ConfigurableJoint>();
|
||
|
if (target == null)
|
||
|
return targetRB.transform;
|
||
|
return FindTrueTarget(target, mJoints);
|
||
|
}
|
||
|
|
||
|
List<MarathonJoint> ParseGear(XElement xdoc, List<KeyValuePair<string, Joint>> joints)
|
||
|
{
|
||
|
var mujocoJoints = new List<MarathonJoint>();
|
||
|
XElement element = BuildFromClasses("gear", xdoc);
|
||
|
|
||
|
string jointName = element.Attribute("joint")?.Value;
|
||
|
if (jointName == null)
|
||
|
{
|
||
|
DebugPrint($"--- WARNING: ParseGears: no jointName found. Ignoring ({element.ToString()}");
|
||
|
return mujocoJoints;
|
||
|
}
|
||
|
|
||
|
var matches = joints.Where(x => x.Key.ToLowerInvariant() == jointName.ToLowerInvariant())
|
||
|
?.Select(x => x.Value);
|
||
|
if (matches == null)
|
||
|
{
|
||
|
DebugPrint(
|
||
|
$"--- ERROR: ParseGears: joint:'{jointName}' was not found in joints. Ignoring ({element.ToString()}");
|
||
|
return mujocoJoints;
|
||
|
}
|
||
|
|
||
|
foreach (Joint joint in matches)
|
||
|
{
|
||
|
HingeJoint hingeJoint = joint as HingeJoint;
|
||
|
ConfigurableJoint configurableJoint = joint as ConfigurableJoint;
|
||
|
JointSpring spring = new JointSpring();
|
||
|
JointMotor motor = new JointMotor();
|
||
|
if (hingeJoint != null)
|
||
|
{
|
||
|
spring = hingeJoint.spring;
|
||
|
hingeJoint.useSpring = false;
|
||
|
hingeJoint.useMotor = true;
|
||
|
motor = hingeJoint.motor;
|
||
|
motor.freeSpin = true;
|
||
|
}
|
||
|
|
||
|
if (configurableJoint != null)
|
||
|
{
|
||
|
configurableJoint.rotationDriveMode = RotationDriveMode.XYAndZ;
|
||
|
}
|
||
|
|
||
|
var mujocoJoint = new MarathonJoint
|
||
|
{
|
||
|
Joint = joint,
|
||
|
JointName = jointName,
|
||
|
};
|
||
|
ApplyClassToGear(element, joint, mujocoJoint);
|
||
|
|
||
|
mujocoJoints.Add(mujocoJoint);
|
||
|
}
|
||
|
|
||
|
return mujocoJoints;
|
||
|
}
|
||
|
|
||
|
void ApplyClassToGear(XElement classElement, Joint joint, MarathonJoint mujocoJoint)
|
||
|
{
|
||
|
HingeJoint hingeJoint = joint as HingeJoint;
|
||
|
FixedJoint fixedJoint = joint as FixedJoint;
|
||
|
ConfigurableJoint configurableJoint = joint as ConfigurableJoint;
|
||
|
JointSpring spring = hingeJoint?.spring ?? new JointSpring();
|
||
|
JointMotor motor = hingeJoint?.motor ?? new JointMotor();
|
||
|
JointLimits limits = hingeJoint?.limits ?? new JointLimits();
|
||
|
var angularXDrive = configurableJoint?.angularXDrive ?? new JointDrive();
|
||
|
foreach (var attribute in classElement.Attributes())
|
||
|
{
|
||
|
switch (attribute.Name.LocalName)
|
||
|
{
|
||
|
case "joint":
|
||
|
break;
|
||
|
case "ctrllimited":
|
||
|
var ctrlLimited = Convert.ToBoolean(attribute.Value, System.Globalization.CultureInfo.InvariantCulture);
|
||
|
mujocoJoint.CtrlLimited = ctrlLimited;
|
||
|
break;
|
||
|
case "ctrlrange":
|
||
|
var ctrlRange = MarathonHelper.ParseVector2(attribute.Value);
|
||
|
mujocoJoint.CtrlRange = ctrlRange;
|
||
|
break;
|
||
|
case "gear":
|
||
|
var gear = (float)Convert.ToDouble(attribute.Value, System.Globalization.CultureInfo.InvariantCulture);
|
||
|
gear *= MotorScale;
|
||
|
//var gear = 200;
|
||
|
mujocoJoint.Gear = gear;
|
||
|
spring.spring = gear;
|
||
|
motor.force = gear;
|
||
|
angularXDrive.maximumForce = gear;
|
||
|
angularXDrive.positionDamper = 1;
|
||
|
angularXDrive.positionSpring = 1;
|
||
|
break;
|
||
|
case "name":
|
||
|
var objName = attribute.Value;
|
||
|
mujocoJoint.Name = objName;
|
||
|
break;
|
||
|
default:
|
||
|
DebugPrint($"*** MISSING --> {name}.{attribute.Name.LocalName}");
|
||
|
throw new NotImplementedException(attribute.Name.LocalName);
|
||
|
#pragma warning disable
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (hingeJoint != null)
|
||
|
{
|
||
|
hingeJoint.spring = spring;
|
||
|
hingeJoint.motor = motor;
|
||
|
hingeJoint.limits = limits;
|
||
|
}
|
||
|
|
||
|
if (configurableJoint != null)
|
||
|
{
|
||
|
configurableJoint.angularXDrive = angularXDrive;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
List<MarathonSensor> ParseSensors(XElement xdoc, IEnumerable<Collider> colliders)
|
||
|
{
|
||
|
var mujocoSensors = new List<MarathonSensor>();
|
||
|
var name = "touch";
|
||
|
|
||
|
var elements = xdoc?.Elements(name);
|
||
|
if (elements == null)
|
||
|
return mujocoSensors;
|
||
|
foreach (var element in elements)
|
||
|
{
|
||
|
var mujocoSensor = new MarathonSensor
|
||
|
{
|
||
|
Name = element.Attribute("name")?.Value,
|
||
|
SiteName = element.Attribute("site")?.Value,
|
||
|
};
|
||
|
var match = colliders
|
||
|
.Where(x => x.name == mujocoSensor.SiteName)
|
||
|
.FirstOrDefault();
|
||
|
if (match != null)
|
||
|
mujocoSensor.SiteObject = match;
|
||
|
else
|
||
|
throw new NotImplementedException();
|
||
|
mujocoSensors.Add(mujocoSensor);
|
||
|
}
|
||
|
|
||
|
return mujocoSensors;
|
||
|
}
|
||
|
|
||
|
public static Joint ToConfigurable(HingeJoint hingeJoint)
|
||
|
{
|
||
|
if (hingeJoint.useMotor)
|
||
|
{
|
||
|
throw new NotImplementedException();
|
||
|
}
|
||
|
|
||
|
ConfigurableJoint configurableJoint = hingeJoint.gameObject.AddComponent<ConfigurableJoint>();
|
||
|
configurableJoint.anchor = hingeJoint.anchor;
|
||
|
configurableJoint.autoConfigureConnectedAnchor = hingeJoint.autoConfigureConnectedAnchor;
|
||
|
configurableJoint.axis = new Vector3(0 - hingeJoint.axis.x, 0 - hingeJoint.axis.y, 0 - hingeJoint.axis.z);
|
||
|
configurableJoint.breakForce = hingeJoint.breakForce;
|
||
|
configurableJoint.breakTorque = hingeJoint.breakTorque;
|
||
|
configurableJoint.connectedAnchor = hingeJoint.connectedAnchor;
|
||
|
configurableJoint.connectedBody = hingeJoint.connectedBody;
|
||
|
configurableJoint.enableCollision = hingeJoint.enableCollision;
|
||
|
configurableJoint.secondaryAxis = Vector3.zero;
|
||
|
|
||
|
configurableJoint.xMotion = ConfigurableJointMotion.Locked;
|
||
|
configurableJoint.yMotion = ConfigurableJointMotion.Locked;
|
||
|
configurableJoint.zMotion = ConfigurableJointMotion.Locked;
|
||
|
|
||
|
configurableJoint.angularXMotion =
|
||
|
hingeJoint.useLimits ? ConfigurableJointMotion.Limited : ConfigurableJointMotion.Free;
|
||
|
configurableJoint.angularYMotion = ConfigurableJointMotion.Locked;
|
||
|
configurableJoint.angularZMotion = ConfigurableJointMotion.Locked;
|
||
|
|
||
|
|
||
|
SoftJointLimit limit = new SoftJointLimit();
|
||
|
limit.limit = hingeJoint.limits.max;
|
||
|
limit.bounciness = hingeJoint.limits.bounciness;
|
||
|
configurableJoint.highAngularXLimit = limit;
|
||
|
|
||
|
limit = new SoftJointLimit();
|
||
|
limit.limit = hingeJoint.limits.min;
|
||
|
limit.bounciness = hingeJoint.limits.bounciness;
|
||
|
configurableJoint.lowAngularXLimit = limit;
|
||
|
|
||
|
SoftJointLimitSpring limitSpring = new SoftJointLimitSpring();
|
||
|
limitSpring.damper = hingeJoint.useSpring ? hingeJoint.spring.damper : 0f;
|
||
|
limitSpring.spring = hingeJoint.useSpring ? hingeJoint.spring.spring : 0f;
|
||
|
configurableJoint.angularXLimitSpring = limitSpring;
|
||
|
|
||
|
GameObject.DestroyImmediate(hingeJoint);
|
||
|
|
||
|
return configurableJoint;
|
||
|
}
|
||
|
}
|
||
|
}
|