Skip to content

Commit 1e0f960

Browse files
author
Chris Elion
authored
[MLA-240] Physic-based pose generation (#4166)
* hierarchy POC * WIP * abstract class * clean up init * separate files * cleanup, unit test * add Articulation util * sensor WIP * ArticulationBody sensor, starting docs * docstrings, cleanup * hierarchy tests, transform operators * unit tests * use Pose struct instead * delete QTTransform * rename * renames and compile fixes * remove ArticulationBodySensor* for now * revert CrawlerAgent changes * rename
1 parent 1d9df31 commit 1e0f960

16 files changed

+665
-28
lines changed

com.unity.ml-agents.extensions/Runtime/RuntimeExample.cs

Lines changed: 0 additions & 6 deletions
This file was deleted.

com.unity.ml-agents.extensions/Runtime/Sensors.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#if UNITY_2020_1_OR_NEWER
2+
3+
using System.Collections.Generic;
4+
using UnityEngine;
5+
6+
namespace Unity.MLAgents.Extensions.Sensors
7+
{
8+
9+
public class ArticulationBodyPoseExtractor : PoseExtractor
10+
{
11+
ArticulationBody[] m_Bodies;
12+
13+
public ArticulationBodyPoseExtractor(ArticulationBody rootBody)
14+
{
15+
if (!rootBody.isRoot)
16+
{
17+
Debug.Log("Must pass ArticulationBody.isRoot");
18+
return;
19+
}
20+
21+
var bodies = rootBody.GetComponentsInChildren <ArticulationBody>();
22+
if (bodies[0] != rootBody)
23+
{
24+
Debug.Log("Expected root body at index 0");
25+
return;
26+
}
27+
28+
var numBodies = bodies.Length;
29+
m_Bodies = bodies;
30+
int[] parentIndices = new int[numBodies];
31+
parentIndices[0] = -1;
32+
33+
var bodyToIndex = new Dictionary<ArticulationBody, int>();
34+
for (var i = 0; i < numBodies; i++)
35+
{
36+
bodyToIndex[m_Bodies[i]] = i;
37+
}
38+
39+
for (var i = 1; i < numBodies; i++)
40+
{
41+
var body = m_Bodies[i];
42+
var parent = body.GetComponentInParent<ArticulationBody>();
43+
parentIndices[i] = bodyToIndex[parent];
44+
}
45+
46+
SetParentIndices(parentIndices);
47+
}
48+
49+
protected override Pose GetPoseAt(int index)
50+
{
51+
var body = m_Bodies[index];
52+
var go = body.gameObject;
53+
var t = go.transform;
54+
return new Pose { rotation = t.rotation, position = t.position };
55+
}
56+
57+
58+
}
59+
}
60+
#endif // UNITY_2020_1_OR_NEWER

com.unity.ml-agents.extensions/Runtime/RuntimeExample.cs.meta renamed to com.unity.ml-agents.extensions/Runtime/Sensors/ArticulationBodyPoseExtractor.cs.meta

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using System;
2+
3+
using Unity.MLAgents.Sensors;
4+
5+
namespace Unity.MLAgents.Extensions.Sensors
6+
{
7+
[Serializable]
8+
public struct PhysicsSensorSettings
9+
{
10+
/// <summary>
11+
/// Whether to use model space (relative to the root body) translations as observations.
12+
/// </summary>
13+
public bool UseModelSpaceTranslations;
14+
15+
/// <summary>
16+
/// Whether to use model space (relative to the root body) rotatoins as observations.
17+
/// </summary>
18+
public bool UseModelSpaceRotations;
19+
20+
/// <summary>
21+
/// Whether to use local space (relative to the parent body) translations as observations.
22+
/// </summary>
23+
public bool UseLocalSpaceTranslations;
24+
25+
/// <summary>
26+
/// Whether to use local space (relative to the parent body) translations as observations.
27+
/// </summary>
28+
public bool UseLocalSpaceRotations;
29+
30+
/// <summary>
31+
/// Creates a PhysicsSensorSettings with reasonable default values.
32+
/// </summary>
33+
/// <returns></returns>
34+
public static PhysicsSensorSettings Default()
35+
{
36+
return new PhysicsSensorSettings
37+
{
38+
UseModelSpaceTranslations = true,
39+
UseModelSpaceRotations = true,
40+
};
41+
}
42+
43+
/// <summary>
44+
/// Whether any model space observations are being used.
45+
/// </summary>
46+
public bool UseModelSpace
47+
{
48+
get { return UseModelSpaceTranslations || UseModelSpaceRotations; }
49+
}
50+
51+
/// <summary>
52+
/// Whether any local space observations are being used.
53+
/// </summary>
54+
public bool UseLocalSpace
55+
{
56+
get { return UseLocalSpaceTranslations || UseLocalSpaceRotations; }
57+
}
58+
59+
60+
/// <summary>
61+
/// The number of floats needed to represent a given number of transforms.
62+
/// </summary>
63+
/// <param name="numTransforms"></param>
64+
/// <returns></returns>
65+
public int TransformSize(int numTransforms)
66+
{
67+
int obsPerTransform = 0;
68+
obsPerTransform += UseModelSpaceTranslations ? 3 : 0;
69+
obsPerTransform += UseModelSpaceRotations ? 4 : 0;
70+
obsPerTransform += UseLocalSpaceTranslations ? 3 : 0;
71+
obsPerTransform += UseLocalSpaceRotations ? 4 : 0;
72+
73+
return numTransforms * obsPerTransform;
74+
}
75+
}
76+
77+
internal static class ObservationWriterPhysicsExtensions
78+
{
79+
/// <summary>
80+
/// Utility method for writing a PoseExtractor to an ObservationWriter.
81+
/// </summary>
82+
/// <param name="writer"></param>
83+
/// <param name="settings"></param>
84+
/// <param name="poseExtractor"></param>
85+
/// <param name="baseOffset">The offset into the ObservationWriter to start writing at.</param>
86+
/// <returns>The number of observations written.</returns>
87+
public static int WritePoses(this ObservationWriter writer, PhysicsSensorSettings settings, PoseExtractor poseExtractor, int baseOffset = 0)
88+
{
89+
var offset = baseOffset;
90+
if (settings.UseModelSpace)
91+
{
92+
foreach (var pose in poseExtractor.ModelSpacePoses)
93+
{
94+
if(settings.UseModelSpaceTranslations)
95+
{
96+
writer.Add(pose.position, offset);
97+
offset += 3;
98+
}
99+
if (settings.UseModelSpaceRotations)
100+
{
101+
writer.Add(pose.rotation, offset);
102+
offset += 4;
103+
}
104+
}
105+
}
106+
107+
if (settings.UseLocalSpace)
108+
{
109+
foreach (var pose in poseExtractor.LocalSpacePoses)
110+
{
111+
if(settings.UseLocalSpaceTranslations)
112+
{
113+
writer.Add(pose.position, offset);
114+
offset += 3;
115+
}
116+
if (settings.UseLocalSpaceRotations)
117+
{
118+
writer.Add(pose.rotation, offset);
119+
offset += 4;
120+
}
121+
}
122+
}
123+
124+
return offset - baseOffset;
125+
}
126+
}
127+
}

com.unity.ml-agents.extensions/Tests/Editor/EditorExampleTest.cs.meta renamed to com.unity.ml-agents.extensions/Runtime/Sensors/PhysicsSensorSettings.cs.meta

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
using System.Collections.Generic;
2+
using UnityEngine;
3+
4+
namespace Unity.MLAgents.Extensions.Sensors
5+
{
6+
/// <summary>
7+
/// Abstract class for managing the transforms of a hierarchy of objects.
8+
/// This could be GameObjects or Monobehaviours in the scene graph, but this is
9+
/// not a requirement; for example, the objects could be rigid bodies whose hierarchy
10+
/// is defined by Joint configurations.
11+
///
12+
/// Poses are either considered in model space, which is relative to a root body,
13+
/// or in local space, which is relative to their parent.
14+
/// </summary>
15+
public abstract class PoseExtractor
16+
{
17+
int[] m_ParentIndices;
18+
Pose[] m_ModelSpacePoses;
19+
Pose[] m_LocalSpacePoses;
20+
21+
/// <summary>
22+
/// Read access to the model space transforms.
23+
/// </summary>
24+
public IList<Pose> ModelSpacePoses
25+
{
26+
get { return m_ModelSpacePoses; }
27+
}
28+
29+
/// <summary>
30+
/// Read access to the local space transforms.
31+
/// </summary>
32+
public IList<Pose> LocalSpacePoses
33+
{
34+
get { return m_LocalSpacePoses; }
35+
}
36+
37+
/// <summary>
38+
/// Number of transforms in the hierarchy (read-only).
39+
/// </summary>
40+
public int NumPoses
41+
{
42+
get { return m_ModelSpacePoses?.Length ?? 0; }
43+
}
44+
45+
/// <summary>
46+
/// Initialize with the mapping of parent indices.
47+
/// The 0th element is assumed to be -1, indicating that it's the root.
48+
/// </summary>
49+
/// <param name="parentIndices"></param>
50+
protected void SetParentIndices(int[] parentIndices)
51+
{
52+
m_ParentIndices = parentIndices;
53+
var numTransforms = parentIndices.Length;
54+
m_ModelSpacePoses = new Pose[numTransforms];
55+
m_LocalSpacePoses = new Pose[numTransforms];
56+
}
57+
58+
/// <summary>
59+
/// Return the world space Pose of the i'th object.
60+
/// </summary>
61+
/// <param name="index"></param>
62+
/// <returns></returns>
63+
protected abstract Pose GetPoseAt(int index);
64+
65+
/// <summary>
66+
/// Update the internal model space transform storage based on the underlying system.
67+
/// </summary>
68+
public void UpdateModelSpacePoses()
69+
{
70+
if (m_ModelSpacePoses == null)
71+
{
72+
return;
73+
}
74+
75+
var worldTransform = GetPoseAt(0);
76+
var worldToModel = worldTransform.Inverse();
77+
78+
for (var i = 0; i < m_ModelSpacePoses.Length; i++)
79+
{
80+
var currentTransform = GetPoseAt(i);
81+
m_ModelSpacePoses[i] = worldToModel.Multiply(currentTransform);
82+
}
83+
}
84+
85+
/// <summary>
86+
/// Update the internal model space transform storage based on the underlying system.
87+
/// </summary>
88+
public void UpdateLocalSpacePoses()
89+
{
90+
if (m_LocalSpacePoses == null)
91+
{
92+
return;
93+
}
94+
95+
for (var i = 0; i < m_LocalSpacePoses.Length; i++)
96+
{
97+
if (m_ParentIndices[i] != -1)
98+
{
99+
var parentTransform = GetPoseAt(m_ParentIndices[i]);
100+
// This is slightly inefficient, since for a body with multiple children, we'll end up inverting
101+
// the transform multiple times. Might be able to trade space for perf here.
102+
var invParent = parentTransform.Inverse();
103+
var currentTransform = GetPoseAt(i);
104+
m_LocalSpacePoses[i] = invParent.Multiply(currentTransform);
105+
}
106+
else
107+
{
108+
m_LocalSpacePoses[i] = Pose.identity;
109+
}
110+
}
111+
}
112+
113+
114+
public void DrawModelSpace(Vector3 offset)
115+
{
116+
UpdateLocalSpacePoses();
117+
UpdateModelSpacePoses();
118+
119+
var pose = m_ModelSpacePoses;
120+
var localPose = m_LocalSpacePoses;
121+
for (var i = 0; i < pose.Length; i++)
122+
{
123+
var current = pose[i];
124+
if (m_ParentIndices[i] == -1)
125+
{
126+
continue;
127+
}
128+
129+
var parent = pose[m_ParentIndices[i]];
130+
Debug.DrawLine(current.position + offset, parent.position + offset, Color.cyan);
131+
var localUp = localPose[i].rotation * Vector3.up;
132+
var localFwd = localPose[i].rotation * Vector3.forward;
133+
var localRight = localPose[i].rotation * Vector3.right;
134+
Debug.DrawLine(current.position+offset, current.position+offset+.1f*localUp, Color.red);
135+
Debug.DrawLine(current.position+offset, current.position+offset+.1f*localFwd, Color.green);
136+
Debug.DrawLine(current.position+offset, current.position+offset+.1f*localRight, Color.blue);
137+
}
138+
}
139+
}
140+
141+
public static class PoseExtensions
142+
{
143+
/// <summary>
144+
/// Compute the inverse of a Pose. For any Pose P,
145+
/// P.Inverse() * P
146+
/// will equal the identity pose (within tolerance).
147+
/// </summary>
148+
/// <param name="pose"></param>
149+
/// <returns></returns>
150+
public static Pose Inverse(this Pose pose)
151+
{
152+
var rotationInverse = Quaternion.Inverse(pose.rotation);
153+
var translationInverse = -(rotationInverse * pose.position);
154+
return new Pose { rotation = rotationInverse, position = translationInverse };
155+
}
156+
157+
/// <summary>
158+
/// This is equivalent to Pose.GetTransformedBy(), but keeps the order more intuitive.
159+
/// </summary>
160+
/// <param name="pose"></param>
161+
/// <param name="rhs"></param>
162+
/// <returns></returns>
163+
public static Pose Multiply(this Pose pose, Pose rhs)
164+
{
165+
return rhs.GetTransformedBy(pose);
166+
}
167+
168+
// TODO optimize inv(A)*B?
169+
}
170+
}

com.unity.ml-agents.extensions/Runtime/Sensors/PoseExtractor.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)