Initial release of the Unity plugin.

PiperOrigin-RevId: 425947505
This commit is contained in:
Tom Erez
2022-02-02 20:17:13 +00:00
committed by Saran Tunyasuvunakool
parent 8ec3dd97a4
commit 6de9eafa13
298 changed files with 20617 additions and 2 deletions
@@ -0,0 +1,24 @@
// Copyright 2019 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using UnityEngine;
namespace Mujoco {
// Use to annotate a float field that should only assume absolute values.
[AttributeUsage(AttributeTargets.Field)]
public class AbsoluteValueAttribute : PropertyAttribute {
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: deb86daf07f8f4e4a97fabbb0f9f6c2e
timeCreated: 1538126279
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+361
View File
@@ -0,0 +1,361 @@
// Copyright 2019 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using UnityEngine;
namespace Mujoco {
// Mujoco engine helper methods.
public static class MjEngineTool {
private const int _elementsPerPosition = 3;
private const int _elementsPerRotation = 4;
private const int _elementsPerTransform = 7;
private static double[] _mjQuat = new double[4];
private static double[] _mjMat = new double[9];
private const int _MjSuccess = 1;
public static string Sanitize(string name) {
return name.Replace('/', '_');
}
// Loads a model from the specified file.
// `filename` should contain an absolute path to the file.
public static unsafe MujocoLib.mjModel_* LoadModelFromFile(string fileName) {
var errorBuf = new StringBuilder(1024);
MujocoLib.mjModel_* model = MujocoLib.mj_loadXML(fileName, null, errorBuf, 1024);
if (model == null || errorBuf.Length > 0) {
throw new IOException(string.Format("Error loading the model: {0}", errorBuf));
}
return model;
}
// Saves a model to the specified file.
// `filename` should contain an absolute path to the file.
public static unsafe void SaveModelToFile(string fileName, MujocoLib.mjModel_* model) {
var errorBuf = new StringBuilder(1024);
MujocoLib.mj_saveLastXML(fileName, model, errorBuf, 1024);
if (errorBuf.Length > 0) {
throw new IOException(string.Format("Error saving the model: {0}", errorBuf));
}
}
// Loads a model from a string.
// It allows to perform diskless instantiation of Mujoco models.
public static unsafe MujocoLib.mjModel_* LoadModelFromString(string contents) {
using (var vfs = new MjVfs()) {
var filename = "filename";
vfs.AddFile(filename, contents);
return vfs.LoadXML(filename);
}
}
// Stores a unity vector in a Mujoco target buffer.
// The vector coordinates will be stored at the 'entryIndex * 3' of the 'mjTarget' array.
public static unsafe void SetMjVector3(double* mjTarget, Vector3 unityVec, int entryIndex) {
var startOffset = entryIndex * _elementsPerPosition;
var mjVec = MjVector3(unityVec);
mjTarget[startOffset] = mjVec[0];
mjTarget[startOffset + 1] = mjVec[1];
mjTarget[startOffset + 2] = mjVec[2];
}
// Stores a unity quaternion in a Mujoco target buffer.
// The vector coordinates will be stored at the 'entryIndex * 4' of the 'mjTarget' array.
public static unsafe void SetMjQuaternion(
double* mjTarget, Quaternion unityQuat, int entryIndex) {
var startOffset = entryIndex * _elementsPerRotation;
var mjQuat = MjQuaternion(unityQuat);
mjTarget[startOffset] = mjQuat.w;
mjTarget[startOffset + 1] = mjQuat.x;
mjTarget[startOffset + 2] = mjQuat.y;
mjTarget[startOffset + 3] = mjQuat.z;
}
// Stores a unity transform (position+rotation) in a Mujoco target buffer.
// The vector coordinates will be stored at the 'entryIndex * 7' of the 'mjTarget' array.
public static unsafe void SetMjTransform(
double* mjTarget, Vector3 unityVec, Quaternion unityQuat, int entryIndex) {
var startOffset = entryIndex * _elementsPerTransform;
var mjVec = MjVector3(unityVec);
var mjQuat = MjQuaternion(unityQuat);
mjTarget[startOffset] = mjVec[0];
mjTarget[startOffset + 1] = mjVec[1];
mjTarget[startOffset + 2] = mjVec[2];
mjTarget[startOffset + 3] = mjQuat.w;
mjTarget[startOffset + 4] = mjQuat.x;
mjTarget[startOffset + 5] = mjQuat.y;
mjTarget[startOffset + 6] = mjQuat.z;
}
// Converts a Unity Vector3 to a Mujoco vector.
public static Vector3 MjVector3(Vector3 unityVec) {
return new Vector3(unityVec.x, unityVec.z, unityVec.y);
}
// Converts a Mujoco vector to a Unity vector.
public static unsafe Vector3 UnityVector3(double* coords, int entryIndex) {
var startOffset = entryIndex * _elementsPerPosition;
return new Vector3(
(float)coords[startOffset], (float)coords[startOffset + 2], (float)coords[startOffset + 1]);
}
// Converts a Mujoco vector to a Unity vector.
// The vector coordinates are expected to be stored at the 'entryIndex * 3' of the 'coords'
// array.
public static unsafe Vector3 UnityVector3(float[] coords, int entryIndex) {
var startOffset = entryIndex * _elementsPerPosition;
return new Vector3(coords[startOffset], coords[startOffset + 2], coords[startOffset + 1]);
}
// Converts a Mujoco vector to a Unity vector.
public static Vector3 UnityVector3(Vector3 mjVector) {
return new Vector3(mjVector.x, mjVector.z, mjVector.y);
}
// Converts a Unity Quaternion to a Mujoco Quaternion
public static Quaternion MjQuaternion(Quaternion unityQuat) {
return new Quaternion(w:-unityQuat.w, x:unityQuat.x, y:unityQuat.z, z:unityQuat.y);
}
// Converts a Mujoco quaternion to a Unity quaternion.
// The quaternions is read starting at 'entryIndex * 4' of the 'coords' array.
public static Quaternion UnityQuaternion(double[] coords, int entryIndex) {
var startOffset = entryIndex * _elementsPerRotation;
return new Quaternion(
x:(float)coords[startOffset + 1], y:(float)coords[startOffset + 3],
z:(float)coords[startOffset + 2], w:(float)-coords[startOffset]);
}
// Converts a Mujoco quaternion to a Unity quaternion.
// The quaternion is read starting at 'entryIndex * 4' of the 'coords' array.
public static unsafe Quaternion UnityQuaternion(double* coords, int entryIndex) {
var startOffset = entryIndex * _elementsPerRotation;
return new Quaternion(
x:(float)coords[startOffset + 1], y:(float)coords[startOffset + 3],
z:(float)coords[startOffset + 2], w:(float)-coords[startOffset]);
}
// Converts a Mujoco quaternion to a Unity quaternion.
public static Quaternion UnityQuaternion(Quaternion mjQuat) {
// This is simply the inverse permutation of the one in MjQuaternion
return new Quaternion(x:mjQuat.x, y:mjQuat.z, z:mjQuat.y, w:-mjQuat.w);
}
// Converts a Mujoco matrix to a Unity quaternion.
// The matrix coordinates are at the 'entryIndex * 9' of the 'coords' array.
public static unsafe Quaternion UnityQuaternionFromMatrix(double* coords, int entryIndex) {
var startOffset = entryIndex * _mjMat.Length;
for (var j = 0; j < _mjMat.Length; ++j) {
_mjMat[j] = coords[startOffset + j];
}
fixed (double* a = _mjQuat)
fixed (double* b = _mjMat) {
MujocoLib.mju_mat2Quat(a, b);
}
return UnityQuaternion(_mjQuat, 0);
}
// Converts a Unity extents Vector3 to a Mujoco extents vector.
// Use to convert size vectors that should contain only absolute values.
public static Vector3 MjExtents(Vector3 unityExtents) {
return new Vector3(unityExtents.x, unityExtents.z, unityExtents.y);
}
// Converts a Mujoco extents vector to a Unity extents Vector3.
// Use to convert size vectors that should contain only absolute values.
public static Vector3 UnityExtents(Vector3 mjExtents) {
return new Vector3(mjExtents.x, mjExtents.z, mjExtents.y);
}
// Converts a vector containing Mujoco Euler angles to a Unity representation.
public static Vector3 UnityEuler(Vector3 mjEuler) {
return new Vector3(mjEuler.x, mjEuler.z, -mjEuler.y);
}
public static string Vector3ToMjcf(Vector3 vec) {
return $"{vec.x} {vec.y} {vec.z}";
}
public static string QuaternionToMjcf(Quaternion quat) {
return $"{quat.w} {quat.x} {quat.y} {quat.z}";
}
// We can't use transform.localPosition because we allow arbitrary deep gameObject hierarchies
// between MuJoCo parent and child entities. Therefore, the method takes into account extra
// transforms that may be present in the chain separating the component from its parent body.
public static MjTransformation LocalTransformInParentBody(MjComponent component) {
var MjParent = MjHierarchyTool.FindParentComponent<MjBaseBody>(component);
var parentGlobalPosition = Vector3.zero;
var parentGlobalRotation = Quaternion.identity;
if (MjParent != null) {
parentGlobalPosition = MjParent.transform.position;
parentGlobalRotation = MjParent.transform.rotation;
}
var invParentGlobalRotation = Quaternion.Inverse(parentGlobalRotation);
var localPosition =
invParentGlobalRotation * (component.transform.position - parentGlobalPosition);
var localRotation = invParentGlobalRotation * component.transform.rotation;
return new MjTransformation(localPosition, localRotation);
}
// Identity quaternion in MujocoLib.
public static readonly Quaternion MjQuaternionIdentity = new Quaternion(w:-1, x:0, y:0, z:0);
// Mujoco's Z-axis corresponds to Unity's up (Y) axis.
public static readonly Vector3 MjVector3Up = new Vector3(0, 0, 1);
// Sorts the components of a Vector2. Doesn't modify the original vector.
public static Vector2 GetSorted(Vector2 vec) {
return new Vector2(Math.Min(vec.x, vec.y), Math.Max(vec.x, vec.y));
}
// Converts an array of floats to an Mjcf.
public static string ArrayToMjcf(float[] array) {
return String.Join(" ", array);
}
// Converts a list of floats to an Mjcf.
public static string ListToMjcf(List<float> list) {
return String.Join(" ", list);
}
// Generates an Mjcf of the specified component's transform.
//
// It will use the component's position (Vector3) and rotation (Quaternion), relative to its
// MjComponent parent, and save them in "pos" and "quat" attributes.
//
// The method takes into account extra transforms that may be present in the chain separating
// the component from its MjComponent parent.
public static void PositionRotationToMjcf(XmlElement mjcf, MjComponent component) {
var localTransform = LocalTransformInParentBody(component);
mjcf.SetAttribute(
"pos",
MjEngineTool.Vector3ToMjcf(MjEngineTool.MjVector3(localTransform.Translation)));
mjcf.SetAttribute(
"quat",
MjEngineTool.QuaternionToMjcf(MjEngineTool.MjQuaternion(localTransform.Rotation)));
}
// Generates an Mjcf of the specified component's transform.
//
// It will use the component's position (Vector3) and forward direction (Vector3), relative to
// its MjComponent parent, and save them in "pos" and "axis" attributes. Designed to work
// with Joints, it will set the "ref" attribute to 0, indicating that the joint is in its
// reference pose frame.
//
// The method takes into account extra transforms that may be present in the chain separating
// the component from its MjComponent parent.
public static void PositionAxisToMjcf(XmlElement mjcf, MjComponent component) {
var localTransform = LocalTransformInParentBody(component);
var axis = (localTransform.Rotation * Vector3.right).normalized;
mjcf.SetAttribute(
"pos",
MjEngineTool.Vector3ToMjcf(MjEngineTool.MjVector3(localTransform.Translation)));
mjcf.SetAttribute("axis", MjEngineTool.Vector3ToMjcf(MjEngineTool.MjVector3(axis)));
mjcf.SetAttribute("ref", "0");
}
public static void PositionToMjcf(XmlElement mjcf, MjComponent component) {
var localTransform = LocalTransformInParentBody(component);
mjcf.SetAttribute(
"pos",
MjEngineTool.Vector3ToMjcf(MjEngineTool.MjVector3(localTransform.Translation)));
}
private const int _fromOffset = 0;
private const int _toOffset = 1;
private const float _fromToValidityTolerance = 1e-3f;
public static bool ParseFromToMjcf(XmlElement mjcf, out Vector3 fromPoint, out Vector3 toPoint) {
var fromto = mjcf.GetFloatArrayAttribute("fromto", defaultValue: null);
if (fromto != null) {
fromPoint = MjEngineTool.UnityVector3(fromto, _fromOffset);
toPoint = MjEngineTool.UnityVector3(fromto, _toOffset);
var nodeName = mjcf.GetStringAttribute("name", mjcf.Name);
if ((toPoint - fromPoint).magnitude < _fromToValidityTolerance) {
throw new ArgumentException(
$"{nodeName}: 'fromto' produces a vector that's too short. {fromto} has magnitude " +
"{fromto.magnitude} lower than the tolerance {_fromToValidityTolerance}");
}
return true;
} else {
fromPoint = Vector3.zero;
toPoint = Vector3.zero;
return false;
}
}
// Parses the transform-shape specifications, shared by the Geom and Site Mjcfs.
public static void ParseTransformMjcf(XmlElement mjcf, Transform transform) {
Vector3 fromPoint, toPoint;
if (ParseFromToMjcf(mjcf, out fromPoint, out toPoint)) {
transform.localPosition = Vector3.Lerp(toPoint, fromPoint, 0.5f);
transform.localRotation = Quaternion.FromToRotation(
Vector3.up, (toPoint - fromPoint).normalized);
} else {
// Parse the regular transform.
transform.localPosition = MjEngineTool.UnityVector3(
mjcf.GetVector3Attribute("pos", defaultValue: Vector3.zero));
transform.localRotation = Quaternion.identity;
if (mjcf.HasAttribute("quat")) {
transform.localRotation = MjEngineTool.UnityQuaternion(
mjcf.GetQuaternionAttribute("quat",
defaultValue: MjEngineTool.MjQuaternionIdentity));
} else if (mjcf.HasAttribute("zaxis")) {
var upAxis = MjEngineTool.UnityVector3(
mjcf.GetVector3Attribute("zaxis", defaultValue: MjEngineTool.MjVector3Up));
transform.localRotation = Quaternion.FromToRotation(
Vector3.up, upAxis);
} else if (mjcf.HasAttribute("xyaxes")) {
var xyaxes = mjcf.GetFloatArrayAttribute(
"xyaxes", defaultValue: new float[] { 1, 0, 0, 0, 1, 0 });
var xAxis = MjEngineTool.UnityVector3(xyaxes, 0);
var yAxis = MjEngineTool.UnityVector3(xyaxes, 1);
var zAxis = Vector3.Cross(xAxis, yAxis);
transform.localRotation = Quaternion.LookRotation(zAxis, yAxis);
} else if (mjcf.HasAttribute("axisangle")) {
var axisAngle = mjcf.GetFloatArrayAttribute(
"axisangle", defaultValue: new float[] { 0, 0, 1, 0 });
var axis = MjEngineTool.UnityVector3(axisAngle, 0);
var angle = axisAngle[3];
if (MjSceneImportSettings.AnglesInDegrees == false) {
angle *= Mathf.Rad2Deg;
}
transform.localRotation = Quaternion.AngleAxis(angle, axis);
} else if (mjcf.HasAttribute("euler")) {
var euler = MjEngineTool.UnityEuler(
mjcf.GetVector3Attribute("euler", defaultValue: Vector3.zero));
if (MjSceneImportSettings.AnglesInDegrees == false) {
euler = euler * Mathf.Rad2Deg;
}
transform.localRotation = Quaternion.Euler(euler);
}
}
}
}
public static class MjSceneImportSettings {
public static bool AnglesInDegrees = true;
}
}
+13
View File
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 957bf8e1dc4af40c2a42dd1a33fe10d2
timeCreated: 1536920023
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2019 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace Mujoco {
// A set of helper methods for working with hierarchies of components.
public static class MjHierarchyTool {
public static IEnumerable<MjComponent> LinearizeHierarchyBFS(Transform start) {
var open = new Queue<Transform>();
open.Enqueue(start);
while (open.Count > 0) {
var transform = open.Dequeue();
var components = transform.GetComponents<MjComponent>();
foreach (var component in components) {
if (component.isActiveAndEnabled) {
yield return component;
}
}
foreach (Transform child in transform) {
open.Enqueue(child);
}
}
}
// Recurses up the tree (excluding self) and returns the first instance of a MjComponent.
public static MjComponent FindParentComponent(MonoBehaviour start) {
return FindParentComponent<MjComponent>(start);
}
// This is not equivalent to Unity's GetComponentInParent as it excludes self from the search.
public static T FindParentComponent<T>(MonoBehaviour start) where T : MonoBehaviour {
if (start.transform.parent == null) {
return null;
}
var loopTransform = start.transform.parent;
T result = loopTransform.GetComponent<T>();
while (result == null && loopTransform.parent != null) {
loopTransform = loopTransform.parent;
result = loopTransform.GetComponent<T>();
}
return result;
}
public static List<T> GetComponentsInImmediateChildren<T>(Transform parent)
where T : MonoBehaviour {
var components = new List<T>();
foreach (Transform child in parent) {
components.AddRange(child.gameObject.GetComponents<T>());
}
return components;
}
// Finds a single component that matches both the type and the name criteria.
public static T FindComponentOfTypeAndName<T>(string name) where T : MjComponent {
var components = UnityEngine.Object.FindObjectsOfType<T>().Where(
component => component.gameObject.name == name);
var numComponents = components.Count();
if (components == null || numComponents == 0) {
throw new ArgumentException($"No components named '{name}' were found.");
}
if (numComponents > 1) {
throw new ArgumentException($"More than one component named '{name}' was found.");
}
return components.FirstOrDefault() as T;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e246736c791384d918a7781a04225eee
timeCreated: 1537463659
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+84
View File
@@ -0,0 +1,84 @@
// Copyright 2019 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using UnityEngine;
namespace Mujoco {
// Utility class that represents a translation and rotation.
// Used for converting between MuJoCo and Unity spaces.
public struct MjTransformation {
public Vector3 Translation;
public Quaternion Rotation;
public MjTransformation(Vector3 translation, Quaternion rotation) {
Translation = translation;
Rotation = rotation;
}
public void Set(Vector3 translation, Quaternion rotation) {
Translation = translation;
Rotation = rotation;
}
// Initializes MjTransformation from the specified Transform's local coordinates.
public static MjTransformation LoadGlobal(Transform transform) {
if (transform != null) {
return new MjTransformation(transform.position, transform.rotation);
} else {
return new MjTransformation(Vector3.zero, Quaternion.identity);
}
}
// Initializes MjTransformation from the specified Transform's global coordinates.
public static MjTransformation LoadLocal(Transform transform) {
if (transform != null) {
return new MjTransformation(transform.localPosition, transform.localRotation);
} else {
return new MjTransformation(Vector3.zero, Quaternion.identity);
}
}
// Stores the transform coordinates in the specified Transform's global coordinates.
public void StoreGlobal(Transform transform) {
transform.position = Translation;
transform.rotation = Rotation;
}
// Stores the transform coordinates in the specified Transform's local coordinates.
public void StoreLocal(Transform transform) {
transform.localPosition = Translation;
transform.localRotation = Rotation;
}
// Concatenates two transforms.
// Equivalent of a matrix multiplication. This operation is not transitive.
public static MjTransformation operator *(
MjTransformation lhs, MjTransformation rhs) {
return new MjTransformation(
lhs.Translation + lhs.Rotation * rhs.Translation, lhs.Rotation * rhs.Rotation);
}
// Returns an inverse of this transform.
public MjTransformation Inverse() {
var inverseRotation = Quaternion.Inverse(Rotation);
return new MjTransformation(inverseRotation * Translation * -1.0f, inverseRotation);
}
public override string ToString() {
return $"[{Translation}, {Rotation}]";
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 69065be4c455640e8ab0a9d346646be5
timeCreated: 1551107971
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+119
View File
@@ -0,0 +1,119 @@
// Copyright 2019 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
namespace Mujoco {
using static MujocoLib;
// A wrapper around Mujoco's native _mjVFS that simplifies the process of creating memory files.
public sealed class MjVfs : IDisposable {
private _mjVFS _managedVfs;
private IntPtr _unmanagedVfs;
// Managed version of the underlying _mjVFS structure.
public _mjVFS Data {
get {
_managedVfs = (_mjVFS)Marshal.PtrToStructure(_unmanagedVfs, typeof(_mjVFS));
return _managedVfs;
}
set {
_managedVfs = value;
Marshal.StructureToPtr(_managedVfs, _unmanagedVfs, fDeleteOld: false);
}
}
// Number of files added to the filesystem.
public int FilesCount {
get { return Data.nfile; }
}
// Adds a new file to the virtual filesystem.
public unsafe void AddFile(string filename, string contents) {
var result = mj_makeEmptyFileVFS(_unmanagedVfs.ToPointer(), filename, contents.Length);
if (result != 0) {
throw new Exception(
"VFS error (" + result + ") encountered while creating an empty file");
}
var fileIndex = mj_findFileVFS(_unmanagedVfs.ToPointer(), filename);
if (fileIndex < 0) {
throw new IndexOutOfRangeException("VFS didn't properly create the empty file.");
}
SetFileContents(fileIndex, contents);
}
// Searches the VFS for the specified file and returns its index.
// The index then can be used to retrieve the file contents from Data.filedata array.
public unsafe int FindFile(string filename) {
return mj_findFileVFS(_unmanagedVfs.ToPointer(), filename);
}
// Loads a model from the specified file.
// The file is assumed to be located in the filesystem. If it's not found, the method will throw
// an ArgumentException.
public unsafe MujocoLib.mjModel_* LoadXML(string filename) {
if (FindFile(filename) < 0) {
throw new ArgumentException($"File {filename} was not added to the VFS.");
}
var errorBuf = new StringBuilder(1024);
MujocoLib.mjModel_* model = MujocoLib.mj_loadXML(
filename, _unmanagedVfs.ToPointer(), errorBuf, errorBuf.Capacity);
if (model == null || errorBuf.Length > 0) {
throw new IOException($"Error loading the model: {errorBuf}");
}
return model;
}
private unsafe void SetFileContents(int fileIndex, string contents) {
var data = Data;
Marshal.FreeHGlobal(data.filedata[fileIndex]);
data.filedata[fileIndex] = Marshal.StringToHGlobalAnsi(contents);
Data = data;
}
public MjVfs() {
_managedVfs = new _mjVFS();
_unmanagedVfs = Marshal.AllocHGlobal(Marshal.SizeOf(_managedVfs));
Marshal.StructureToPtr(_managedVfs, _unmanagedVfs, true);
}
~MjVfs() {
ReleaseUnmanagedMemory();
}
public void Dispose() {
GC.SuppressFinalize(this);
ReleaseUnmanagedMemory();
}
private void ReleaseUnmanagedMemory() {
if (_unmanagedVfs != IntPtr.Zero) {
var data = Data;
for (var fileIndex = 0; fileIndex < data.nfile; ++fileIndex) {
Marshal.FreeHGlobal(data.filedata[fileIndex]);
}
Marshal.FreeHGlobal(_unmanagedVfs);
_unmanagedVfs = IntPtr.Zero;
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 3cf049acadd3a4c8eb75f2a5a72ba589
timeCreated: 1535566910
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,104 @@
// Copyright 2019 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using UnityEngine;
namespace Mujoco {
// An instance of this class will be used to collect dynamic dependencies produced by a Mujoco model
// during scene generation.
public class MjcfGenerationContext {
public int NUserSensor {
get { return _nuserSensor; }
set { _nuserSensor = Math.Max(_nuserSensor, value); }
}
private int _nuserSensor;
private int _numGeneratedNames = 0;
private Dictionary<Mesh, string> _meshAssets = new Dictionary<Mesh, string>();
public void GenerateMjcf(XmlElement mjcf) {
GenerateConfigurationMjcf(mjcf);
GenerateAssetsMjcf(mjcf);
}
// Adds a mesh to the list of assets that will be added to the generated MJCF.
// Returns the unique name that identifies this new asset.
public string AddMeshAsset(Mesh mesh) {
if (!_meshAssets.ContainsKey(mesh)) {
var uniqueName = $"mesh_{_numGeneratedNames}";
_numGeneratedNames++;
_meshAssets.Add(mesh, uniqueName);
}
return _meshAssets[mesh];
}
// Generates a Unique name for the specified component.
// Uniqueness of names is required for every type independently.
// The per-type uniqueness will be guaranteed by the combination of information used:
// - name of the host GameObject.
// - number of names that have been generated to date.
public string GenerateName(Component comp) {
var name = $"{comp.gameObject.name}_{_numGeneratedNames}";
_numGeneratedNames++;
return name;
}
private void GenerateConfigurationMjcf(XmlElement mjcf) {
var doc = mjcf.OwnerDocument;
var compilerMjcf = (XmlElement)mjcf.AppendChild(doc.CreateElement("compiler"));
compilerMjcf.SetAttribute("coordinate", "local");
var optionMjcf = (XmlElement)mjcf.AppendChild(doc.CreateElement("option"));
optionMjcf.SetAttribute(
"gravity", MjEngineTool.Vector3ToMjcf(MjEngineTool.MjVector3(Physics.gravity)));
optionMjcf.SetAttribute("timestep", $"{Time.fixedDeltaTime}");
var sizeMjcf = (XmlElement)mjcf.AppendChild(doc.CreateElement("size"));
sizeMjcf.SetAttribute("nuser_sensor", $"{_nuserSensor}");
var settings = MjGlobalSettings.Instance;
if (settings) {
settings.OptionSizeToMjcf(optionMjcf, sizeMjcf);
}
}
private void GenerateAssetsMjcf(XmlElement mjcf) {
var doc = mjcf.OwnerDocument;
var assetMjcf = (XmlElement)mjcf.AppendChild(doc.CreateElement("asset"));
foreach (var asset in _meshAssets) {
var meshMjcf = (XmlElement)assetMjcf.AppendChild(doc.CreateElement("mesh"));
meshMjcf.SetAttribute("name", asset.Value);
GenerateMeshMjcf(asset.Key, meshMjcf);
}
}
private static void GenerateMeshMjcf(Mesh mesh, XmlElement mjcf) {
var vertexPositionsStr = new StringBuilder();
foreach (var unityVertex in mesh.vertices) {
var mjVertex = MjEngineTool.MjVector3(unityVertex);
vertexPositionsStr.Append(MjEngineTool.Vector3ToMjcf(mjVertex));
vertexPositionsStr.Append(" ");
}
mjcf.SetAttribute("vertex", vertexPositionsStr.ToString());
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: c6b8eb0cef9a04e2d92bb573828f1be8
timeCreated: 1550078178
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+167
View File
@@ -0,0 +1,167 @@
// Copyright 2019 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Linq;
using System.Xml;
using UnityEngine;
namespace Mujoco {
// This class helps us manipulate MuJoCo-bound XML files. It is only intended to be used
// within the context of MuJoCo - elsewhere the preferred format is JSON.
public static class XmlElementExtensions {
public static bool GetBoolAttribute(
this XmlElement element, string name, bool defaultValue = false) {
if (!element.HasAttribute(name)) {
return defaultValue;
}
var strValue = element.GetAttribute(name);
bool parsedValue;
if (bool.TryParse(strValue, out parsedValue)) {
return parsedValue;
} else {
throw new ArgumentException($"'{strValue}' is not a bool.");
}
}
public static float GetFloatAttribute(
this XmlElement element, string name, float defaultValue = 0.0f) {
if (!element.HasAttribute(name)) {
return defaultValue;
}
var strValue = element.GetAttribute(name);
float parsedValue;
if (float.TryParse(strValue, out parsedValue)) {
return parsedValue;
} else {
throw new ArgumentException($"'{strValue}' is not a float.");
}
}
// The MuJoCo parser is case-sensitive.
public static T GetEnumAttribute<T>(
this XmlElement element, string name, T defaultValue,
bool ignoreCase = false) where T : struct, IConvertible {
if (!typeof(T).IsEnum) {
throw new ArgumentException("T must be an enumerated type.");
}
if (!element.HasAttribute(name)) {
return defaultValue;
}
var strValue = element.GetAttribute(name);
T parsedValue;
if (Enum.TryParse<T>(strValue, ignoreCase, out parsedValue)) {
return parsedValue;
} else {
throw new ArgumentException(
$"Reading attribute {name}: '{strValue}' is not a value of enum {typeof(T)}.");
}
}
public static string GetStringAttribute(
this XmlElement element, string name, string defaultValue = null) {
if (!element.HasAttribute(name)) {
return defaultValue;
}
return element.GetAttribute(name);
}
public static T GetObjectReferenceAttribute<T>(
this XmlElement mjcf, string attributeName) where T : MjComponent {
var objectName = mjcf.GetStringAttribute(attributeName);
if (string.IsNullOrEmpty(objectName)) {
return null;
}
T foundObject = MjHierarchyTool.FindComponentOfTypeAndName<T>(objectName);
if (foundObject == null) {
throw new ArgumentException(
$"Object {objectName} of type {typeof(T).ToString()} referred by {attributeName} " +
"doesn't exist.");
}
return foundObject;
}
public static Vector2 GetVector2Attribute(
this XmlElement element, string name, Vector2 defaultValue) {
var defaultValues = new float[] {defaultValue.x, defaultValue.y};
var components = element.GetFloatArrayAttribute(name, defaultValues, fillMissingValues: false);
if (components.Length != 2) {
throw new ArgumentException("Invalid Vector2 string representation.");
}
return new Vector2(components[0], components[1]);
}
public static Vector3 GetVector3Attribute(
this XmlElement element, string name, Vector3 defaultValue) {
var defaultValues = new float[] {
defaultValue.x, defaultValue.y, defaultValue.z};
var components = element.GetFloatArrayAttribute(name, defaultValues, fillMissingValues: false);
if (components.Length != 3) {
throw new ArgumentException("Invalid Vector3 string representation.");
}
return new Vector3(components[0], components[1], components[2]);
}
public static Quaternion GetQuaternionAttribute(
this XmlElement element, string name, Quaternion defaultValue) {
var mjDefault = new float[] {
defaultValue.w, defaultValue.x, defaultValue.y, defaultValue.z };
var components = element.GetFloatArrayAttribute(name, mjDefault, fillMissingValues: false);
if (components.Length != 4) {
throw new ArgumentException("Invalid Quaternion string representation.");
}
return new Quaternion(w:components[0], x:components[1], y:components[2], z:components[3]);
}
// Parses an array of whitespace separated floating points.
//
// Args:
// . element: XmlElement that contains the attribute to be parsed.
// . name: Name of the attribute to be parsed.
// . defaultValue: An array of floats, or null. A default value to be returned in case the
// the attribute is missing.
// . fillMissingValues: If a default value was provided, and it has more components than the value
// parsed from the attribute, the missing components will be copied from the defaultValue.
public static float[] GetFloatArrayAttribute(
this XmlElement element, string name, float[] defaultValue, bool fillMissingValues = true) {
if (!element.HasAttribute(name)) {
return defaultValue;
}
var strValue = element.GetAttribute(name);
var components = strValue.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
var resultLength = components.Length;
if (fillMissingValues && defaultValue != null) {
// If filling of the missing values was enabled, and a default value was provided,
// allocate an array large enough to store a value of this length, in case when the parsed
// value has fewer components.
resultLength = Math.Max(resultLength, defaultValue.Length);
}
var result = new float[resultLength];
for (var i = 0; i < components.Length; ++i) {
float componentValue;
if (float.TryParse(components[i], out componentValue)) {
result[i] = componentValue;
} else {
throw new ArgumentException($"'{components[i]}' is not a float.");
}
}
for (var i = components.Length; i < resultLength; ++i) {
// Fill the missing values with defaults.
result[i] = defaultValue[i];
}
return result;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 783a359f292c1477caf7e242023ed32c
timeCreated: 1538658945
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: