Initial release of the Unity plugin.
PiperOrigin-RevId: 425947505
This commit is contained in:
committed by
Saran Tunyasuvunakool
parent
8ec3dd97a4
commit
6de9eafa13
@@ -0,0 +1,115 @@
|
||||
// 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.Xml;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mujoco {
|
||||
|
||||
public class MjXmlModifiers {
|
||||
private XmlDocument _root;
|
||||
|
||||
public MjXmlModifiers(XmlDocument root) {
|
||||
_root = root;
|
||||
}
|
||||
|
||||
public void ApplyModifiersToElement(XmlElement element) {
|
||||
// Combine all defaults into one. At this stage, we want to overwrite attributes defined by
|
||||
// the previous defaults.
|
||||
var aggregateDefaults = _root.CreateElement("aggregate");
|
||||
// Root default leaf should be processed only once, and handled first (so it's overriden).
|
||||
var rootDefaultLeaf = _root.SelectSingleNode($"/mujoco/default/{element.Name}") as XmlElement;
|
||||
if (rootDefaultLeaf != null) {
|
||||
CopyAttributes(rootDefaultLeaf, aggregateDefaults);
|
||||
}
|
||||
// Order matters - reverse class list so that most relevant is last.
|
||||
var classes = GetApplicableDefaultClasses(element).Reverse();
|
||||
foreach (var className in classes) {
|
||||
var defaultClassElement =
|
||||
_root.SelectSingleNode($"descendant::default[@class='{className}']") as XmlElement;
|
||||
// Ancestry iterates up in the tree, but we want to apply changes from remote to specific.
|
||||
var ancestors = GetDefaultAncestry(defaultClassElement, element.Name).Reverse();
|
||||
foreach (var defaultAncestor in ancestors) {
|
||||
CopyAttributesOverwriteExisting(defaultAncestor, aggregateDefaults);
|
||||
}
|
||||
}
|
||||
// Add the merged attribiutes to the node, without overwriting the existing values.
|
||||
CopyAttributes(aggregateDefaults, element);
|
||||
}
|
||||
|
||||
private IEnumerable<XmlElement> GetDefaultAncestry(XmlElement classElement, string nodeType) {
|
||||
var defaultElement = classElement;
|
||||
var top = _root.SelectSingleNode("/mujoco/default");
|
||||
while (defaultElement != top) {
|
||||
foreach (var element in defaultElement.ChildNodes) {
|
||||
if (((XmlElement)element).Name == nodeType) {
|
||||
yield return (XmlElement)element;
|
||||
}
|
||||
}
|
||||
defaultElement = defaultElement.ParentNode as XmlElement;
|
||||
}
|
||||
}
|
||||
|
||||
// Return a list of names of all the applicable classes. Implemented by climbing up the tree,
|
||||
// hence results are in order of relevance, from the most specific to most remotely-inherited.
|
||||
public static IEnumerable<string> GetApplicableDefaultClasses(XmlElement modifiedElement) {
|
||||
var nodeType = modifiedElement.Name;
|
||||
|
||||
// Build the inheritance list for the element that's meant to be modified.
|
||||
// It will consist of all qualifiers supplied by the element itself and its parents
|
||||
// using "class" and "childclass" values.
|
||||
var inheritance = new List<string>();
|
||||
var element = modifiedElement;
|
||||
while (element != null) {
|
||||
var className = element.GetStringAttribute("childclass", string.Empty);
|
||||
if (element == modifiedElement) {
|
||||
// "class" attribute does not partcipate in building of the inheritance tree, and so we're
|
||||
// only considering it if it's defined on the very element we're trying to modify.
|
||||
className = element.GetStringAttribute("class", className);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(className) && !inheritance.Contains(className)) {
|
||||
inheritance.Add(className);
|
||||
}
|
||||
element = element.ParentNode as XmlElement;
|
||||
}
|
||||
return inheritance;
|
||||
}
|
||||
|
||||
private static string GetElementClass(XmlElement element) {
|
||||
var elementClass = element.GetStringAttribute("class", string.Empty);
|
||||
if (string.IsNullOrEmpty(elementClass)) {
|
||||
elementClass = element.GetStringAttribute("childclass", string.Empty);
|
||||
}
|
||||
return elementClass;
|
||||
}
|
||||
|
||||
public static void CopyAttributes(XmlElement from, XmlElement to) {
|
||||
foreach (XmlAttribute attribute in from.Attributes) {
|
||||
if (to.HasAttribute(attribute.Name) == false) {
|
||||
to.SetAttribute(attribute.Name, attribute.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CopyAttributesOverwriteExisting(XmlElement from, XmlElement to) {
|
||||
foreach (XmlAttribute attribute in from.Attributes) {
|
||||
to.SetAttribute(attribute.Name, attribute.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41357c10b9c0b2fcfaab18600e467579
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,435 @@
|
||||
// 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.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mujoco {
|
||||
// API for importing Mujoco XML files into Unity scenes.
|
||||
public class MjcfImporter {
|
||||
// Any materials created at runtime will only be automatically cleaned up if no other objects
|
||||
// reference it AND Unity changes scenes. In long-running worlds with no scene resets, this means
|
||||
// those materials are never cleaned even after their MjBodies are destroyed, so we cache a
|
||||
// single shared material to use for all generated meshes.
|
||||
public static Material DefaultMujocoMaterial {
|
||||
get {
|
||||
if (_DefaultMujocoMaterial == null) {
|
||||
_DefaultMujocoMaterial = new Material(Shader.Find("Standard"));
|
||||
}
|
||||
|
||||
return _DefaultMujocoMaterial;
|
||||
}
|
||||
set {
|
||||
_DefaultMujocoMaterial = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Modifiers that change the settings of parsed nodes. They're limited to the scope of ParseRoot
|
||||
// method.
|
||||
protected MjXmlModifiers _modifiers;
|
||||
|
||||
private static Material _DefaultMujocoMaterial;
|
||||
|
||||
private const string _angleTypeDegree = "degree";
|
||||
|
||||
private int _numGeneratedNames = 0;
|
||||
|
||||
// A list of callbacks on how to handle custom node types.
|
||||
private IDictionary<string, Action<XmlElement, GameObject>> _customNodeHandlers =
|
||||
new Dictionary<string, Action<XmlElement, GameObject>>();
|
||||
|
||||
public void AddNodeHandler(string name, Action<XmlElement, GameObject> handler) {
|
||||
_customNodeHandlers[name] = handler;
|
||||
}
|
||||
|
||||
public MjcfImporter() {}
|
||||
|
||||
public GameObject ImportString(string mjcfString, string name = null) {
|
||||
var mjcfXml = new XmlDocument();
|
||||
mjcfXml.LoadXml(mjcfString);
|
||||
return ImportXml(mjcfXml, name);
|
||||
}
|
||||
|
||||
// A generic version of CreateGameObjectWithUniqueName.
|
||||
public GameObject CreateGameObjectWithUniqueName<T>(
|
||||
GameObject parentObject, XmlElement parentNode) where T : MjComponent {
|
||||
return CreateGameObjectWithUniqueName(parentObject, parentNode, typeof(T));
|
||||
}
|
||||
|
||||
public string GenerateUniqueName(XmlElement parentNode) {
|
||||
var name = string.Empty;
|
||||
if (parentNode.HasAttribute("name")) {
|
||||
name = parentNode.GetAttribute("name");
|
||||
}
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
name = parentNode.Name + "_" + _numGeneratedNames++;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public unsafe GameObject ImportXml(XmlDocument mjcfXml, string name = null) {
|
||||
// Reset the numbering of anonymous nodes to ensure deterministic naming of loaded scenes.
|
||||
_numGeneratedNames = 0;
|
||||
|
||||
_modifiers = new MjXmlModifiers(mjcfXml);
|
||||
|
||||
ConfigureAngleType(mjcfXml);
|
||||
|
||||
// We're adopting the approach of building the scene hierarchy as we parse the Mjcf.
|
||||
// Should we encounter any error, we'll terminate and clean it immediately.
|
||||
// Having a single root object for that will be very helpful, since we only need to delete that
|
||||
// object.
|
||||
var sceneRoot = new GameObject(name ?? "default");
|
||||
|
||||
// We'll use Exceptions as a way to communicate errors encountered during parsing.
|
||||
try {
|
||||
ParseRoot(sceneRoot, mjcfXml.SelectSingleNode("/mujoco") as XmlElement);
|
||||
} catch (Exception ex) {
|
||||
// We consider any error as critical, and end the import process immediately, cleaning up
|
||||
// what we have already built.
|
||||
Debug.LogException(ex);
|
||||
GameObject.DestroyImmediate(sceneRoot);
|
||||
throw ex;
|
||||
} finally {
|
||||
_modifiers = null;
|
||||
}
|
||||
|
||||
return sceneRoot;
|
||||
}
|
||||
|
||||
// Creates a new GameObject, assigns it a unique name, and attaches a component of the specified
|
||||
// type. If the requested component type is abstract, the method will attempt to instantiate one
|
||||
// of its implementations that passes the parser test.
|
||||
protected GameObject CreateGameObjectWithUniqueName(
|
||||
GameObject parentObject, XmlElement parentNode, Type componentType) {
|
||||
var name = GenerateUniqueName(parentNode);
|
||||
var gameObject = new GameObject(name);
|
||||
gameObject.transform.parent = parentObject.transform;
|
||||
var component = gameObject.AddComponent(componentType) as MjComponent;
|
||||
component.ParseMjcf(parentNode);
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
protected void ConfigureAngleType(XmlDocument mjcfXml) {
|
||||
var compilerNode = mjcfXml.SelectSingleNode("/mujoco/compiler") as XmlElement;
|
||||
if (compilerNode != null) {
|
||||
// Parse the angle type.
|
||||
var angleSetting = compilerNode.GetStringAttribute("angle", defaultValue: _angleTypeDegree);
|
||||
MjSceneImportSettings.AnglesInDegrees = angleSetting == _angleTypeDegree;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ParseRoot(GameObject parentObject, XmlElement parentNode) {
|
||||
|
||||
// This makes no references nor being referred into, so it can be parsed whenever.
|
||||
var optionNode = parentNode.SelectSingleNode("option") as XmlElement;
|
||||
var sizeNode = parentNode.SelectSingleNode("size") as XmlElement;
|
||||
if (optionNode != null || sizeNode != null) {
|
||||
var globalsObject = CreateGameObjectInParent("Global Settings", parentObject);
|
||||
var settingsComponent = globalsObject.AddComponent<MjGlobalSettings>();
|
||||
settingsComponent.ParseOptionSizeMjcf(optionNode, sizeNode);
|
||||
}
|
||||
|
||||
// This makes references to assets.
|
||||
var worldBodyNode = parentNode.SelectSingleNode("worldbody") as XmlElement;
|
||||
ParseBodyChildren(parentObject, worldBodyNode);
|
||||
|
||||
// This section references bodies, must be parsed after worldbody.
|
||||
var excludeNode = parentNode.SelectSingleNode("contact") as XmlElement;
|
||||
if (excludeNode != null) {
|
||||
var excludesParentObject = CreateGameObjectInParent("excludes", parentObject);
|
||||
foreach (var child in excludeNode.OfType<XmlElement>()) {
|
||||
if (child.Name != "exclude") {
|
||||
Debug.LogWarning(
|
||||
$"Only 'exclude' is supported - {child.Name} isn't supported yet.",
|
||||
parentObject);
|
||||
} else {
|
||||
_modifiers.ApplyModifiersToElement(child);
|
||||
CreateGameObjectWithUniqueName<MjExclude>(excludesParentObject, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This section references joints/sites/geoms, must be parsed after worldbody.
|
||||
var tendonNode = parentNode.SelectSingleNode("tendon") as XmlElement;
|
||||
if (tendonNode != null) {
|
||||
var tendonsParentObject = CreateGameObjectInParent("tendons", parentObject);
|
||||
foreach (var child in tendonNode.OfType<XmlElement>()) {
|
||||
if (child.Name == "fixed") {
|
||||
CreateGameObjectWithUniqueName<MjFixedTendon>(tendonsParentObject, child);
|
||||
} else if (child.Name == "spatial") {
|
||||
CreateGameObjectWithUniqueName<MjSpatialTendon>(tendonsParentObject, child);
|
||||
} else {
|
||||
Debug.Log($"Tendon type {child.Name} not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This section references worldbody elements + tendons, must be parsed after them.
|
||||
var equalityNode = parentNode.SelectSingleNode("equality") as XmlElement;
|
||||
if (equalityNode != null) {
|
||||
var equalitiesParentObject = CreateGameObjectInParent("equality constraints", parentObject);
|
||||
foreach (var child in equalityNode.OfType<XmlElement>()) {
|
||||
var equalityType = ParseEqualityType(child);
|
||||
_modifiers.ApplyModifiersToElement(child);
|
||||
CreateGameObjectWithUniqueName(equalitiesParentObject, child, equalityType);
|
||||
}
|
||||
}
|
||||
|
||||
// This section references joints and tendons, must be parsed after worldbody and tendon.
|
||||
var actuatorNode = parentNode.SelectSingleNode("actuator") as XmlElement;
|
||||
if (actuatorNode != null) {
|
||||
var actuatorsParentObject = CreateGameObjectInParent("actuators", parentObject);
|
||||
foreach (var child in actuatorNode.OfType<XmlElement>()) {
|
||||
_modifiers.ApplyModifiersToElement(child);
|
||||
CreateGameObjectWithUniqueName<MjActuator>(actuatorsParentObject, child);
|
||||
}
|
||||
}
|
||||
|
||||
// This section references tendons, actuators and worldbody elements, must be parsed last.
|
||||
var sensorNode = parentNode.SelectSingleNode("sensor") as XmlElement;
|
||||
if (sensorNode != null) {
|
||||
var sensorParentObject = CreateGameObjectInParent("sensors", parentObject);
|
||||
foreach (var child in sensorNode.OfType<XmlElement>()) {
|
||||
_modifiers.ApplyModifiersToElement(child);
|
||||
var sensorType = ParseSensorType(child);
|
||||
if (sensorType != null) {
|
||||
CreateGameObjectWithUniqueName(sensorParentObject, child, sensorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ParseGeom(GameObject parentObject, XmlElement child) {
|
||||
var gameObject = CreateGameObjectWithUniqueName<MjGeom>(parentObject, child);
|
||||
// Add the visuals.
|
||||
gameObject.AddComponent<MjMeshFilter>();
|
||||
var renderer = gameObject.AddComponent<MeshRenderer>();
|
||||
renderer.sharedMaterial = DefaultMujocoMaterial;
|
||||
}
|
||||
|
||||
private GameObject CreateGameObjectInParent(string name, GameObject parentObject) {
|
||||
var createdObject = new GameObject(name);
|
||||
createdObject.transform.parent = parentObject.transform;
|
||||
return createdObject;
|
||||
}
|
||||
|
||||
private void ParseBodyChildren(GameObject parentObject, XmlElement parentNode) {
|
||||
foreach (var child in parentNode.Cast<XmlNode>().OfType<XmlElement>()) {
|
||||
_modifiers.ApplyModifiersToElement(child);
|
||||
|
||||
if (_customNodeHandlers.TryGetValue(child.Name, out var handler)) {
|
||||
handler?.Invoke(child, parentObject);
|
||||
} else {
|
||||
ParseBodyChild(child, parentObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called by ParseBodyChildren for each XML node, overridable by inheriting claases.
|
||||
private void ParseBodyChild(XmlElement child, GameObject parentObject) {
|
||||
switch (child.Name) {
|
||||
case "geom": {
|
||||
// this one's different because it's overwritten in MjImporterWithAssets.
|
||||
ParseGeom(parentObject, child);
|
||||
break;
|
||||
}
|
||||
case "inertial": {
|
||||
CreateGameObjectWithUniqueName<MjInertial>(parentObject, child);
|
||||
break;
|
||||
}
|
||||
case "body": {
|
||||
var childObject = CreateGameObjectWithUniqueName<MjBody>(parentObject, child);
|
||||
ParseBodyChildren(childObject, child);
|
||||
break;
|
||||
}
|
||||
case "joint": {
|
||||
var jointType = ParseJointType(child);
|
||||
CreateGameObjectWithUniqueName(parentObject, child, jointType);
|
||||
break;
|
||||
}
|
||||
case "freejoint": {
|
||||
CreateGameObjectWithUniqueName<MjFreeJoint>(parentObject, child);
|
||||
break;
|
||||
}
|
||||
case "site": {
|
||||
CreateGameObjectWithUniqueName<MjSite>(parentObject, child);
|
||||
break;
|
||||
}
|
||||
case "camera": {
|
||||
CreateGameObjectWithCamera(parentObject, child);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Debug.Log($"The importer does not yet support tags <{child.Name}>.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Type ParseEqualityType(XmlElement node) {
|
||||
Type equalityType = null;
|
||||
switch (node.Name) {
|
||||
case "connect":
|
||||
equalityType = typeof(MjConnect);
|
||||
break;
|
||||
case "weld":
|
||||
equalityType = typeof(MjWeld);
|
||||
break;
|
||||
case "joint":
|
||||
equalityType = typeof(MjJointConstraint);
|
||||
break;
|
||||
case "tendon":
|
||||
equalityType = typeof(MjTendonConstraint);
|
||||
break;
|
||||
case "distance":
|
||||
equalityType = typeof(MjDistance);
|
||||
break;
|
||||
default:
|
||||
Debug.Log($"The importer does not yet support equality <{node.Name}>.");
|
||||
break;
|
||||
}
|
||||
return equalityType;
|
||||
}
|
||||
|
||||
private static Type ParseSensorType(XmlElement node) {
|
||||
Type sensorType = null;
|
||||
switch (node.Name) {
|
||||
case "actuatorpos":
|
||||
case "actuatorvel":
|
||||
case "actuatorfrc":
|
||||
sensorType = typeof(MjActuatorScalarSensor);
|
||||
break;
|
||||
case "subtreecom":
|
||||
case "subtreelinvel":
|
||||
case "subtreeangmom":
|
||||
sensorType = typeof(MjBodyVectorSensor);
|
||||
break;
|
||||
case "jointpos":
|
||||
case "jointvel":
|
||||
case "jointlimitpos":
|
||||
case "jointlimitvel":
|
||||
case "jointlimitfrc":
|
||||
sensorType = typeof(MjJointScalarSensor);
|
||||
break;
|
||||
case "touch":
|
||||
case "rangefinder":
|
||||
sensorType = typeof(MjSiteScalarSensor);
|
||||
break;
|
||||
case "accelerometer":
|
||||
case "velocimeter":
|
||||
case "force":
|
||||
case "torque":
|
||||
case "gyro":
|
||||
case "magnetometer":
|
||||
sensorType = typeof(MjSiteVectorSensor);
|
||||
break;
|
||||
case "framequat":
|
||||
switch (node.GetAttribute("objtype")) {
|
||||
case "body":
|
||||
case "xbody":
|
||||
sensorType = typeof(MjBodyQuaternionSensor);
|
||||
break;
|
||||
case "geom":
|
||||
sensorType = typeof(MjGeomQuaternionSensor);
|
||||
break;
|
||||
case "site":
|
||||
sensorType = typeof(MjSiteQuaternionSensor);
|
||||
break;
|
||||
default:
|
||||
Debug.Log($"camera sensors unsupported <{node.Name}>.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "framepos":
|
||||
case "framexaxis":
|
||||
case "frameyaxis":
|
||||
case "framezaxis":
|
||||
case "framelinvel":
|
||||
case "frameangvel":
|
||||
case "framelinacc":
|
||||
case "frameangacc":
|
||||
switch (node.GetAttribute("objtype")) {
|
||||
case "body":
|
||||
case "xbody":
|
||||
sensorType = typeof(MjBodyVectorSensor);
|
||||
break;
|
||||
case "geom":
|
||||
sensorType = typeof(MjGeomVectorSensor);
|
||||
break;
|
||||
case "site":
|
||||
sensorType = typeof(MjSiteVectorSensor);
|
||||
break;
|
||||
default:
|
||||
Debug.Log($"camera sensors unsupported <{node.Name}>.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Debug.Log($"The importer does not yet support sensor <{node.Name}>.");
|
||||
break;
|
||||
}
|
||||
return sensorType;
|
||||
}
|
||||
|
||||
private Type ParseJointType(XmlElement parentNode) {
|
||||
Type jointType = null;
|
||||
switch (parentNode.GetStringAttribute("type", defaultValue: string.Empty)) {
|
||||
case "hinge":
|
||||
jointType = typeof(MjHingeJoint);
|
||||
break;
|
||||
case "ball":
|
||||
jointType = typeof(MjBallJoint);
|
||||
break;
|
||||
case "slide":
|
||||
jointType = typeof(MjSlideJoint);
|
||||
break;
|
||||
case "free":
|
||||
jointType = typeof(MjFreeJoint);
|
||||
break;
|
||||
default:
|
||||
// According to http://mujoco.org/book/XMLreference.html#joint, the default type for a joint
|
||||
// is Hinge.
|
||||
jointType = typeof(MjHingeJoint);
|
||||
break;
|
||||
}
|
||||
return jointType;
|
||||
}
|
||||
|
||||
private GameObject CreateGameObjectWithCamera(
|
||||
GameObject parentObject, XmlElement parentNode) {
|
||||
var name = GenerateUniqueName(parentNode);
|
||||
var gameObject = new GameObject(name);
|
||||
gameObject.transform.parent = parentObject.transform;
|
||||
MjEngineTool.ParseTransformMjcf(parentNode, gameObject.transform);
|
||||
if (parentNode.GetStringAttribute("mode") == "targetbody") {
|
||||
// Look at the parent body:
|
||||
gameObject.transform.localRotation = Quaternion.LookRotation(
|
||||
gameObject.transform.InverseTransformPoint(parentObject.transform.position));
|
||||
} else {
|
||||
// MuJoCo's camera convention is to look down the negative z, which post-swizzle became
|
||||
// negative y, while Unity looks down positive z:
|
||||
gameObject.transform.localRotation *=
|
||||
Quaternion.FromToRotation(Vector3.forward, -Vector3.up);
|
||||
}
|
||||
var camera = gameObject.AddComponent<Camera>();
|
||||
camera.fieldOfView = parentNode.GetFloatAttribute("fovy", defaultValue: 45.0f);
|
||||
camera.nearClipPlane = 0.01f; // MuJoCo default, TODO(etom): get from visual/map/znear
|
||||
return gameObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f80b94c6ee938d716af9c3bc5d83b8f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user