191 lines
7.2 KiB
C#
191 lines
7.2 KiB
C#
// 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 System.Globalization;
|
|
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 bool GetLimitedAttribute(
|
|
this XmlElement element, string name, bool rangeDefined) {
|
|
var strValue = element.GetStringAttribute(name, "auto");
|
|
if (strValue == "auto" && rangeDefined && element.GetAutolimitsEnabled()) return true;
|
|
if (strValue == "auto") return false;
|
|
|
|
bool parsedValue;
|
|
if (bool.TryParse(strValue, out parsedValue)) {
|
|
return parsedValue;
|
|
} else {
|
|
throw new ArgumentException($"'{strValue}' is not a bool.");
|
|
}
|
|
}
|
|
|
|
public static bool GetAutolimitsEnabled(
|
|
this XmlElement element) {
|
|
bool autolimits = (element.OwnerDocument?.GetElementsByTagName("compiler")[0]?["compiler"])
|
|
?.GetBoolAttribute("autolimits", true) ??
|
|
true;
|
|
return autolimits;
|
|
}
|
|
|
|
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, NumberStyles.Any, CultureInfo.InvariantCulture, 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], NumberStyles.Any, CultureInfo.InvariantCulture, 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;
|
|
}
|
|
}
|
|
}
|