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
+1
View File
@@ -13,4 +13,5 @@
XMLreference
programming
APIreference
unity
changelog
+333
View File
@@ -0,0 +1,333 @@
=============
Unity Plug-in
=============
Introduction
------------
The MuJoCo `Unity plug-in <https://github.com/deepmind/mujoco/tree/main/unity>`_ allows the Unity Editor and runtime to use the MuJoCo physics engine. Users can import MJCF
files and edit the models in the Editor. The plug-in relies on Unity for most aspects -- assets, game logic, simulation
time -- but uses MuJoCo to determine how objects move, giving the designer access to MuJoCo's full API.
.. _UInstallation:
Installation instructions
-------------------------
The plug-in directory (available at https://github.com/deepmind/mujoco/tree/main/unity) includes a ``package.json``
file. Unity's package manager recognizes this file and will import the plug-in's C# codebase to your project. In
addition, Unity also needs the native MuJoCo library, which can be found in the specific platfomr archive at
https://github.com/deepmind/mujoco/release.
On Unity version 2020.2 and later, the Package Manager will look for the native library file and copy it to the package
directory when the package is imported. Alternatively, you can manually copy the native library to the package directory
and rename it, see platform-specific instructions below. The library can also be copied into any location under your
project's Assets directory.
MacOS
_____
The MuJoCo app needs to be run at least once before the native library can be used, in order to register the library as
a trusted binary. Then, copy the dynamic library file from
``/Applications/MuJoCo.app/Contents/Frameworks/MuJoCo.framework/Versions/Current/libmujoco.2.1.1.dylib`` (it can be
found by browsing the contents of ``MuJoCo.app``) and rename it as ``mujoco.dylib``.
Linux
_____
Expand the ``tar.gz`` archive to ``~/.mujoco``. Then copy the dynamic library from
``~/.mujoco/mujoco-2.1.1/lib/libmujoco_nogl.so.2.1.1`` (note the ``_nogl`` suffix) and rename it as ``libmujoco.so``.
Windows
_______
Expand the ``zip`` archive to a directory called ``MuJoCo`` in your user directory, and copy the file
``MuJoCo\bin\mujoco.dll``.
.. _UUsing:
Using the plug-in
-----------------
.. _UImporter:
Importer
________
The importer is invoked from the Editor's *Asset* menu: click on "Import MuJoCo Scene" and select the XML file with your
model's MJCF specification.
Context menus
_____________
- Right-clicking a geom component offers two options:
- "Add mesh renderer" adds components to the same game object that render the geom: a standard ``MeshRenderer`` and a
``MjMeshFilter`` that creates a procedural mesh that is recreated when the geom shape properties change.
- "Convert to free object" adds two new game objects: a parent with an ``MjBody`` component and a sibling with an
``MjFreeJoint`` component. This allows the previously static geom to move about freely in the scene. This action
only applies to "world" geoms -- those that do not currently have an ``MjBody`` parent.
- Right-clicking a Unity Collider offers the option to "Add a matching MuJoCo geom" to the same game object. Note that
this does not comprise a complete conversion of the physics -- Rigidbody, ArticulationBody and Joint configurations
still need to be recreated manually.
Mouse spring
____________
When the selected game object has an ``MjBody`` component, spring forces can be applied to this body towards the mouse
cursor through a control-left-drag action in the Scene view. The 3D position of the spring force origin is found by
projecting the mouse position on a plane defined by the camera X direction and the world Y direction. Adding the shift
key changes the projection plane to be parallel to the world's X and Z axes.
.. _UTips:
Tips to Unity users
___________________
- If any compilation or runtime errors are encountered, the state of the system is undefined. Therefore, we recommend
turning on “Error Pause” in the console window.
- In PhysX, every `Rigidbody` is a “free body”. In contrast, MuJoCo requires explicit specification of joints for
mobility. For convenience, we provide a context menu for “freeing” a world geom (i.e., an ``MjGeom`` component without
any ``MjBody`` ancestor) by adding a parent ``MjBody`` and a sibling ``MjFreeJoint``.
- The plug-in doesnt support collision detection without physical presence, so there is no built-in notion of trigger
colliders. The presence or absence of a contact force can be read by adding a touch sensor and reading its
``SensorReading`` value (which will correspond to the normal force, see `touch sensor documentation <sensor-touch>`).
.. _UDesign:
Design principles
-----------------
The plug-in design provides a one-to-one mapping between MJCF elements and Unity components. In order to simulate a
Unity scene (e.g., when the user hits the “play” button in the Editor) using MuJoCo, the plug-in:
1. Scans the GameObject hierarchy in the scene for MuJoCo components.
2. Creates an MJCF description and passes it to MuJoCos compiler.
3. Binds every component to the MuJoCo runtime via the corresponding index in MuJoCos data structures. This index is
used for updating Unitys transforms during simulation.
This design principle has several implications:
- Most fields of the Unity components correspond directly to MJCF attributes. Therefore, the user can refer to the
MuJoCo documentation for details on the semantics of different values.
- The layout of MuJoCo components in the GameObject hierarchy determines the layout of the resulting MuJoCo model.
Therefore, we adopt a design rule that **every game object must have at most one MuJoCo component**.
- We rely on Unity for spatial configuration, which requires vector components to be `swizzled
<https://en.wikipedia.org/wiki/Swizzling_(computer_graphics)>`_ since Unity uses left-handed frames with Y as the
vertical axis, while MuJoCo uses right-handed frames with Z as the vertical axis.
- Unity transform scaling affects positions, orientations, and scale of the entire game object subtree. However, MuJoCo
doesnt support collision of skewed cylinders and capsules (skewed spheres are supported via the ellipsoid primitive).
The gizmo for geoms and sites ignores this skew (similarly to PhysX colliders), and will always show the primitive
shape as it will appear to the physics.
- During runtime, changing values of component fields will not trigger scene recreation, so it will have no immediate
effect on the physics. However, the new values will be loaded upon the next scene recreation.
Wherever possible, we do things the Unity Way: gravity is read from Unitys physics settings, and the simulation step is
read from Unitys Time Managers `Fixed Timestep`. All aspects of appearance (e.g., meshes, materials, and textures)
are handled by Unitys Asset Manager, and RGBA specifications are done using material assets.
.. _UNotes:
Implementation notes
--------------------
Importer workflow
_________________
When the user selects an MJCF file, the importer first loads
the file in MuJoCo, saves it to a temporary location, and then processes the generated saved file. This has several
effects:
- It validates the MJCF - we are guaranteed that the saved MJCF matches the `schema <CSchema>`_.
- It validates the assets (materials, meshes, textures) and imports these assets into Unity, as well as creating new
material assets for geom RGBA specification.
- It allows the importer to handle :ref:`\<include\> <include>` elements without replicating MuJoCos file-system
workflow.
- The current version of MuJoCo generates MJCF files with explicit :ref:`\<inertial\> <inertial>` elements, even when
the original model uses geoms for implicit definition of the body inertia. If you plan to change geom properties of
an imported model, remove these auto-generated ``MjInertial`` components manually. We plan to address this in a
future release of MuJoCo.
In Unity, there is no equivalent to MJCFs “cascading” :ref:`\<default\> <default>` clauses. Therefore, components in
Unity reflect the corresponding elements state after applying all the relevant default classes, and the class structure
in the original MJCF is discarded.
The MuJoCo Scene
________________
When a MuJoCo scene is created, the ``MjScene`` component first scans the scene for all instances of ``MjComponent``.
Each component creates its own MJCF element using Unity scenes spatial structure to describe the models initial
reference pose (called ``qpos0`` in MuJoCo). ``MjScene`` combines these XML elements according to the hierarchy of the
respective game objects and creates a single MJCF description of the physics model. It then creates the runtime structs
``mjModel`` and ``mjData``, and binds each component to the runtime by identifying its unique index.
During runtime, ``MjScene.FixedUpdate()`` calls :ref:`mj_step`, and then synchronizes the state of each game object
according to the index ``MjComponent.MujocoId`` identified at binding time. An ``MjScene`` component is added
automatically when the application starts (e.g., when the user hits “play”) if and only if the scene includes any MuJoCo
components. If your applications initialization phase involves ticking the physics while adding game objects and
components, you can call ``MjScene.CreateScene()`` when the initialization phase is over.
Scene recreation maintains continuity of physics and state in the following way:
1. The position and velocity of joints is cached.
2. MuJoCos state is reset (to ``qpos0``) and Unity transforms are synchronized.
3. A new XML is generated, creating a model that has the same ``qpos0`` as the previous one for the joints that
persisted.
4. The MuJoCo state (for the joints that persisted) is set from the cache, and Unity transforms are synchronized.
Because the MuJoCo library doesnt (yet) expose an API for scene editing, adding and removing MuJoCo components causes
complete scene recreation. This can be expensive for large models or if it happens frequently. We expect this
performance limitation to be lifted in future versions of MuJoCo.
Global Settings
_______________
An exception to the one-element-per-one-component is the Global Settings component. This component is responsible for
all the configuration options that are included in the fixed-size, singleton, global elements of MJCF. Currently it
holds information that corresponds to the :ref:`\<option\> <option>` and :ref:`\<size\> <size>` elements, and in the
future it will also be used for the :ref:`\<compiler\> <compiler>` element, if/when fields there will be relevant to the
Unity plug-in.
Invoking the importer at application runtime
____________________________________________
The importer is implemented by the class ``MjImporterWithAssets``, which is a subclass of ``MjcfImporter``. This parent
class takes an MJCF string and generates the hierarchy of components. It can be invoked at play-time (it doesnt
involve Editor functionality), and it doesnt invoke any functions of the MuJoCo library. This is useful when MuJoCo
models are generated procedurally (e.g., by some evolutionary process) and/or when an MJCF is imported only to be
converted (e.g., to PhysX, or URDF). Since it cannot interact with Unitys ``AssetManager`` (which is a feature of the
Editor), this classs functionality is restricted. Specifically:
- It ignores all assets (including collision meshes).
- It ignores visuals (including RGBA specifications).
MuJoCo sensor components
________________________
MuJoCo defines many sensors, and we were concerned that creating a separate ``MjComponent`` class for each would lead to
a lot of code duplication. Therefore, we created classes according to the type of object (actuator / body / geom /
joint / site) whose properties are measured, and the type (scalar / vector / quaternion) of the measured data.
Heres a table that maps types to sensors:
+------------------------+---------------+---------------------+
| **Mujoco Object Type** | **Data Type** | **Sensor Name** |
+------------------------+---------------+---------------------+
| Actuator | Scalar | - ``actuatorpos`` |
| | | - ``actuatorvel`` |
| | | - ``actuatorfrc`` |
+------------------------+---------------+---------------------+
| Body | Vector | - ``subtreecom`` |
| | | - ``subtreelinvel`` |
| | | - ``subtreeangmom`` |
| | | - ``framepos`` |
| | | - ``framexaxis`` |
| | | - ``frameyaxis`` |
| | | - ``framezaxis`` |
| | | - ``framelinvel`` |
| | | - ``frameangvel`` |
| | | - ``framelinacc`` |
| | | - ``frameangacc`` |
+------------------------+---------------+---------------------+
| Body | Quaternion | - ``framequat`` |
+------------------------+---------------+---------------------+
| Geom | Vector | - ``framepos`` |
| | | - ``framexaxis`` |
| | | - ``frameyaxis`` |
| | | - ``framezaxis`` |
| | | - ``framelinvel`` |
| | | - ``frameangvel`` |
| | | - ``framelinacc`` |
| | | - ``frameangacc`` |
+------------------------+---------------+---------------------+
| Geom | Quaternion | - ``framequat`` |
+------------------------+---------------+---------------------+
| Joint | Scalar | - ``jointpos`` |
| | | - ``jointvel`` |
| | | - ``jointlimitpos`` |
| | | - ``jointlimitvel`` |
| | | - ``jointlimitfrc`` |
+------------------------+---------------+---------------------+
| Site | Scalar | - ``touch`` |
| | | - ``rangefinder`` |
+------------------------+---------------+---------------------+
| Site | Vector | - ``accelerometer`` |
| | | - ``velocimeter`` |
| | | - ``force`` |
| | | - ``torque`` |
| | | - ``gyro`` |
| | | - ``magnetometer`` |
| | | - ``framepos`` |
| | | - ``framexaxis`` |
| | | - ``frameyaxis`` |
| | | - ``framezaxis`` |
| | | - ``framelinvel`` |
| | | - ``frameangvel`` |
| | | - ``framelinacc`` |
| | | - ``frameangacc`` |
+------------------------+---------------+---------------------+
| Site | Quaternion | - ``framequat`` |
+------------------------+---------------+---------------------+
Heres the same table in reverse, mapping sensors to classes:
================= ===================================
Sensor Name Plugin Class
================= ===================================
``accelerometer`` SiteVector
``actuatorfrc`` ActuatorScalar
``actuatorpos`` ActuatorScalar
``actuatorvel`` ActuatorScalar
``force`` SiteVector
``frameangacc`` \*Vector (depends on frame type)
``frameangvel`` \*Vector (depends on frame type)
``framelinacc`` \*Vector (depends on frame type)
``framelinvel`` \*Vector (depends on frame type)
``framepos`` \*Vector (depends on frame type)
``framequat`` \*Quaternion (depends on frame type)
``framexaxis`` \*Vector (depends on frame type)
``frameyaxis`` \*Vector (depends on frame type)
``framezaxis`` \*Vector (depends on frame type)
``gyro`` SiteVector
``jointlimitfrc`` JointScalar
``jointlimitpos`` JointScalar
``jointlimitvel`` JointScalar
``jointpos`` JointScalar
``jointvel`` JointScalar
``magnetometer`` SiteVector
``subtreeangmom`` BodyVector
``subtreecom`` BodyVector
``subtreelinvel`` BodyVector
``torque`` SiteVector
``touch`` SiteScalar
``velocimeter`` SiteVector
================= ===================================
The following sensors are not yet implemented:
| ``tendonpos``
| ``tendonvel``
| ``ballquat``
| ``ballangvel``
| ``tendonlimitpos``
| ``tendonlimitvel``
| ``tendonlimitfrc``
| ``user``
Mesh Shapes
___________
The plug-in allows using arbitrary Unity meshes for MuJoCo collision. At model compilation, MuJoCo calls `qhull
<http://www.qhull.org/>`__ to create a convex hull of the mesh, and uses that for collisions. Currently the computed
convex hull is not visible in Unity, but we intend to expose it in future versions.
Interaction with External Processes
___________________________________
Robotis `MuJoCo plug-in for Unity <https://roboti.us/download.html>`_ steps the simulation in an external Python
process, and uses Unity only for rendering. In contrast, our plug-in relies on Unity to step the simulation. It should
be possible to use our plug-in while an external process "drives" the simulation, for example by seting ``qpos``,
calling ``mj_kinematics``, synchronizing the transforms, and then using Unity to render or compute game logic. In order
to establish communication with an external process, you can use Unity's `ML-Agents
<https://github.com/Unity-Technologies/ml-agents>`_ package.
+3 -2
View File
@@ -56,7 +56,8 @@
rgb1="0.6 0.6 0.6" rgb2="0.6 0.6 0.6" markrgb="1 1 1"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".4 .4 .4" rgb2=".6 .6 .6"
width="512" height="512"/>
<material name='MatPlane' reflectance='0.3' texture="texplane" texrepeat="1 1" texuniform="true"/>
<material name='MatPlane' reflectance='0.3' texture="texplane" texrepeat="1 1" texuniform="true"
rgba=".7 .7 .7 1"/>
<material name='object1' texture="texgeom" texuniform="true" rgba=".4 .9 .6 1" />
<material name='object2' texture="texgeom" texuniform="true" rgba=".4 .6 .9 1" />
<material name='object3' texture="texgeom" texuniform="true" rgba=".4 .9 .9 1" />
@@ -73,7 +74,7 @@
<worldbody>
<light directional="true" diffuse=".8 .8 .8" pos="0 0 10" dir="0 0 -10"/>
<geom pos="0 0 0" type="plane" size="3 3 .5" rgba=".7 .7 .7 1" material="MatPlane"/>
<geom pos="0 0 0" type="plane" size="3 3 .5" material="MatPlane"/>
<geom class="border" fromto="-3 3 0 3 3 0" />
<geom class="border" fromto="-3 -3 0 3 -3 0" />
<geom class="border" fromto="3 3 0 3 -3 0" />
+5
View File
@@ -0,0 +1,5 @@
# Changelog
## [2.1.1] - 2022-02-02
### Initial release
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e0f2a83d7d240967bb5945d478c8b7f5
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 19efbd5e1b0bbc43b9f6a3defa20bb80
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f5a5ea2338a298b5e94d5f5653c0b85b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,26 @@
// 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 UnityEditor;
using UnityEngine;
namespace Mujoco {
[CustomPropertyDrawer(typeof(AbsoluteValueAttribute))]
public class AbsoluteValuePropertyDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
property.floatValue = Mathf.Abs(EditorGUI.FloatField(position, label, property.floatValue));
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ac61b66fbbd264beebb1df6cd423d122
timeCreated: 1538126279
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+199
View File
@@ -0,0 +1,199 @@
// 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 UnityEditor;
using UnityEngine;
namespace Mujoco {
[CustomEditor(typeof(MjActuator))]
[CanEditMultipleObjects]
public class MjActuatorEditor : Editor {
private bool _showCustomParams = false;
private bool _showDebugValues = false;
private bool _showDynPrm = false;
private bool _showGainPrm = false;
private bool _showBiasValues = false;
// General actuator properties.
private SerializedProperty _dynType;
private SerializedProperty _gainType;
private SerializedProperty _biasType;
private SerializedProperty _dynPrm;
private SerializedProperty _gainPrm;
private SerializedProperty _biasPrm;
// Position actuator properties.
private SerializedProperty _kp;
// Velocity actuator properties.
private SerializedProperty _kv;
// Cylinder actuator properties.
private SerializedProperty _cylinderTimeConst;
private SerializedProperty _area;
private SerializedProperty _diameter;
private SerializedProperty _bias;
// Cylinder actuator properties.
private SerializedProperty _muscleTimeConst;
private SerializedProperty _range;
private SerializedProperty _force;
private SerializedProperty _scale;
private SerializedProperty _lmin;
private SerializedProperty _lmax;
private SerializedProperty _vmax;
private SerializedProperty _fpmax;
private SerializedProperty _fvmax;
public void OnEnable() {
_showCustomParams = false;
_showDebugValues = false;
_showDynPrm = false;
_showGainPrm = false;
_showBiasValues = false;
var customParams = serializedObject.FindProperty("CustomParams");
// General actuator properties.
_dynType = customParams.FindPropertyRelative("DynType");
_gainType = customParams.FindPropertyRelative("GainType");
_biasType = customParams.FindPropertyRelative("BiasType");
_dynPrm = customParams.FindPropertyRelative("DynPrm");
_gainPrm = customParams.FindPropertyRelative("GainPrm");
_biasPrm = customParams.FindPropertyRelative("BiasPrm");
// Position actuator properties.
_kp = customParams.FindPropertyRelative("Kp");
// Velocity actuator properties.
_kv = customParams.FindPropertyRelative("Kv");
// Cylinder actuator properties.
_cylinderTimeConst = customParams.FindPropertyRelative("CylinderTimeConst");
_area = customParams.FindPropertyRelative("Area");
_diameter = customParams.FindPropertyRelative("Diameter");
_bias = customParams.FindPropertyRelative("Bias");
// Cylinder actuator properties.
_muscleTimeConst = customParams.FindPropertyRelative("MuscleTimeConst");
_range = customParams.FindPropertyRelative("Range");
_force = customParams.FindPropertyRelative("Force");
_scale = customParams.FindPropertyRelative("Scale");
_lmin = customParams.FindPropertyRelative("LMin");
_lmax = customParams.FindPropertyRelative("LMax");
_vmax = customParams.FindPropertyRelative("VMax");
_fpmax = customParams.FindPropertyRelative("FpMax");
_fvmax = customParams.FindPropertyRelative("FvMax");
}
public override void OnInspectorGUI() {
serializedObject.Update();
foreach (var target in serializedObject.targetObjects) {
EditActuator(target as MjActuator);
}
serializedObject.ApplyModifiedProperties();
}
private void EditActuator(MjActuator actuator) {
DrawDefaultInspector();
_showCustomParams = EditorGUILayout.Foldout(_showCustomParams, "Custom Params");
if (_showCustomParams) {
EditCustomParams(actuator.Type, actuator.CustomParams);
}
_showDebugValues = EditorGUILayout.Foldout(_showDebugValues, "Debug");
if (_showDebugValues) {
using (new EditorGUI.DisabledScope(true)) {
EditorGUILayout.FloatField("Length", actuator.Length);
EditorGUILayout.FloatField("Velocity", actuator.Velocity);
EditorGUILayout.FloatField("Force", actuator.Force);
}
}
}
private void EditCustomParams(MjActuator.ActuatorType type,
MjActuator.CustomParameters parameters) {
switch (type) {
case MjActuator.ActuatorType.General: {
EditGeneralParams(parameters);
break;
}
case MjActuator.ActuatorType.Position: {
EditPositionParams(parameters);
break;
}
case MjActuator.ActuatorType.Velocity: {
EditVelocityParams(parameters);
break;
}
case MjActuator.ActuatorType.Cylinder: {
EditCylinderParams(parameters);
break;
}
case MjActuator.ActuatorType.Muscle: {
EditMuscleParams(parameters);
break;
}
}
}
private void EditGeneralParams(MjActuator.CustomParameters parameters) {
EditorGUILayout.PropertyField(_dynType);
EditorGUILayout.PropertyField(_gainType);
EditorGUILayout.PropertyField(_biasType);
_showDynPrm = EditorGUILayout.Foldout(_showDynPrm, "DynPrm");
if (_showDynPrm) {
for (var i = 0; i < parameters.DynPrm.Count; ++i) {
EditorGUILayout.PropertyField(_dynPrm.GetArrayElementAtIndex(i));
}
}
_showGainPrm = EditorGUILayout.Foldout(_showGainPrm, "GainPrm");
if (_showGainPrm) {
for (var i = 0; i < parameters.GainPrm.Count; ++i) {
EditorGUILayout.PropertyField(_gainPrm.GetArrayElementAtIndex(i));
}
}
_showBiasValues = EditorGUILayout.Foldout(_showBiasValues, "BiasPrm");
if (_showBiasValues) {
for (var i = 0; i < parameters.BiasPrm.Count; ++i) {
EditorGUILayout.PropertyField(_biasPrm.GetArrayElementAtIndex(i));
}
}
}
private void EditPositionParams(MjActuator.CustomParameters parameters) {
EditorGUILayout.PropertyField(_kp);
}
private void EditVelocityParams(MjActuator.CustomParameters parameters) {
EditorGUILayout.PropertyField(_kv);
}
private void EditCylinderParams(MjActuator.CustomParameters parameters) {
EditorGUILayout.PropertyField(_cylinderTimeConst);
EditorGUILayout.PropertyField(_area);
EditorGUILayout.PropertyField(_diameter);
_showBiasValues = EditorGUILayout.Foldout(_showBiasValues, "Bias");
if (_showBiasValues) {
for (var i = 0; i < parameters.Bias.Length; ++i) {
EditorGUILayout.PropertyField(_bias.GetArrayElementAtIndex(i));
}
}
}
private void EditMuscleParams(MjActuator.CustomParameters parameters) {
EditorGUILayout.PropertyField(_muscleTimeConst, new GUIContent("TimeConst"));
EditorGUILayout.PropertyField(_range);
EditorGUILayout.PropertyField(_force);
EditorGUILayout.PropertyField(_scale);
EditorGUILayout.PropertyField(_lmin);
EditorGUILayout.PropertyField(_lmax);
EditorGUILayout.PropertyField(_vmax);
EditorGUILayout.PropertyField(_fpmax);
EditorGUILayout.PropertyField(_fvmax);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 76f90e10df489475490b387a53dd88da
timeCreated: 1547050687
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+77
View File
@@ -0,0 +1,77 @@
// 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 UnityEditor;
using UnityEngine;
namespace Mujoco {
[CustomEditor(typeof(MjGeom), true)]
[CanEditMultipleObjects]
public class MjGeomEditor : MjShapeComponentEditor {
public override void OnInspectorGUI() {
serializedObject.Update();
EditorGUILayout.PropertyField(serializedObject.FindProperty("Mass"));
EditorGUILayout.PropertyField(serializedObject.FindProperty("Density"));
serializedObject.ApplyModifiedProperties();
base.OnInspectorGUI();
}
[MenuItem("CONTEXT/MjGeom/Add mesh renderer")]
private static void AddMeshComponents(MenuCommand menuCommand) {
var geom = menuCommand.context as MjGeom;
geom.gameObject.AddComponent<MjMeshFilter>();
geom.gameObject.AddComponent<MeshRenderer>().material = new Material(Shader.Find("Diffuse"));
}
[MenuItem("CONTEXT/MjGeom/Convert to free object")]
private static void ConvertToFreeObject(MenuCommand menuCommand) {
var geom = menuCommand.context as MjGeom;
if (geom.GetComponentInParent<MjBody>()) {
Debug.LogError("This geom already has a Body parent.", geom.GetComponentInParent<MjBody>());
return;
}
var parent = new GameObject(geom.gameObject.name + " Body").transform;
var root = geom.transform.root;
if (root != geom.transform) {
parent.parent = geom.transform.parent;
}
parent.gameObject.AddComponent<MjBody>();
var joint = new GameObject("Free Joint").AddComponent<MjFreeJoint>();
joint.transform.parent = parent;
geom.transform.parent = parent;
}
[MenuItem("CONTEXT/Collider/Add a matching MuJoCo geom")]
private static void AddMatchingGeom(MenuCommand menuCommand) {
var collider = menuCommand.context as Collider;
collider.enabled = false;
var geom = collider.gameObject.AddComponent<MjGeom>();
if (collider as BoxCollider) {
geom.ShapeType = MjShapeComponent.ShapeTypes.Box;
} else if (collider as SphereCollider) {
geom.ShapeType = MjShapeComponent.ShapeTypes.Sphere;
} else if (collider as CapsuleCollider) {
geom.ShapeType = MjShapeComponent.ShapeTypes.Capsule;
} else if (collider as MeshCollider) {
geom.ShapeType = MjShapeComponent.ShapeTypes.Mesh;
((MjMeshShape)geom.Shape).Mesh = ((MeshCollider)collider).sharedMesh;
} else {
Debug.LogError("Collider type not supported yet.", collider);
collider.enabled = true;
GameObject.DestroyImmediate(geom);
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: d5409b149ad694715a57f4dc8bb7a857
timeCreated: 1552041114
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+58
View File
@@ -0,0 +1,58 @@
// 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 UnityEditor;
using UnityEditor.AnimatedValues;
using UnityEngine;
namespace Mujoco {
public static class MjHandles {
// Renders a visualization of an axis, anchored at the specified world space origin.
//
// Args:
// origin: World space coordinates of the anchoring point for the visualization.
// direction: Axis direction.
public static void Axis(Vector3 origin, Vector3 direction) {
var size = HandleUtility.GetHandleSize(origin) * 0.3f;
var axisEnd = origin + direction * size;
var capRotation = Quaternion.LookRotation(direction);
Handles.DrawLine(origin, axisEnd);
Handles.ConeHandleCap(0, axisEnd, capRotation, size, EventType.Repaint);
}
// Renders a visualization of linear (translational) limits, anchored at the specified world
// space origin.
//
// Args:
// origin: World space coordinates of the anchoring point for the visualization.
// lower: Distance from the origin below, where the limit expires.
// upper: Distance from the origin above, where the limit expires.
// axis: Axis along which the limit works.
public static void LinearLimits(Vector3 origin, float lower, float upper, Vector3 axis) {
if (upper > lower) {
var discRadius = HandleUtility.GetHandleSize(origin) * 0.7f;
var startPosition = origin + axis * lower;
var endPosition = origin + axis * upper;
Handles.DrawSolidDisc(startPosition, axis, discRadius);
Handles.DrawSolidDisc(endPosition, axis, discRadius);
Handles.DrawLine(origin, endPosition);
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 1a810c29762134ebb9bbee3903172764
timeCreated: 1537794456
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,41 @@
// 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 UnityEditor;
using UnityEditor.AnimatedValues;
using UnityEngine;
namespace Mujoco {
[CustomEditor(typeof(MjHingeJoint))]
public class MjHingeJointEditor : Editor {
private MjHingeJoint _joint;
protected virtual void OnEnable() {
_joint = (MjHingeJoint)target;
}
protected virtual void OnSceneGUI() {
DrawHandles(_joint);
}
public static void DrawHandles(MjHingeJoint joint) {
Handles.color = Color.yellow;
MjHandles.Axis(joint.transform.position, joint.RotationAxis);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: bdaa159fcf7064108aed2229bc39ac0d
timeCreated: 1537794793
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+176
View File
@@ -0,0 +1,176 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
namespace Mujoco {
// During play mode, in the scene view, with a MjBody component selected, holding down control
// and left mouse drag will apply a force on the body. Holding shift down will change the applied
// force direction between World XZ plane and Y[camera-up].
[CustomEditor(typeof(MjBody))]
public class MjMouseSpring : Editor {
private bool _lastShiftKeyState = false;
private Plane _mouseDragPlane;
private Vector3 _mouseDragCurrentPoint = Vector3.negativeInfinity;
private Color _translucentRed = new Color(1, 0, 0, 0.1f);
public void OnDisable() {
// If we're still the hot control at this stage, we need to release.
int uniqueID = GUIUtility.GetControlID(FocusType.Passive);
if (GUIUtility.hotControl == uniqueID) {
GUIUtility.hotControl = 0;
}
}
private void SetDragOriginAndDragPlane(Vector3 planeOrigin, Vector3 normal) {
normal[1] = 0;
normal = _lastShiftKeyState ? Vector3.up : normal;
_mouseDragPlane.SetNormalAndPosition(normal, planeOrigin);
}
private void UpdatePositionOnDragPlane(Vector3 mousePosition) {
float rayDist;
Ray ray = HandleUtility.GUIPointToWorldRay(mousePosition);
if (_mouseDragPlane.Raycast(ray, out rayDist)) {
_mouseDragCurrentPoint = ray.GetPoint(rayDist);
}
}
private void DrawMouseSpringGui(Vector3 bodyPosition, Vector3 direction, Camera renderCamera) {
// Update and draw the drag indicator.
var rot = Quaternion.FromToRotation(Vector3.up, direction);
// Backup defaults.
var backupColour = Handles.color;
// Update the drag plane indicator disc and outline...
var discRadius = (_mouseDragCurrentPoint - bodyPosition).magnitude;
Handles.color = Color.red;
Handles.DrawWireDisc(bodyPosition, _mouseDragPlane.normal, discRadius);
Handles.color = _translucentRed;
Handles.DrawSolidDisc(bodyPosition, _mouseDragPlane.normal, discRadius);
// ...and give it a phat poly line to show the drag anchor origin.
Handles.color = Color.white;
Handles.DrawAAPolyLine(6, new Vector3[] { bodyPosition, _mouseDragCurrentPoint });
// Restore defaults.
Handles.color = backupColour;
}
public unsafe void OnSceneGUI() {
if (!Application.isPlaying) {
return;
}
var currentEvent = UnityEngine.Event.current;
// Cache the hot control to determine whether we're currently capturing mouse input.
int uniqueID = GUIUtility.GetControlID(FocusType.Passive);
// Mouse spring is active if the control key is held down and the user is dragging the
// left mouse button, or if we're already in the process of capturing mouse input.
bool mouseSpringActive = GUIUtility.hotControl == uniqueID;
bool mouseSpringStarting = currentEvent.control && currentEvent.button == 0;
if (!mouseSpringStarting && !mouseSpringActive) {
return;
}
var sceneCamera = SceneView.currentDrawingSceneView?.camera;
if (sceneCamera == null) {
Debug.LogError("SceneView.currentDrawingSceneView is null");
return;
}
MjBody body = target as MjBody;
Vector3 bodyPosition =
body != null ? body.transform.position : Vector3.zero;
var scene = MjScene.Instance;
switch (currentEvent.type) {
case EventType.MouseDown:
if (EditorWindow.mouseOverWindow == UnityEditor.EditorWindow.GetWindow<SceneView>()) {
// The mouse was pressed in the scene view, so start capturing all mouse input until
// the mouse button's released.
GUIUtility.hotControl = uniqueID;
_lastShiftKeyState = false;
SetDragOriginAndDragPlane(bodyPosition, -sceneCamera.transform.forward);
currentEvent.Use();
}
return;
case EventType.MouseDrag:
if (mouseSpringActive) {
// We're currently capturing all mouse input, so consume the event.
GUIUtility.hotControl = uniqueID;
currentEvent.Use();
}
return;
case EventType.MouseUp:
if (mouseSpringActive) {
// We're still capturing all mouse input so consume the event, but we can release our
// control over capturing input now as the mouse button's been released.
GUIUtility.hotControl = 0;
currentEvent.Use();
// as opposed to unity's addforce, xfrc_applied is persistent
scene.Data->xfrc_applied[6*body.MujocoId + 0] = 0;
scene.Data->xfrc_applied[6*body.MujocoId + 1] = 0;
scene.Data->xfrc_applied[6*body.MujocoId + 2] = 0;
}
return;
case EventType.Repaint: {
if (mouseSpringActive) {
if (currentEvent.shift != _lastShiftKeyState) {
_lastShiftKeyState = currentEvent.shift;
SetDragOriginAndDragPlane(bodyPosition, -sceneCamera.transform.forward);
}
// Raycast towards the drag plane to update _mouseDragCurrentPoint.
UpdatePositionOnDragPlane(currentEvent.mousePosition);
Vector3 bodyVel = Vector3.one;
double[] mjBodyVel = new double[6];
fixed (double* res = mjBodyVel) {
MujocoLib.mj_objectVelocity(
scene.Model, scene.Data, (int)MujocoLib.mjtObj.mjOBJ_BODY, body.MujocoId, res, 0);
// linear velocity is in the last 3 entries
bodyVel = MjEngineTool.UnityVector3(res, 1);
}
float springStiffness = 100;
var settings = MjGlobalSettings.Instance;
if (settings) {
springStiffness = settings.MouseSpringStiffness;
}
Vector3 delta = _mouseDragCurrentPoint - bodyPosition;
float mass = 1.0f / (float)scene.Model->body_invweight0[2*body.MujocoId];
Vector3 unityForce = delta * springStiffness * mass;
unityForce -= bodyVel * Mathf.Sqrt(springStiffness) * mass;
Vector3 mjForce = MjEngineTool.MjVector3(unityForce);
scene.Data->xfrc_applied[6*body.MujocoId + 0] = mjForce.x;
scene.Data->xfrc_applied[6*body.MujocoId + 1] = mjForce.y;
scene.Data->xfrc_applied[6*body.MujocoId + 2] = mjForce.z;
// Draw and immediately force a repaint.
DrawMouseSpringGui(body.transform.position, delta, sceneCamera);
SceneView.RepaintAll();
}
return;
}
}
}
}
}
#endif
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6c4ae8cbdd0cac0a387364d88408d932
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
// 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 UnityEditor;
using UnityEngine;
namespace Mujoco {
[CustomEditor(typeof(MjShapeComponent), true)]
[CanEditMultipleObjects]
public class MjShapeComponentEditor : Editor {
public override void OnInspectorGUI() {
serializedObject.Update();
var shapeType = serializedObject.FindProperty("ShapeType");
EditorGUILayout.PropertyField(shapeType);
EditorGUILayout.PropertyField(
serializedObject.FindProperty($"{shapeType.enumDisplayNames[shapeType.enumValueIndex]}"),
includeChildren: true);
serializedObject.ApplyModifiedProperties();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 21af9fef28ac0899e982e791916b5fc5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
// 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 UnityEditor;
using UnityEditor.AnimatedValues;
using UnityEngine;
namespace Mujoco {
[CustomEditor(typeof(MjSlideJoint))]
public class MjSlideJointEditor : Editor {
private MjSlideJoint _joint;
protected virtual void OnEnable() {
_joint = (MjSlideJoint)target;
}
protected virtual void OnSceneGUI() {
DrawHandles(_joint);
}
// Draw the handles for the slide joint.
public static void DrawHandles(MjSlideJoint joint) {
// Draw the handles.
if (joint.Settings.Solver.Limited) {
Handles.color = Color.blue;
MjHandles.LinearLimits(joint.transform.position, joint.RangeLower, joint.RangeUpper,
joint.SlideAxis);
}
Handles.color = Color.yellow;
MjHandles.Axis(joint.transform.position, joint.SlideAxis);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: bf7c14a263938456e9393a8d0fce7b9f
timeCreated: 1538066332
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 974c7b5fe753877ebaa54ec26e113e1c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,32 @@
// 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.IO;
using UnityEditor;
using UnityEngine;
namespace Mujoco {
// Mujoco scenes importer window.
public class MjImporterEditorWindow : EditorWindow {
[MenuItem("Assets/Import MuJoCo Scene")]
public static void Apply() {
string path = EditorUtility.OpenFilePanel("Select an MJCF model", "", "xml");
if (!string.IsNullOrEmpty(path)) {
var importer = new MjImporterWithAssets();
importer.ImportFile(path);
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 85ddf6116132e4c128ed82ac6034ce4a
timeCreated: 1538581084
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,287 @@
// 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.Linq;
using System.Reflection;
using System.Xml;
using UnityEditor;
using UnityEngine;
namespace Mujoco {
// API for importing Mujoco XML files into Unity scenes.
public class MjImporterWithAssets : MjcfImporter {
private const string _semiTransparentMaterialName = "mujoco_semitransparent_template";
private string _sourceMeshesDir;
private string _targetMeshesDir;
private string _targetAssetDir;
private unsafe MujocoLib.mjModel_* _mjModel = null;
// Imports the scene from the specified file, which should be a well-formed MJCF document.
// The imported scene will be placed under a single node with the requested name assigned.
//
// Args:
// filePath: Path to XML file.
//
// Throws:
// Exception if the parsed XML is malformed or contains rough errors. If an exception is thrown,
// the entire imported scene will be automatically deleted.
// TODO(etom) - reconsider unencouraged pattern of validation through exception side-effects.
public unsafe GameObject ImportFile(string filePath) {
// If MuJoCo can't parse the mjcfString, we abort the entire process.
// MjEngineTool.LoadModelFromString throws an exception when MuJoCo fails to parse the provided
// mjcfString.
var name = Path.GetFileNameWithoutExtension(filePath) + $"{UnityEngine.Random.Range(0,999)}";
string newPath = Path.Combine(Application.temporaryCachePath, $"{name}.xml");
_mjModel = MjEngineTool.LoadModelFromFile(filePath);
MjEngineTool.SaveModelToFile(newPath, _mjModel);
Debug.Log($"Imported MJCF loaded, saved to {newPath}");
string mjcfString = File.ReadAllText(newPath);
GameObject root = null;
try {
root = ImportString(mjcfString, name, filePath);
} finally {
MujocoLib.mj_deleteModel(_mjModel);
}
return root;
}
public GameObject ImportString(
string mjcfString, string name = null, string filePath = null) {
var mjcfXml = new XmlDocument();
mjcfXml.LoadXml(mjcfString);
// Determine the folder where the meshes are stored.
ConfigureMeshPath(filePath, name, mjcfXml);
GameObject sceneRoot = null;
try {
sceneRoot = ImportXml(mjcfXml, name);
} catch (Exception) {
// We consider any error as critical, and end the import process immediately, cleaning up
// any assets created.
AssetDatabase.DeleteAsset(_targetAssetDir);
}
return sceneRoot;
}
protected override void ParseRoot(GameObject parentObject, XmlElement parentNode) {
// This makes no references, and should be parsed first.
var assetNode = parentNode.SelectSingleNode("asset") as XmlElement;
if (assetNode != null) {
ParseAssets(assetNode);
}
base.ParseRoot(parentObject, parentNode);
}
protected override void ParseGeom(GameObject parentObject, XmlElement child) {
var gameObject = CreateGameObjectWithUniqueName<MjGeom>(parentObject, child);
gameObject.AddComponent<MjMeshFilter>();
var renderer = gameObject.AddComponent<MeshRenderer>();
ResolveOrCreateMaterial(renderer, child);
}
private void ConfigureMeshPath(string path, string projectName, XmlDocument mjcf) {
// Crate the mesh target directory.
_targetMeshesDir = Path.Combine(
Application.dataPath, "Local", "MjImports", projectName, "Resources");
_targetAssetDir = Path.Combine("Assets", "Local", "MjImports", projectName, "Resources");
if (!Directory.Exists(_targetMeshesDir)) {
Directory.CreateDirectory(_targetMeshesDir);
}
if (string.IsNullOrEmpty(path)) { // we're loading a string
return;
}
_sourceMeshesDir = Path.GetDirectoryName(path);
var compilerNode = mjcf.SelectSingleNode("/mujoco/compiler") as XmlElement;
if (compilerNode != null) {
// Parse the location of meshes
var relativeMeshDir = compilerNode.GetStringAttribute("meshdir", defaultValue: string.Empty);
_sourceMeshesDir = Path.Combine(_sourceMeshesDir, relativeMeshDir);
}
Debug.Log(
$"Meshes locations: source = {_sourceMeshesDir}, target = {_targetMeshesDir}");
}
private void ParseAssets(XmlElement parentNode) {
foreach (var child in parentNode.SelectNodes("descendant::mesh").OfType<XmlElement>()) {
_modifiers.ApplyModifiersToElement(child);
ParseMesh(child);
}
foreach (var child in parentNode.SelectNodes("descendant::material").OfType<XmlElement>()) {
_modifiers.ApplyModifiersToElement(child);
ParseMaterial(child);
}
AssetDatabase.SaveAssets();
}
private void ImportMeshFromModel(int meshIndex) {
}
// Using mesh assets involves:
// (1) Copying the asset to a Resources folder, and rescaling it during that operation.
// (2) Allowing Unity to parse it using a registered asset importer (STLMeshImporter).
// (3) Loading that asset as a Mesh resource when the referring geom is parsed.
//
// We're also using a dedicated folder to deploy the meshes that are being imported, so that the
// user can find all meshes loaded by importing a specific model.
private void ParseMesh(XmlElement parentNode) {
if (parentNode.HasAttribute("vertex")) {
throw new NotImplementedException("XML with explicit mesh info not supported yet.");
}
var fileName = parentNode.GetStringAttribute("file");
// If we want to use the element name as the asset name, we must sanitize it:
var unsanitizedAssetReferenceName =
parentNode.GetStringAttribute("name", defaultValue: string.Empty);
var assetReferenceName = MjEngineTool.Sanitize(unsanitizedAssetReferenceName);
var sourceFilePath = Path.Combine(_sourceMeshesDir, fileName);
var targetFilePath = Path.Combine(_targetMeshesDir, assetReferenceName + ".stl");
if (File.Exists(targetFilePath)) {
File.Delete(targetFilePath);
}
var scale = MjEngineTool.UnityVector3(
parentNode.GetVector3Attribute("scale", defaultValue: Vector3.one));
CopyMeshAndRescale(sourceFilePath, targetFilePath, scale);
var assetPath = Path.Combine(_targetAssetDir, assetReferenceName + ".stl");
// This asset path should be available because the MuJoCo compiler guarantees element names
// are unique, but check for completeness (and in case sanitizing the name broke uniqueness):
if (AssetDatabase.LoadMainAssetAtPath(assetPath) != null) {
throw new Exception(
$"Trying to import mesh {unsanitizedAssetReferenceName} but {assetPath} already exists.");
}
AssetDatabase.ImportAsset(assetPath);
var copiedMesh = AssetDatabase.LoadMainAssetAtPath(assetPath) as Mesh;
if (copiedMesh == null) {
throw new Exception($"Mesh {assetPath} was not imported.");
}
copiedMesh.RecalculateNormals();
copiedMesh.RecalculateTangents();
copiedMesh.RecalculateBounds();
}
private void CopyMeshAndRescale(
string sourceFilePath, string targetFilePath, Vector3 scale) {
var originalMeshBytes = File.ReadAllBytes(sourceFilePath);
var mesh = StlMeshParser.ParseBinary(originalMeshBytes, scale);
var rescaledMeshBytes = StlMeshParser.SerializeBinary(mesh);
File.WriteAllBytes(targetFilePath, rescaledMeshBytes);
}
private void ParseMaterial(XmlElement parentNode) {
var rgba = parentNode.GetFloatArrayAttribute(
"rgba", defaultValue: new float[] {1.0f, 1.0f, 1.0f, 1.0f});
var emission = parentNode.GetFloatAttribute("emission", defaultValue: 0.0f);
var reflectance = parentNode.GetFloatAttribute("reflectance", defaultValue: 0.0f);
var specular = parentNode.GetFloatAttribute("specular", defaultValue: 0.5f);
var shininess = parentNode.GetFloatAttribute("shininess", defaultValue: 0.5f);
var unsanitizedName = parentNode.GetStringAttribute("name", defaultValue: string.Empty);
var name = MjEngineTool.Sanitize(unsanitizedName);
var albedo = new Color(rgba[0], rgba[1], rgba[2], rgba[3]);
// Mujoco uses a Blinn/Phong shading model with the addition of reflectance. Unfortunately, at
// the moment of writing this comment, Unity does not come with a compatible shader. The closest
// results can be achieved using the Standard shader that implements the Cook-Torrence shading
// model.
Material material;
if (rgba[3] < 1f) {
material = new Material(AssetDatabase.LoadMainAssetAtPath(
AssetDatabase.GUIDToAssetPath(
AssetDatabase.FindAssets(_semiTransparentMaterialName)[0])) as Material);
} else {
material = new Material(Shader.Find("Standard"));
}
material.SetColor("_Color", albedo);
material.SetFloat("_Metallic", reflectance);
// In order to convert the specular/shininess parameters into glossiness/roughness,
// we perform a coarse approximation.
// In Blinn/Phong model, Shininess corresponds to the width of the specular spot, while
// the Specularity corresponds to its strength (how visible it is). In order to approximate it
// with a single parameter Glossiness, we will assume that the largest value wins. The model
// will break at the extremes (Spec~=1 & Shin~=0, Spec~=0 & Shin~=1), however it should be
// representative for the intermediate values.
float glossiness = Math.Max(specular, shininess);
// We observe that any reflective material is automatically glossy, by the property of not
// having rough surface that would scatter the incoming light.
// Instead of modifying the Shininess parameter however, we're dirrectly modifying
// the glossiness by bringing the value closer to the upper boundary.
glossiness = (1.0f - reflectance) * glossiness + reflectance;
material.SetFloat("_Glossiness", glossiness);
// We choose to define a simple emission model that only emits light, without scaling
// the brightness of the defined color. If the user requires, they should tweak the material
// settings manually.
if (emission > 0.5f) {
material.EnableKeyword("_EMISSION");
material.SetColor("_EmissionColor", albedo);
}
var assetPath = Path.Combine(_targetAssetDir, name + ".mat");
if (AssetDatabase.LoadMainAssetAtPath(assetPath) != null) {
throw new Exception(
$"Trying to create material {unsanitizedName} but {assetPath} already exists.");
}
AssetDatabase.CreateAsset(material, assetPath);
}
// Loads a named material asset, or creates an ad-hoc material asset for the specific node.
private void ResolveOrCreateMaterial(MeshRenderer renderer, XmlElement parentNode) {
// When the asset was parsed and stored its name was sanitized, so we should load it using a
// sanitized name:
var materialName =
MjEngineTool.Sanitize(parentNode.GetStringAttribute("material", defaultValue: string.Empty));
Material material = null;
if (!string.IsNullOrEmpty(materialName)) {
var assetPath = Path.Combine(_targetAssetDir, materialName + ".mat");
material = (Material)AssetDatabase.LoadAssetAtPath(assetPath, typeof(Material));
} else {
// Nodes may contain inlined color definitions, which override the assigned material colors.
if (parentNode.HasAttribute("rgba")) {
// We need a bespoke copy of the material from the database for this particular node.
var rgba = parentNode.GetFloatArrayAttribute(
"rgba", defaultValue: new float[] {1.0f, 1.0f, 1.0f, 1.0f});
if (rgba[3] < 1f) {
material = new Material(
AssetDatabase.LoadMainAssetAtPath(
AssetDatabase.GUIDToAssetPath(
AssetDatabase.FindAssets(_semiTransparentMaterialName)[0])) as Material);
} else {
material = new Material(Shader.Find("Standard"));
}
material.color = new Color(rgba[0], rgba[1], rgba[2], rgba[3]);
// We use the geom's name, guaranteed to be unique, as the asset name.
// If geom is nameless, use a random number.
var name =
MjEngineTool.Sanitize(parentNode.GetStringAttribute(
"name", defaultValue: $"{UnityEngine.Random.Range(0, 1000000)}"));
var assetPath = Path.Combine(_targetAssetDir, name + ".mat");
if (AssetDatabase.LoadMainAssetAtPath(assetPath) != null) {
throw new Exception(
$"Creating a material asset for the geom {name}, but {assetPath} already exists.");
}
AssetDatabase.CreateAsset(material, assetPath);
AssetDatabase.SaveAssets();
material = AssetDatabase.LoadMainAssetAtPath(assetPath) as Material;
} else {
material = DefaultMujocoMaterial;
}
}
renderer.sharedMaterial = material;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 0115d33f9ea9249e78644b1fa5cd7765
timeCreated: 1538581083
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+36
View File
@@ -0,0 +1,36 @@
// 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.Linq;
using UnityEditor.AssetImporters;
using UnityEngine;
namespace Mujoco {
// Importer for STL mesh files.
[ScriptedImporter(version: 1, ext: "stl")]
public class StlMeshImporter : ScriptedImporter {
public override void OnImportAsset(AssetImportContext ctx) {
var assetName = Path.GetFileNameWithoutExtension(ctx.assetPath);
var modelContents = File.ReadAllBytes(ctx.assetPath);
var mesh = StlMeshParser.ParseBinary(modelContents, Vector3.one);
ctx.AddObjectToAsset(assetName, mesh);
ctx.SetMainObject(mesh);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 9060c43f3694e454c873753ea0cc2f49
timeCreated: 1550070047
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+127
View File
@@ -0,0 +1,127 @@
// 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.IO;
using System.Linq;
using UnityEngine;
namespace Mujoco {
public static class BinaryReaderExtensions {
public static Vector3 ReadVector3(this BinaryReader reader) {
var x = reader.ReadSingle();
var y = reader.ReadSingle();
var z = reader.ReadSingle();
return new Vector3(x, y, z);
}
}
public static class BinaryWriterExtensions {
public static void Write(this BinaryWriter writer, Vector3 val) {
writer.Write(val.x);
writer.Write(val.y);
writer.Write(val.z);
}
}
public class StlMeshParser {
private const int _headerLength = 80;
private const int _attributesSizeLength = 2;
private const int _verticesPerTriangle = 3;
private const int _unityLimitNumVerticesPerMesh = 65535;
private const string _asciiFileTypeId = "solid";
private static Vector3 ToXZY(Vector3 v) => new Vector3(v.x, v.z, v.y);
// The binary STL format is described here: https://en.wikipedia.org/wiki/STL_(file_format)
public static Mesh ParseBinary(byte[] stlFileContents, Vector3 scale) {
var fileTypeId = System.Text.Encoding.UTF8.GetString(
stlFileContents.Take(_asciiFileTypeId.Length).ToArray());
if (fileTypeId == _asciiFileTypeId) {
throw new IOException("Ascii STL file format is not supported.");
}
using (var stream = new MemoryStream(stlFileContents)) {
using (var reader = new BinaryReader(stream)) {
reader.ReadBytes(_headerLength);
var numTriangles = reader.ReadUInt32();
var numVertices = numTriangles * _verticesPerTriangle;
if (numVertices > _unityLimitNumVerticesPerMesh) {
throw new IndexOutOfRangeException(
"The mesh exceeds the number of vertices per mesh allowed by Unity. " +
$"({numVertices} > {_unityLimitNumVerticesPerMesh})");
}
var triangleIndices = new List<int>(capacity: (int)numVertices);
var vertices = new List<Vector3>(capacity: (int)numVertices);
var normals = new List<Vector3>(capacity: (int)numVertices);
for (var i = 0; i < numVertices; i += _verticesPerTriangle) {
var triangleNormal = ToXZY(reader.ReadVector3());
normals.AddRange(new[] { triangleNormal, triangleNormal, triangleNormal });
vertices.AddRange(new[] {
ToXZY(reader.ReadVector3()),
ToXZY(reader.ReadVector3()),
ToXZY(reader.ReadVector3()) });
triangleIndices.AddRange(new[] {i, i + 2, i + 1});
reader.ReadInt16(); // Read the unused attribute indices field.
}
var mesh = new Mesh();
mesh.vertices = vertices.ToArray();
mesh.normals = normals.ToArray();
mesh.triangles = triangleIndices.ToArray();
mesh.vertices = mesh.vertices.Select(
vertexPosition => Vector3.Scale(vertexPosition, scale)).ToArray();
mesh.RecalculateNormals();
mesh.RecalculateTangents();
mesh.RecalculateBounds();
return mesh;
}
}
}
public static byte[] SerializeBinary(Mesh mesh) {
using (var stream = new MemoryStream()) {
using (var writer = new BinaryWriter(stream)) {
// Write the header. We only want to write the id and then pad the rest with zeros, up to 80
// bytes.
writer.Write(_asciiFileTypeId);
writer.Write(new byte[_headerLength - _asciiFileTypeId.Length - 1]);
var numTriangles = mesh.triangles.Length / 3;
writer.Write((int)numTriangles);
for (var i = 0; i < mesh.triangles.Length; i += _verticesPerTriangle) {
// STL format uses face normals, while Unity Meshes use vertex normals. We need to convert
// one into another by calculating a mean of vertex normals.
var i1 = mesh.triangles[i];
var i2 = mesh.triangles[i + 1];
var i3 = mesh.triangles[i + 2];
var faceNormal = (mesh.normals[i1] + mesh.normals[i2] + mesh.normals[i3]).normalized;
writer.Write(ToXZY(faceNormal));
writer.Write(ToXZY(mesh.vertices[i1]));
writer.Write(ToXZY(mesh.vertices[i3]));
writer.Write(ToXZY(mesh.vertices[i2]));
writer.Write((short)0);
}
return stream.GetBuffer();
}
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 0d443565163aa4ddea5d910f3517ecfb
timeCreated: 1550063597
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+16
View File
@@ -0,0 +1,16 @@
{
"name": "Mujoco.Editor",
"rootNamespace": "",
"references": [
"Mujoco"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7bdf927c2041ed42ba41bdbbf914db24
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
# Unity Plug-in
The MuJoCo Unity plug-in allows the Unity Editor and runtime to use the MuJoCo
physics engine. Users can import MJCF files and edit the models in the Editor.
The plug-in relies on Unity for most aspects -- assets, game logic, simulation
time -- but uses MuJoCo to determine how objects move, giving the designer
access to MuJoCo's full API. See documentation and installation instructions
[here](https://mujoco.readthedocs.io/en/latest/unity.html).
+7
View File
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: aefae1ccbed1b0597a0ac21f37888aa3
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fa19c3a835699cca3a2b4f2aabbe14b7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 21aeeb2df019248c78e85014384bd21f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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 System.Collections;
using System.Collections.Generic;
namespace Mujoco {
// An implementation of IList<T>, only missing the indexer and the count.
public abstract class FixedSizeIListHelper<T> : IList<T> {
public IEnumerator<T> GetEnumerator() {
for (int i = 0; i < this.Count; ++i) {
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
public void Add(T item) {
throw new NotSupportedException();
}
public void Clear() {
throw new NotSupportedException();
}
public bool Contains(T item) {
for (int i = 0; i < Count; ++i) {
if (this[i].Equals(item)) {
return true;
}
}
return false;
}
public void CopyTo(T[] array, int arrayIndex) {
for (int i = 0; i < Count; ++i) {
array[arrayIndex + i] = this[i];
}
}
public bool Remove(T item) {
throw new NotSupportedException();
}
public bool IsReadOnly => false;
public int IndexOf(T item) {
for (int i = 0; i < Count; ++i) {
if (this[i].Equals(item)) {
return i;
}
}
return -1;
}
public void Insert(int index, T item) {
throw new NotSupportedException();
}
public void RemoveAt(int index) {
throw new NotSupportedException();
}
public abstract int Count { get; }
public abstract T this[int index] {
get;
set;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 9015a95216e0843069042f712722cca6
timeCreated: 1538057012
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,47 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEngine;
namespace Mujoco {
public class MujocoBinaryRetriever {
[InitializeOnLoadMethod]
static void SubscribeToEvent() {
// This causes the method to be invoked after the Editor registers the new list of packages.
Events.registeredPackages += RegisteredPackagesEventHandler;
}
static void RegisteredPackagesEventHandler(
PackageRegistrationEventArgs packageRegistrationEventArgs) {
var mujocoPath = packageRegistrationEventArgs.added[0].assetPath;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/mujoco.dylib") == null) {
File.Copy(
"/Applications/MuJoCo.app/Contents/Frameworks" +
"/MuJoCo.framework/Versions/Current/libmujoco.2.1.1.dylib",
mujocoPath + "/mujoco.dylib");
AssetDatabase.Refresh();
}
} else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) {
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/libmujoco.so") == null) {
File.Copy(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) +
"/.mujoco/mujoco-2.1.1/lib/libmujoco_nogl.so.2.1.1",
mujocoPath + "/libmujoco.so");
AssetDatabase.Refresh();
}
} else {
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/mujoco.dll") == null) {
File.Copy(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) +
"\\MuJoCo\\bin\\mujoco.dll",
mujocoPath + "\\mujoco.dll");
AssetDatabase.Refresh();
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3328869c60ca872129794f55c2d5bd05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6bffff449e27e88308992f7b9ce821e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 49cbe951a7bc505f39a1762fe932f068
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+46
View File
@@ -0,0 +1,46 @@
// 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.Xml;
using UnityEngine;
namespace Mujoco {
// Functionality that needs to be implemented for each shape type.
public interface IMjShape {
// Generate the Mjcf representation of the shape. Infer the scale of the shape from the transform.
void ToMjcf(XmlElement mjcf, Transform transform);
// Parse the shape settings from the specified Mjcf node.
void FromMjcf(XmlElement mjcf);
// Build a parametric mesh (vertices, triangles) of this shape.
Tuple<Vector3[], int[]> BuildMesh();
// Generate a timestamp that can be used to quickly compare if the settings of a shape
// have changed.
// The timestamp should also allow to distinguish between different types of shapes.
//
// Because most of shapes use at most 3 floating point values as parameters, we will
// use the first 3 components of the returned Vector4 to store those values. We will store
// the id of the shape in the 4th component. That id will be derrived from MjShapeComponent.ShapeTypes
// enum.
Vector4 GetChangeStamp();
// Draw a debug gizmo for the shape.
void DebugDraw(Transform transform);
}
}
+13
View File
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: f4b869d65e8404f3aa92042bbd7b9c43
timeCreated: 1547647082
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+360
View File
@@ -0,0 +1,360 @@
// 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.Linq;
using UnityEngine;
namespace Mujoco {
public static class MeshGenerators {
// Creates a sphere mesh geometry.
//
// Args:
// scale: Non-uniform sphere scale. Allows to create ellipsoid shapes.
// numVerticalSlices: How many vertices should there be in a single horizontal slice.
// numHorizontalSlices: How many horizontal slices should the sphere consist of.
public static Tuple<Vector3[], int[]> BuildSphere(
Vector3 scale, int numVerticalSlices = 16, int numHorizontalSlices = 16) {
// Generate the vertices.
Vector3[] vertices;
int[] triangles;
GenerateSphereMeshSlice(
scale: scale,
numVerticalSlices: numVerticalSlices,
numHorizontalSlices: numHorizontalSlices,
firstSliceY: -1.0f,
lastSliceY: 1.0f,
vertices: out vertices,
triangles: out triangles);
return Tuple.Create(vertices, triangles);
}
// Creates a cylinder mesh geometry.
//
// Args:
// radius: Radius of the cylider's body.
// height: Height of the cylinder, from its bottom to its top base.
// numVerticalSlices: How many vertices should there be around the base's circumference.
public static Tuple<Vector3[], int[]> BuildCylinder(
float radius, float height, int numVerticalSlices = 16) {
Vector3[] bodyVertices;
int[] bodyTriangles;
GenerateCylinderBody(radius, height, numVerticalSlices, out bodyVertices, out bodyTriangles);
Vector3[] baseCapVertices;
int[] baseCapTriangles;
GenerateCylinderBaseCaps(
radius, height, numVerticalSlices, out baseCapVertices, out baseCapTriangles);
var merger = new MeshMerger();
merger.Add(bodyVertices, bodyTriangles);
merger.Add(baseCapVertices, baseCapTriangles);
return Tuple.Create(merger.Vertices, merger.Triangles);
}
// Creates a capsule mesh geometry.
//
// Args:
// radius: Radius of the capsule's body.
// height: Height of the capsule, from its bottom to its top base.
// numVerticalSlices: How many vertices should there be in a single horizontal slice.
// numHorizontalSlices: How many vertical slices should the sphere consist of.
public static Tuple<Vector3[], int[]> BuildCapsule(
float radius, float height, int numVerticalSlices = 16, int numHorizontalSlices = 16) {
var baseHalfHeight = Math.Max(0.0f, height * 0.5f - radius);
Vector3[] topCapVertices;
int[] topCapTriangles;
GenerateSphereMeshSlice(
scale: Vector3.one * radius,
numVerticalSlices: numVerticalSlices,
numHorizontalSlices: numHorizontalSlices / 2,
firstSliceY: -1.0f,
lastSliceY: 0.0f,
vertices: out topCapVertices,
triangles: out topCapTriangles);
Vector3[] bottomCapVertices;
int[] bottomCapTriangles;
GenerateSphereMeshSlice(
scale: Vector3.one * radius,
numVerticalSlices: numVerticalSlices,
numHorizontalSlices: numHorizontalSlices / 2,
firstSliceY: 0.0f,
lastSliceY: 1.0f,
vertices: out bottomCapVertices,
triangles: out bottomCapTriangles);
Vector3[] bodyVertices;
int[] bodyTriangles;
GenerateCylinderBody(
radius: radius,
height: baseHalfHeight * 2.0f,
numVerticalSlices: numVerticalSlices,
vertices: out bodyVertices,
triangles: out bodyTriangles);
var merger = new MeshMerger();
merger.AddAndTranslate(topCapVertices, topCapTriangles, Vector3.up * baseHalfHeight * -1.0f);
merger.AddAndTranslate(
bottomCapVertices, bottomCapTriangles, Vector3.up * baseHalfHeight * 1.0f);
merger.Add(bodyVertices, bodyTriangles);
return Tuple.Create(merger.Vertices, merger.Triangles);
}
// Creates a box mesh geometry.
//
// Args:
// extents: Extents of the box, along each major axis.
public static Tuple<Vector3[], int[]> BuildBox(Vector3 extents) {
// In order to ensure the box renders with flat faces, we need to make sure the triangles
// do not share vertices. We'll accomplish that by assigning a unique vertex to every triangle
// apex.
// We define a set of 8 vertex positions that form the box, and then sample from that set using
// triangle indices.
var vertexPositions = new Vector3[] {
Vector3.Scale(new Vector3(-1, -1, -1), extents),
Vector3.Scale(new Vector3(1, -1, -1), extents),
Vector3.Scale(new Vector3(-1, -1, 1), extents),
Vector3.Scale(new Vector3(1, -1, 1), extents),
Vector3.Scale(new Vector3(-1, 1, -1), extents),
Vector3.Scale(new Vector3(1, 1, -1), extents),
Vector3.Scale(new Vector3(-1, 1, 1), extents),
Vector3.Scale(new Vector3(1, 1, 1), extents),
};
var vertexSamplingPattern = new int[] {
0, 1, 3, 0, 3, 2,
4, 7, 5, 4, 6, 7,
0, 5, 1, 0, 4, 5,
1, 7, 3, 1, 5, 7,
3, 6, 2, 3, 7, 6,
2, 4, 0, 2, 6, 4,
};
var vertices = vertexSamplingPattern.Select(index => vertexPositions[index]).ToArray();
var triangles = Enumerable.Range(0, vertexSamplingPattern.Length).ToArray();
return Tuple.Create(vertices, triangles);
}
// Creates a plane mesh geometry.
//
// Args:
// width: Width of the plane, along the OX axis.
// height: Height of the plane, along the OZ axis.
public static Tuple<Vector3[], int[]> BuildPlane(float width, float height) {
var vertices = new Vector3[] {
new Vector3(-0.5f * width, 0, -0.5f * height),
new Vector3(0.5f * width, 0, -0.5f * height),
new Vector3(-0.5f * width, 0, 0.5f * height),
new Vector3(0.5f * width, 0, 0.5f * height),
};
var triangles = new int[] {
0, 3, 1,
0, 2, 3,
};
return Tuple.Create(vertices, triangles);
}
// Generates a slice of a sphere mesh.
// Conceptually, the algorithm generates a sphere with a unit radius (spanning from -1 to 1 along
// each of the major axes). Then, it cuts it using 2 planes parallel to the XZ plane, located at
// distances defined by 'firstSliceY' and 'lastSliceY' parameters respectively. It then returns
// the section of the mesh contained between those planes.
//
// This allows to use the method to generate variants of the sphere - full sphere, hemispheres.
//
// Args:
// scale: Non-uniform sphere scale. Allows to create ellipsoid shapes.
// numVerticalSlices: How many vertices should there be in a single horizontal slice.
// numHorizontalSlices: How many horizontal slices should the sphere consist of.
// firstSliceY: Vertical position of the first slice. Must be a value in range <-1, 1>.
// lastSliceY: Vertical position of the last slice. Must a value in range <-1, 1>.
// vertices: (Out) Array of sphere vertex positions.
// triangles: (Out) Array with the sphere triangle connectivity.
private static void GenerateSphereMeshSlice(
Vector3 scale, int numVerticalSlices, int numHorizontalSlices, float firstSliceY,
float lastSliceY, out Vector3[] vertices, out int[] triangles) {
numVerticalSlices = Math.Max(3, numVerticalSlices);
numHorizontalSlices = Math.Max(3, numHorizontalSlices);
triangles = new int[(numHorizontalSlices - 1) * numVerticalSlices * 6];
vertices = new Vector3[numVerticalSlices * numHorizontalSlices];
if (firstSliceY < -1.0f || firstSliceY > 1.0f) {
throw new IOException("firstSliceY should be a value in range <-1, 1>");
}
if (lastSliceY < -1.0f || lastSliceY > 1.0f) {
throw new IOException("lastSliceY should be a value in range <-1, 1>");
}
if (firstSliceY > lastSliceY) {
throw new IOException("Value of firstSliceY should be lower than the value of lastSliceY.");
}
// Generate the vertices.
var deltaY = (lastSliceY - firstSliceY) / (numHorizontalSlices - 1);
var deltaYaw = 360.0f / numVerticalSlices;
for (var slice = 0; slice < numHorizontalSlices; ++slice) {
var y = firstSliceY + deltaY * slice;
var radius = (float)Math.Sqrt(1 - Math.Min(1.0f, y * y));
for (var vertex = 0; vertex < numVerticalSlices; ++vertex) {
var position = Quaternion.AngleAxis(deltaYaw * vertex, Vector3.up) * Vector3.right * radius;
position.y = y;
vertices[slice * numVerticalSlices + vertex] = Vector3.Scale(position, scale);
}
}
// Build the triangles.
for (var slice = 0; slice < (numHorizontalSlices - 1); ++slice) {
var firstVertexInSlice = slice * numVerticalSlices;
for (var vertex = 0; vertex < numVerticalSlices; ++vertex) {
var index1 = firstVertexInSlice + vertex;
var index2 = (vertex + 1 == numVerticalSlices) ? firstVertexInSlice : index1 + 1;
var index3 = index1 + numVerticalSlices;
var index4 = index2 + numVerticalSlices;
var quadBaseAddress = (slice * numVerticalSlices + vertex) * 6;
triangles[quadBaseAddress] = index1;
triangles[quadBaseAddress + 1] = index2;
triangles[quadBaseAddress + 2] = index4;
triangles[quadBaseAddress + 3] = index1;
triangles[quadBaseAddress + 4] = index4;
triangles[quadBaseAddress + 5] = index3;
}
}
}
// Generates the cylinder mesh vertices.
// This functionality will be shared between the cylinder body and cylinder base caps generators.
//
// Args:
// radius: Cylinder radius.
// height: Cylinder height.
// numVerticalSlices: How many vertices should there be around the base's circumference.
// vertices: (Out) Array of sphere vertex positions.
private static void GenerateCylinderVertices(
float radius, float height, int numVerticalSlices, out Vector3[] vertices) {
vertices = new Vector3[numVerticalSlices * 2];
var dYaw = 360.0f / numVerticalSlices;
var yaw = 0.0f;
var vertexIndex = 0;
for (var y = 0; y <= 1; ++y) {
var yPos = (y - 0.5f) * height;
for (var i = 0; i < numVerticalSlices; ++i, yaw += dYaw) {
var vertexPosition = Quaternion.AngleAxis(yaw, Vector3.up) * Vector3.right * radius;
vertexPosition.y = yPos;
vertices[vertexIndex++] = vertexPosition;
}
}
}
// Generates the meshes for the cylinder body, excluding its bases.
//
// Args:
// radius: Cylinder radius.
// height: Cylinder height.
// numVerticalSlices: How many vertices should there be around the base's circumference.
// vertices: (Out) Array of sphere vertex positions.
// triangles: (Out) Array with the sphere triangle connectivity.
private static void GenerateCylinderBody(
float radius, float height, int numVerticalSlices, out Vector3[] vertices,
out int[] triangles) {
GenerateCylinderVertices(radius, height, numVerticalSlices, out vertices);
triangles = new int[numVerticalSlices * 6];
var apexIndex = 0;
for (var i = 0; i < numVerticalSlices; ++i) {
var v1 = i;
var v2 = (i + 1 == numVerticalSlices) ? 0 : v1 + 1;
var v3 = v1 + numVerticalSlices;
var v4 = v2 + numVerticalSlices;
triangles[apexIndex++] = v1;
triangles[apexIndex++] = v2;
triangles[apexIndex++] = v4;
triangles[apexIndex++] = v1;
triangles[apexIndex++] = v4;
triangles[apexIndex++] = v3;
}
}
// Generates the meshes for the cylinder base circles.
//
// Args:
// radius: Cylinder radius.
// height: Cylinder height.
// numVerticalSlices: How many vertices should there be around the base's circumference.
// vertices: (Out) Array of sphere vertex positions.
// triangles: (Out) Array with the sphere triangle connectivity.
private static void GenerateCylinderBaseCaps(
float radius, float height, int numVerticalSlices, out Vector3[] vertices,
out int[] triangles) {
GenerateCylinderVertices(radius, height, numVerticalSlices, out vertices);
triangles = new int[(numVerticalSlices - 1) * 6];
var apexIndex = 0;
for (var baseIdx = 0; baseIdx <= 1; ++baseIdx) {
var v1 = baseIdx * numVerticalSlices;
for (var i = 1; i < numVerticalSlices; ++i) {
var v2 = v1 + (i % numVerticalSlices);
var v3 = v1 + ((i + 1) % numVerticalSlices);
triangles[apexIndex++] = v1;
triangles[apexIndex++] = baseIdx == 0 ? v3 : v2;
triangles[apexIndex++] = baseIdx == 0 ? v2 : v3;
}
}
}
}
// A tool used to merge the geometry of separate meshes.
public class MeshMerger {
private List<Vector3> _vertices = new List<Vector3>();
private List<int> _triangles = new List<int>();
// The vertices of the merged mesh.
public Vector3[] Vertices => _vertices.ToArray();
// The connectivity array of the merged mesh.
public int[] Triangles => _triangles.ToArray();
// Adds a new submesh.
//
// Args:
// vertices: Array of submesh vertices.
// vertices: Triangles connectivity array of the submesh.
public void Add(Vector3[] vertices, int[] triangles) {
var triangleOffset = _triangles.Count;
var vertexOffset = _vertices.Count;
_vertices.AddRange(vertices);
_triangles.AddRange(triangles);
for (var i = triangleOffset; i < _triangles.Count; ++i) {
_triangles[i] += vertexOffset;
}
}
// Adds a new submesh, translating its vertices by a specified amount.
//
// Args:
// vertices: Array of submesh vertices.
// vertices: Triangles connectivity array of the submesh.
// translation: Additional translation to be applied to the submesh.
public void AddAndTranslate(Vector3[] vertices, int[] triangles, Vector3 translation) {
var triangleOffset = _triangles.Count;
var vertexOffset = _vertices.Count;
_vertices.AddRange(vertices);
for (var i = vertexOffset; i < _vertices.Count; ++i) {
_vertices[i] += translation;
}
_triangles.AddRange(triangles);
for (var i = triangleOffset; i < _triangles.Count; ++i) {
_triangles[i] += vertexOffset;
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: deb4d8c01485d4e44bc815b133e707c0
timeCreated: 1539115944
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+394
View File
@@ -0,0 +1,394 @@
// 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 {
// Actuators provide means to set joints in motion.
public class MjActuator : MjComponent {
public enum ActuatorType {
General,
Motor,
Position,
Velocity,
Cylinder,
Muscle
}
// This structure holds the parameters shared by all types of actuators.
//
// All constants found in this class are copied from the official documentation, and can be found
// here: http://mujoco.org/book/XMLreference.html#actuator
[Serializable]
public class CommonParameters {
// If true, the control input to this actuator is automatically clamped to ctrlrange at runtime.
// If false, control input clamping is disabled.
public bool CtrlLimited;
// If true, the force output of this actuator is automatically clamped to forcerange at runtime.
// If false, force output clamping is disabled.
public bool ForceLimited;
// Range for clamping the control input.
public Vector2 CtrlRange;
// Range for clamping the force output.
public Vector2 ForceRange;
// Range of feasible lengths of the actuator's transmission.
public Vector2 LengthRange;
// This attribute scales the length (and consequently moment arms, velocity and force) of the
// actuator, for all transmission types. It is different from the gain in the force generation
// mechanism, because the gain only scales the force output and does not affect the length,
// moment arms and velocity.
public List<float> Gear = new List<float>() { 1.0f };
public void ToMjcf(XmlElement mjcf) {
mjcf.SetAttribute("ctrllimited", $"{CtrlLimited}".ToLowerInvariant());
mjcf.SetAttribute("forcelimited", $"{ForceLimited}".ToLowerInvariant());
mjcf.SetAttribute(
"ctrlrange",
$"{MjEngineTool.GetSorted(CtrlRange).x} {MjEngineTool.GetSorted(CtrlRange).y}");
mjcf.SetAttribute(
"forcerange",
$"{MjEngineTool.GetSorted(ForceRange).x} {MjEngineTool.GetSorted(ForceRange).y}");
mjcf.SetAttribute(
"lengthrange",
$"{MjEngineTool.GetSorted(LengthRange).x} {MjEngineTool.GetSorted(LengthRange).y}");
mjcf.SetAttribute("gear", MjEngineTool.ListToMjcf(Gear));
}
public void FromMjcf(XmlElement mjcf) {
CtrlLimited = mjcf.GetBoolAttribute("ctrllimited", defaultValue: false);
ForceLimited = mjcf.GetBoolAttribute("forcelimited", defaultValue: false);
CtrlRange = mjcf.GetVector2Attribute("ctrlrange", defaultValue: Vector2.zero);
ForceRange = mjcf.GetVector2Attribute("forcerange", defaultValue: Vector2.zero);
LengthRange = mjcf.GetVector2Attribute("lengthrange", defaultValue: Vector2.zero);
Gear = mjcf.GetFloatArrayAttribute("gear", defaultValue: new float[] { 1.0f }).ToList();
}
}
// This structure holds all parameters unique to each type of the actuator.
//
// Because UnityEditor doesn't handle polymorphism well, I decided to put all parameters here
// and then create a custom editor (MjActuatorEditor), that will display only the values
// relevant to the selected articulation type. The choice will be made based on the value of
// the 'Type' field.
//
// All constants found in this class are copied from the official documentation, and can be found
// here: http://mujoco.org/book/XMLreference.html#actuator
[Serializable]
public class CustomParameters {
//// General actuator parameters.
// Activation dynamics type for the actuator.
public MujocoLib.mjtDyn DynType;
// The gain and bias together determine the output of the force generation mechanism, which is
// currently assumed to be affine. As already explained in Actuation model, the general formula
// is:
// scalar_force = gain_term * (act or ctrl) + bias_term.
// The formula uses the activation state when present, and the control otherwise.
public MujocoLib.mjtGain GainType;
// Bias type.
public MujocoLib.mjtBias BiasType;
// Activation dynamics parameters. The built-in activation types (except for muscle) use only
// the first parameter, but we provide additional parameters in case user callbacks implement a
// more elaborate model. The length of this array is not enforced by the parser, so the user can
// enter as many parameters as needed.
public List<float> DynPrm = new List<float>() { 1.0f, 0.0f, 0.0f };
// Gain parameters. The built-in gain types (except for muscle) use only the first parameter,
// but we provide additional parameters in case user callbacks implement a more elaborate model.
// The length of this array is not enforced by the parser, so the user can enter as many
// parameters as needed.
public List<float> GainPrm = new List<float>() { 1.0f, 0.0f, 0.0f };
// Bias parameters. The affine bias type uses three parameters. The length of this array is not
// enforced by the parser, so the user can enter as many parameters as needed.
public List<float> BiasPrm = new List<float>() { 0.0f, 0.0f, 0.0f };
public void GeneralToMjcf(XmlElement mjcf) {
mjcf.SetAttribute("dyntype", $"{DynType}".Substring(6).ToLowerInvariant());
mjcf.SetAttribute("gaintype", $"{GainType}".Substring(7).ToLowerInvariant());
mjcf.SetAttribute("biastype", $"{BiasType}".Substring(7).ToLowerInvariant());
mjcf.SetAttribute("dynprm", MjEngineTool.ListToMjcf(DynPrm));
mjcf.SetAttribute("gainprm", MjEngineTool.ListToMjcf(GainPrm));
mjcf.SetAttribute("biasprm", MjEngineTool.ListToMjcf(BiasPrm));
}
public void GeneralFromMjcf(XmlElement mjcf) {
var dynTypeStr = mjcf.GetStringAttribute("dyntype", defaultValue: "none");
var gainTypeStr = mjcf.GetStringAttribute("gaintype", defaultValue: "fixed");
var biasTypeStr = mjcf.GetStringAttribute("biastype", defaultValue: "none");
var ignoreCase = true;
Enum.TryParse<MujocoLib.mjtDyn>($"mjdyn_{dynTypeStr}", ignoreCase, out DynType);
Enum.TryParse<MujocoLib.mjtGain>($"mjgain_{gainTypeStr}", ignoreCase, out GainType);
Enum.TryParse<MujocoLib.mjtBias>($"mjbias_{biasTypeStr}", ignoreCase, out BiasType);
DynPrm = mjcf.GetFloatArrayAttribute(
"dynprm", defaultValue: new float[] { 1.0f, 0.0f, 0.0f }).ToList();
GainPrm = mjcf.GetFloatArrayAttribute(
"gainprm", defaultValue: new float[] { 1.0f, 0.0f, 0.0f }).ToList();
BiasPrm = mjcf.GetFloatArrayAttribute(
"biasprm", defaultValue: new float[] { 0.0f, 0.0f, 0.0f }).ToList();
}
//// Position actuator parameters.
// Position feedback gain.
[AbsoluteValue]
public float Kp = 1.0f;
public void PositionToMjcf(XmlElement mjcf) {
mjcf.SetAttribute("kp", $"{Math.Abs(Kp)}");
}
public void PositionFromMjcf(XmlElement mjcf) {
Kp = mjcf.GetFloatAttribute("kp", defaultValue: 1.0f);
}
//// Velocity actuator parameters.
// Velocity feedback gain.
[AbsoluteValue]
public float Kv = 1.0f;
public void VelocityToMjcf(XmlElement mjcf) {
mjcf.SetAttribute("kv", $"{Math.Abs(Kv)}");
}
public void VelocityFromMjcf(XmlElement mjcf) {
Kv = mjcf.GetFloatAttribute("kv", defaultValue: 1.0f);
}
//// Cylinder actuator parameters.
// Time constant of the activation dynamics.
public float CylinderTimeConst = 1.0f;
// Area of the cylinder. This is used internally as actuator gain.
[AbsoluteValue]
public float Area = 1.0f;
// Instead of area the user can specify diameter. If both are specified, diameter has
// precedence.
[AbsoluteValue]
public float Diameter = 0.0f;
// Bias parameters, copied internally into biasprm.
public float[] Bias = new float[] { 0.0f, 0.0f, 0.0f };
public void CylinderToMjcf(XmlElement mjcf) {
mjcf.SetAttribute("timeconst", $"{CylinderTimeConst}");
mjcf.SetAttribute("area", $"{Math.Abs(Area)}");
mjcf.SetAttribute("diameter", $"{Math.Abs(Diameter)}");
mjcf.SetAttribute("bias", MjEngineTool.ArrayToMjcf(Bias));
}
public void CylinderFromMjcf(XmlElement mjcf) {
CylinderTimeConst = mjcf.GetFloatAttribute("timeconst", defaultValue: 1.0f);
Area = mjcf.GetFloatAttribute("area", defaultValue: 1.0f);
Diameter = mjcf.GetFloatAttribute("diameter", defaultValue: 0.0f);
Bias = mjcf.GetFloatArrayAttribute("bias", defaultValue: new float[] { 0.0f, 0.0f, 0.0f });
}
//// Muscle actuator parameters.
// Time constants for activation and de-activation dynamics.
public Vector2 MuscleTimeConst = new Vector2(0.01f, 0.04f);
// Operating length range of the muscle, in units of L0.
public Vector2 Range = new Vector2(0.75f, 1.05f);
// Peak active force at rest. If this value is negative, the peak force is determined
// automatically using the scale attribute below.
public float Force = -1.0f;
// If the force attribute is negative, the peak active force for the muscle is set to this value
// divided by mjModel.actuator_acc0. The latter is the norm of the joint-space acceleration
// vector caused by unit force on the actuator's transmission in qpos0. In other words, scaling
// produces higher peak forces for muscles that pull more weight.
public float Scale = 200.0f;
// Lower position range of the normalized FLV curve, in units of L0.
public float LMin = 0.5f;
// Upper position range of the normalized FLV curve, in units of L0.
public float LMax = 1.6f;
// Shortening velocity at which muscle force drops to zero, in units of L0 per second.
public float VMax = 1.5f;
// Passive force generated at lmax, relative to the peak rest force.
public float FpMax = 1.3f;
// Active force generated at saturating lengthening velocity, relative to the peak rest force.
public float FvMax = 1.2f;
public void MuscleToMjcf(XmlElement mjcf) {
mjcf.SetAttribute("timeconst", $"{MuscleTimeConst[0]} {MuscleTimeConst[1]}");
mjcf.SetAttribute(
"range", $"{MjEngineTool.GetSorted(Range).x} {MjEngineTool.GetSorted(Range).y}");
mjcf.SetAttribute("force", $"{Force}");
mjcf.SetAttribute("scale", $"{Scale}");
mjcf.SetAttribute("lmin", $"{LMin}");
mjcf.SetAttribute("lmax", $"{LMax}");
mjcf.SetAttribute("vmax", $"{VMax}");
mjcf.SetAttribute("fpmax", $"{FpMax}");
mjcf.SetAttribute("fvmax", $"{FvMax}");
}
public void MuscleFromMjcf(XmlElement mjcf) {
MuscleTimeConst = mjcf.GetVector2Attribute(
"timeconst", defaultValue: new Vector2(0.01f, 0.04f));
Range = mjcf.GetVector2Attribute("range", defaultValue: new Vector2(0.75f, 1.05f));
Force = mjcf.GetFloatAttribute("force", defaultValue: -1.0f);
Scale = mjcf.GetFloatAttribute("scale", defaultValue: 200.0f);
LMin = mjcf.GetFloatAttribute("lmin", defaultValue: 0.5f);
LMax = mjcf.GetFloatAttribute("lmax", defaultValue: 1.6f);
VMax = mjcf.GetFloatAttribute("vmax", defaultValue: 1.5f);
FpMax = mjcf.GetFloatAttribute("fpmax", defaultValue: 1.3f);
FvMax = mjcf.GetFloatAttribute("fvmax", defaultValue: 1.2f);
}
}
public ActuatorType Type;
[Tooltip("Joint actuation target. Mutually exclusive with tendon target.")]
public MjBaseJoint Joint;
[Tooltip("Tendon actuation target. Mutually exclusive with joint target.")]
public MjBaseTendon Tendon;
[Tooltip("Parameters specific to each actuator type.")]
[HideInInspector]
public CustomParameters CustomParams = new CustomParameters();
[Tooltip("Parameters shared by all types of actuators.")]
public CommonParameters CommonParams = new CommonParameters();
[Tooltip("Actuator control.")]
public float Control;
// Actuator length.
public float Length { get; private set; }
// Actuator velocity.
public float Velocity { get; private set; }
// Actuator force.
public float Force { get; private set; }
public override MujocoLib.mjtObj ObjectType => MujocoLib.mjtObj.mjOBJ_ACTUATOR;
// Parse the component settings from an external Mjcf.
protected override void OnParseMjcf(XmlElement mjcf) {
if (!Enum.TryParse(mjcf.Name, ignoreCase: true, result: out Type)) {
throw new ArgumentException($"Unknown actuator type {mjcf.Name}.");
}
CommonParams.FromMjcf(mjcf);
switch (Type) {
case MjActuator.ActuatorType.General: {
CustomParams.GeneralFromMjcf(mjcf);
break;
}
case MjActuator.ActuatorType.Position: {
CustomParams.PositionFromMjcf(mjcf);
break;
}
case MjActuator.ActuatorType.Velocity: {
CustomParams.VelocityFromMjcf(mjcf);
break;
}
case MjActuator.ActuatorType.Cylinder: {
CustomParams.CylinderFromMjcf(mjcf);
break;
}
case MjActuator.ActuatorType.Muscle: {
CustomParams.MuscleFromMjcf(mjcf);
break;
}
}
Joint = mjcf.GetObjectReferenceAttribute<MjBaseJoint>("joint");
Tendon = mjcf.GetObjectReferenceAttribute<MjBaseTendon>("tendon");
}
// Generate implementation specific XML element.
protected override XmlElement OnGenerateMjcf(XmlDocument doc) {
if (Joint == null && Tendon == null) {
throw new InvalidOperationException($"Actuator {name} is not assigned a joint nor tendon.");
}
if (Joint != null && Tendon != null) {
throw new InvalidOperationException(
$"Actuator {name} can't have both a tendon and a joint target.");
}
var mjcf = doc.CreateElement(Type.ToString().ToLowerInvariant());
if (Joint != null) {
mjcf.SetAttribute("joint", Joint.MujocoName);
} else {
mjcf.SetAttribute("tendon", Tendon.MujocoName);
}
CommonParams.ToMjcf(mjcf);
switch (Type) {
case MjActuator.ActuatorType.General: {
CustomParams.GeneralToMjcf(mjcf);
break;
}
case MjActuator.ActuatorType.Position: {
CustomParams.PositionToMjcf(mjcf);
break;
}
case MjActuator.ActuatorType.Velocity: {
CustomParams.VelocityToMjcf(mjcf);
break;
}
case MjActuator.ActuatorType.Cylinder: {
CustomParams.CylinderToMjcf(mjcf);
break;
}
case MjActuator.ActuatorType.Muscle: {
CustomParams.MuscleToMjcf(mjcf);
break;
}
}
return mjcf;
}
// Synchronize the state of the component.
public override unsafe void OnSyncState(MujocoLib.mjData_* data) {
data->ctrl[MujocoId] = Control;
Length = (float)data->actuator_length[MujocoId];
Velocity = (float)data->actuator_velocity[MujocoId];
Force = (float)data->actuator_force[MujocoId];
}
public void OnValidate() {
if (Joint != null && Tendon != null) {
Debug.LogError(
$"Actuator {name} can't have both a tendon and a joint target.", this);
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: c3f3a0cb6a6d64327b27c7d5bdd8d430
timeCreated: 1546881319
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,54 @@
// 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 {
public class MjActuatorScalarSensor : MjBaseSensor {
// NOTE: These names must match sensor names listed on mujoco.org.
// Changing them is justified only when Mujoco is upgraded to a new version.
public enum AvailableSensors {
ActuatorPos,
ActuatorVel,
ActuatorFrc,
}
public AvailableSensors SensorType;
public MjActuator Actuator;
public double SensorReading { get; private set; }
protected override XmlElement ToMjcf(XmlDocument doc) {
if (Actuator == null) {
throw new NullReferenceException("Missing a reference to a MjActuator.");
}
var mjcf = doc.CreateElement(SensorType.ToString().ToLower());
mjcf.SetAttribute("actuator", Actuator.MujocoName);
return mjcf;
}
protected override void FromMjcf(XmlElement mjcf) {
if (!Enum.TryParse(mjcf.Name, ignoreCase: true, result: out SensorType)) {
throw new ArgumentException($"Unknown sensor type {mjcf.Name}.");
}
Actuator = mjcf.GetObjectReferenceAttribute<MjActuator>("actuator");
}
public override unsafe void OnSyncState(MujocoLib.mjData_* data) {
SensorReading = data->sensordata[_sensorAddress];
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 9d6f957ac545d4e8ea581fcc861a0498
timeCreated: 1548092931
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+52
View File
@@ -0,0 +1,52 @@
// 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.Xml;
using UnityEngine;
namespace Mujoco {
// The component represents a joint with 1 degree of translational freedom.
public class MjBallJoint : MjBaseJoint {
[Tooltip("In radians.")]
public float RangeUpper;
[Tooltip("Joint settings.")]
public MjJointSettings Settings = MjJointSettings.Default;
protected override void OnParseMjcf(XmlElement mjcf) {
// Transform.
transform.localPosition =
MjEngineTool.UnityVector3(mjcf.GetVector3Attribute("pos", defaultValue: Vector3.zero));
Settings.FromMjcf(mjcf);
var rangeValues = mjcf.GetFloatArrayAttribute("range", defaultValue: new float[] { 0, 0 });
// rangeValues[0] is always 0 for ball joints.
RangeUpper = rangeValues[1];
}
protected override XmlElement OnGenerateMjcf(XmlDocument doc) {
var mjcf = (XmlElement)doc.CreateElement("joint");
mjcf.SetAttribute("type", "ball");
MjEngineTool.PositionToMjcf(mjcf, this);
Settings.ToMjcf(mjcf);
mjcf.SetAttribute("range", $"0 {RangeUpper}");
return mjcf;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: bc71be844486c4586ba729c1ab4d914a
timeCreated: 1552056297
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+24
View File
@@ -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 System.Xml;
using UnityEngine;
namespace Mujoco {
public abstract class MjBaseBody : MjComponent {
public override MujocoLib.mjtObj ObjectType => MujocoLib.mjtObj.mjOBJ_BODY;
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 088eb1d485bd54fcaa1061c79b289dde
timeCreated: 1551357866
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,47 @@
// 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 {
public abstract class MjBaseConstraint : MjComponent {
public override MujocoLib.mjtObj ObjectType => MujocoLib.mjtObj.mjOBJ_EQUALITY;
protected abstract string _constraintName { get; }
public SolverImpedance SolverImpedanceSettings = SolverImpedance.Default;
public SolverReference SolverReferenceSettings = SolverReference.Default;
protected abstract void ToMjcf(XmlElement mjcf);
protected abstract void FromMjcf(XmlElement mjcf);
protected override void OnParseMjcf(XmlElement mjcf) {
FromMjcf(mjcf);
SolverReferenceSettings.FromMjcf(mjcf, "solref");
SolverImpedanceSettings.FromMjcf(mjcf, "solimp");
}
protected override XmlElement OnGenerateMjcf(XmlDocument doc) {
var mjcf = (XmlElement)doc.CreateElement(_constraintName);
ToMjcf(mjcf);
SolverReferenceSettings.ToMjcf(mjcf, "solref");
SolverImpedanceSettings.ToMjcf(mjcf, "solimp");
return mjcf;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: a34f7c43f90c84e64a45942e605c0a2b
timeCreated: 1552309644
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+63
View File
@@ -0,0 +1,63 @@
// 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.Xml;
using UnityEngine;
namespace Mujoco {
public abstract class MjBaseJoint : MjComponent {
public override MujocoLib.mjtObj ObjectType => MujocoLib.mjtObj.mjOBJ_JOINT;
public int QposAddress { get; private set; } = -1;
public int DofAddress { get; private set; } = -1;
// Joint will add Degrees of Freedom to this MjBaseBody.
// It's the MjBaseBody located immediately above this joint in the scene tree.
// NOTE: This property won't be initialized until runtime.
public MjBaseBody ParentBody { get; private set; }
// Joint will constrain the ParentBody to this MjBaseBody.
// It's the MjBaseBody located two levels above this joint in the scene tree.
// For some joints, such as MjFreeJoint, this can be null.
// NOTE: This property won't be initialized until runtime.
public MjBaseBody GrandParentBody { get; private set; }
// Return the bodies connected by this joint.
public void GetConnectedBodies(out MjBaseBody grandParent, out MjBaseBody parent) {
parent = MjHierarchyTool.FindParentComponent<MjBaseBody>(this);
if (parent != null) {
grandParent = MjHierarchyTool.FindParentComponent<MjBaseBody>(parent);
} else {
grandParent = null;
}
}
protected override unsafe void OnBindToRuntime(MujocoLib.mjModel_* model, MujocoLib.mjData_* data) {
QposAddress = model->jnt_qposadr[MujocoId];
DofAddress = model->jnt_dofadr[MujocoId];
}
protected override void Start() {
base.Start();
MjBaseBody grandparentBody, parentBody;
GetConnectedBodies(out grandparentBody, out parentBody);
GrandParentBody = grandparentBody;
ParentBody = parentBody;
if (ParentBody == null) {
throw new Exception("The joint doesn't have a ParentBody.");
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 07bd997db28964e5daf6af0ff47e157f
timeCreated: 1538399740
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+80
View File
@@ -0,0 +1,80 @@
// 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 {
// The base class for Mujoco sensors.
//
// Mujoco offers a very wide range of sensors, each contingent on the following three aspects:
// 1. the Mujoco Component it observes: Body, Joint, etc.
// 2. the type of data it returns: scalar, Vector3, quaternion, etc.
// 3. the field it reads to retreive the data: xpos, xquat, cfrc, etc.
//
// In Mujoco proper, names of some of the sensors repeat for many data types. In order to get rid
// of this ambiguity, we chose to split the sensors into groups.
// Each sensor class represents a group of aspects (1) and (2), which is reflected in its name.
// For example, the class of sensors that listen to Bodies and produce Vector3 observations is
// called MjBodyVectorSensor.
//
// That very class contains a wide range of sensing capabilities - as in fact most of the classes
// do. What each sensor can sense can be controlled by the sensor's Type field. Using the
// MjBodyVectorSensor as an example, one can set it to SubtreeCom, FramePos or any of the other
// supported observation types.
public abstract class MjBaseSensor : MjComponent {
[Tooltip("The standard deviation of zero-mean Gaussian noise added to the sensor output.")]
[AbsoluteValue]
public float Noise = 0.0f;
[Tooltip("When this value is positive, it limits the absolute value of the sensor output.")]
[AbsoluteValue]
public float Cutoff = 0.0f;
public override MujocoLib.mjtObj ObjectType => MujocoLib.mjtObj.mjOBJ_SENSOR;
// Address of the sensor, that can be used to index into MujocoLib.mjData_.sensordata.
protected int _sensorAddress;
// Parse the component settings from an external Mjcf.
protected override void OnParseMjcf(XmlElement mjcf) {
Noise = mjcf.GetFloatAttribute("noise", defaultValue: 0.0f);
Cutoff = mjcf.GetFloatAttribute("cutoff", defaultValue: 0.0f);
FromMjcf(mjcf);
}
// Generate implementation specific XML element.
protected override XmlElement OnGenerateMjcf(XmlDocument doc) {
var mjcf = ToMjcf(doc);
mjcf.SetAttribute("noise", Noise.ToString());
mjcf.SetAttribute("cutoff", Cutoff.ToString());
return mjcf;
}
// Perform bind time initialization of the component.
protected override unsafe void OnBindToRuntime(MujocoLib.mjModel_* model, MujocoLib.mjData_* data) {
_sensorAddress = model->sensor_adr[MujocoId];
}
// Create the implementation dependent Mjcf node.
protected abstract XmlElement ToMjcf(XmlDocument doc);
// Parse the implementation dependent details from the provided Mjcf node.
protected abstract void FromMjcf(XmlElement mjcf);
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 9535e8d4bdb484c4dbd07806fcae7568
timeCreated: 1548084206
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+65
View File
@@ -0,0 +1,65 @@
// 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.Xml;
using UnityEngine;
namespace Mujoco {
public abstract class MjBaseTendon : MjComponent {
public override MujocoLib.mjtObj ObjectType => MujocoLib.mjtObj.mjOBJ_TENDON;
public SolverSettings Solver = SolverSettings.Default;
[Tooltip("Length at zero spring force. If negative, this resting length is computed at qpos0.")]
public float SpringLength = -1.0f;
public float Stiffness = 0.0f;
public float Damping = 0.0f;
// Tendon length.
public float Length { get; private set; }
// Create the implementation dependent Mjcf node.
protected abstract XmlElement ToMjcf(XmlDocument doc);
// Parse the implementation dependent details from the provided Mjcf node.
protected abstract void FromMjcf(XmlElement mjcf);
// Parse the component settings from an external Mjcf.
protected override void OnParseMjcf(XmlElement mjcf) {
Solver.FromMjcf(mjcf);
SpringLength = mjcf.GetFloatAttribute("springlength", defaultValue: -1.0f);
Stiffness = mjcf.GetFloatAttribute("damping");
Damping = mjcf.GetFloatAttribute("stiffness");
FromMjcf(mjcf);
}
// Generate implementation specific XML element.
protected override XmlElement OnGenerateMjcf(XmlDocument doc) {
var mjcf = ToMjcf(doc);
Solver.ToMjcf(mjcf);
mjcf.SetAttribute("springlength", $"{SpringLength}");
mjcf.SetAttribute("damping", $"{Damping}");
mjcf.SetAttribute("stiffness", $"{Stiffness}");
return mjcf;
}
// Synchronize the state of the component.
public override unsafe void OnSyncState(MujocoLib.mjData_* data) {
Length = (float)data->ten_length[MujocoId];
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 2d585fcecff4e4b13b8647ad4d18c5b5
timeCreated: 1551348729
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+42
View File
@@ -0,0 +1,42 @@
// 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.Xml;
using UnityEngine;
namespace Mujoco {
// The component represents the apex of hierarchy that defines a single rigid body.
public class MjBody : MjBaseBody {
protected override void OnParseMjcf(XmlElement mjcf) {
// Transform
transform.localPosition =
MjEngineTool.UnityVector3(mjcf.GetVector3Attribute("pos", defaultValue: Vector3.zero));
transform.localRotation = MjEngineTool.UnityQuaternion(
mjcf.GetQuaternionAttribute("quat", defaultValue: MjEngineTool.MjQuaternionIdentity));
}
protected override XmlElement OnGenerateMjcf(XmlDocument doc) {
var mjcf = (XmlElement)doc.CreateElement("body");
MjEngineTool.PositionRotationToMjcf(mjcf, this);
return mjcf;
}
public override unsafe void OnSyncState(MujocoLib.mjData_* data) {
transform.position = MjEngineTool.UnityVector3(data->xpos, MujocoId);
transform.rotation = MjEngineTool.UnityQuaternion(data->xquat, MujocoId);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 4ef51a360e7c24695800e2d06f70db49
timeCreated: 1537462857
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,52 @@
// 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 {
public class MjBodyQuaternionSensor : MjBaseSensor {
public MjBody Body;
[Tooltip("Should the Frame sensors use the inertial or the regular frame of reference.")]
public bool UseInertialFrame;
public Quaternion SensorReading { get; private set; }
protected override XmlElement ToMjcf(XmlDocument doc) {
if (Body == null) {
throw new NullReferenceException("Missing a reference to a MjBody.");
}
var mjcf = doc.CreateElement("framequat");
mjcf.SetAttribute("objtype", UseInertialFrame ? "body" : "xbody");
mjcf.SetAttribute("objname", Body.MujocoName);
return mjcf;
}
protected override void FromMjcf(XmlElement mjcf) {
UseInertialFrame = mjcf.HasAttribute("body");
Body = mjcf.GetObjectReferenceAttribute<MjBody>("objname");
if (Body == null) {
throw new NullReferenceException("Missing a reference to a MjBody.");
}
}
public override unsafe void OnSyncState(MujocoLib.mjData_* data) {
SensorReading = MjEngineTool.UnityQuaternion(data->sensordata, _sensorAddress);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: af03f553fc93c44ec8d1cc3fbbcce947
timeCreated: 1548170856
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,77 @@
// 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 {
public class MjBodyVectorSensor : MjBaseSensor {
// NOTE: These names must match sensor names listed on mujoco.org.
// Changing them is justified only when Mujoco is upgraded to a new version.
public enum AvailableSensors {
SubtreeCom,
SubtreeLinVel,
SubtreeAngMom,
FramePos,
FrameXAxis,
FrameYAxis,
FrameZAxis,
FrameLinVel,
FrameAngVel,
FrameLinAcc,
FrameAngAcc,
}
public AvailableSensors SensorType;
public MjBody Body;
[Tooltip("Should the Frame sensors use the inertial or the regular frame of reference.")]
public bool UseInertialFrame;
public Vector3 SensorReading { get; private set; }
protected override XmlElement ToMjcf(XmlDocument doc) {
if (Body == null) {
throw new NullReferenceException("Missing a reference to a MjBody.");
}
var tag = SensorType.ToString().ToLower();
var mjcf = doc.CreateElement(tag);
if (tag.Contains("frame")) {
mjcf.SetAttribute("objtype", UseInertialFrame ? "body" : "xbody");
mjcf.SetAttribute("objname", Body.MujocoName);
} else {
mjcf.SetAttribute("body", Body.MujocoName);
}
return mjcf;
}
protected override void FromMjcf(XmlElement mjcf) {
if (!Enum.TryParse(mjcf.Name, ignoreCase: true, result: out SensorType)) {
throw new ArgumentException($"Unknown sensor type {mjcf.Name}.");
}
if (mjcf.Name.Contains("frame")) {
UseInertialFrame = mjcf.HasAttribute("body"); // as opposed to xbody
Body = mjcf.GetObjectReferenceAttribute<MjBody>("objname");
} else {
Body = mjcf.GetObjectReferenceAttribute<MjBody>("body");
}
}
public override unsafe void OnSyncState(MujocoLib.mjData_* data) {
SensorReading = MjEngineTool.UnityVector3(data->sensordata, _sensorAddress);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 537df637b11f44df2a20bea718a015a6
timeCreated: 1548092931
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+55
View File
@@ -0,0 +1,55 @@
// 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 {
[Serializable]
public class MjBoxShape : IMjShape {
public Vector3 Extents = Vector3.one * 0.5f;
public void ToMjcf(XmlElement mjcf, Transform transform) {
var scaledExtents = MjEngineTool.MjExtents(Vector3.Scale(Extents, transform.lossyScale));
mjcf.SetAttribute("size", MjEngineTool.Vector3ToMjcf(scaledExtents));
}
public void FromMjcf(XmlElement mjcf) {
Extents = MjEngineTool.UnityExtents(
mjcf.GetVector3Attribute("size", defaultValue: Vector3.one * 0.5f));
Vector3 fromPoint, toPoint;
if (MjEngineTool.ParseFromToMjcf(mjcf, out fromPoint, out toPoint)) {
var extent = (toPoint - fromPoint).magnitude * 0.5f;
Extents = Vector3.one * extent;
}
}
public Tuple<Vector3[], int[]> BuildMesh() {
return MeshGenerators.BuildBox(extents: Extents);
}
public Vector4 GetChangeStamp() {
return Extents;
}
public void DebugDraw(Transform transform) {
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, transform.lossyScale);
Gizmos.DrawWireCube(Vector3.zero, Extents * 2.0f);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 9783bb0e781e341e5914cdbac095b35e
timeCreated: 1547647081
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,63 @@
// 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 {
[Serializable]
public class MjCapsuleShape : IMjShape {
public float Radius = 0.5f;
public float HalfHeight = 0.5f;
private const float _scaleTolerance = 1e-5f;
public void ToMjcf(XmlElement mjcf, Transform transform) {
if (Math.Abs(transform.lossyScale.x - transform.lossyScale.z) > _scaleTolerance) {
Debug.LogWarning(
$"{transform.name}: Capsule shapes require uniform scaling of XZ plane. Using the" +
" value of X and Y components.", transform);
}
mjcf.SetAttribute(
"size", $"{Radius * transform.lossyScale.x} {HalfHeight * transform.lossyScale.y}");
}
public void FromMjcf(XmlElement mjcf) {
var components = mjcf.GetFloatArrayAttribute("size", defaultValue: new float[] { 0.5f, 0.5f });
Radius = components[0];
HalfHeight = components[1];
Vector3 fromPoint, toPoint;
if (MjEngineTool.ParseFromToMjcf(mjcf, out fromPoint, out toPoint)) {
HalfHeight = 0.5f * (toPoint - fromPoint).magnitude;
}
}
public Tuple<Vector3[], int[]> BuildMesh() {
return MeshGenerators.BuildCapsule(radius: Radius, height: (HalfHeight + Radius) * 2.0f);
}
public Vector4 GetChangeStamp() {
return new Vector4(Radius, HalfHeight, 1, 0);
}
public void DebugDraw(Transform transform) {
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
MjGizmos.DrawWireCapsule(
Vector3.zero, Radius * transform.lossyScale.x, 2 * HalfHeight * transform.lossyScale.y);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 9b0a2054e1eec409fbf34c796a572b38
timeCreated: 1547647081
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+108
View File
@@ -0,0 +1,108 @@
// 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.Xml;
using UnityEngine;
namespace Mujoco {
// The base class for all components that represent Mujoco scene nodes.
[DisallowMultipleComponent]
public abstract class MjComponent : MonoBehaviour {
// Unique name assigned to each Mujoco component.
public string MujocoName { get; private set; }
// Id of the geom in Mujoco's internal data structures.
public int MujocoId { get; protected set; }
public abstract MujocoLib.mjtObj ObjectType { get; }
// Some components (Inertial frames for example) cannot have the name attribute
// added to the generated Mjcf.
protected virtual bool _suppressNameAttribute => false;
// Binds this component to the compiled Mujoco model.
public unsafe void BindToRuntime(MujocoLib.mjModel_* model, MujocoLib.mjData_* data) {
MujocoId = MujocoLib.mj_name2id(model, (int)ObjectType, MujocoName);
if (MujocoId == -1 && !_suppressNameAttribute) {
throw new NullReferenceException($"element name {MujocoName} not found");
}
OnBindToRuntime(model, data);
}
// Generates the XML element that corresponds to this scene node.
public XmlElement GenerateMjcf(string name, XmlDocument doc) {
MujocoName = name;
var mjcf = OnGenerateMjcf(doc);
if (!_suppressNameAttribute) {
mjcf.SetAttribute("name", name);
}
return mjcf;
}
// Parse the component settings from an external Mjcf.
public void ParseMjcf(XmlElement mjcf) {
// I would like to preserve the naming dualism - external mechanisms
// calling a method, and the internal implementation implementing
// the method with an "On" prefix.
OnParseMjcf(mjcf);
}
// Parse the component settings from an external Mjcf.
protected abstract void OnParseMjcf(XmlElement mjcf);
// Generate implementation specific XML element.
protected abstract XmlElement OnGenerateMjcf(XmlDocument doc);
// Perform bind time initialization of the component.
protected virtual unsafe void OnBindToRuntime(MujocoLib.mjModel_* model, MujocoLib.mjData_* data) {}
// Synchronize the state of the component.
public virtual unsafe void OnSyncState(MujocoLib.mjData_* data) {}
private bool _sceneExcludesMe = false;
protected unsafe virtual void Start() {
if (MjScene.Instance == null) {
throw new Exception("MuJoCo Scene not found");
}
if (MjScene.Instance.Model != null) {
_sceneExcludesMe = true;
}
}
protected void Update() {
if (_sceneExcludesMe) {
MjScene.Instance.SceneRecreationAtLateUpdateRequested = true;
_sceneExcludesMe = false;
}
}
private bool _exiting = false;
public void OnApplicationQuit() {
_exiting = true;
}
public void OnDisable() {
if (!_exiting) {
MjScene.Instance.SceneRecreationAtLateUpdateRequested = true;
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 7cb8afbea854147a8bf92a08d5f6577b
timeCreated: 1536879723
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+51
View File
@@ -0,0 +1,51 @@
// 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 {
public class MjConnect : MjBaseConstraint {
public MjBaseBody Body1;
public MjBaseBody Body2;
protected override string _constraintName => "connect";
protected override void FromMjcf(XmlElement mjcf) {
Body1 = mjcf.GetObjectReferenceAttribute<MjBaseBody>("body1");
Body2 = mjcf.GetObjectReferenceAttribute<MjBaseBody>("body2");
if (mjcf.GetStringAttribute("anchor") != null) {
Debug.Log($"anchor in connect {name} ignored. Set Transforms in the editor.");
}
}
// Generate implementation specific XML element.
protected override void ToMjcf(XmlElement mjcf) {
if (Body1 == null || Body2 == null) {
throw new NullReferenceException($"Both bodies in connect {name} are required.");
}
mjcf.SetAttribute("body1", Body1.MujocoName);
mjcf.SetAttribute("body2", Body2.MujocoName);
}
public void OnValidate() {
if (Body1 != null && Body1 == Body2) {
Debug.LogError("Body1 and Body2 can't be the same - resetting Body2.", this);
Body2 = null;
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ee6081d65578643bf8a26f200299a51a
timeCreated: 1552313909
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,63 @@
// 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 {
[Serializable]
public class MjCylinderShape : IMjShape {
public float Radius = 0.5f;
public float HalfHeight = 1.0f;
private const float _scaleTolerance = 1e-6f;
public void ToMjcf(XmlElement mjcf, Transform transform) {
if (Math.Abs(transform.lossyScale.x - transform.lossyScale.z) > _scaleTolerance) {
Debug.LogWarning(
$"{transform.name}: Cylinder shapes require uniform scaling of XZ plane. Using the" +
" value of X and Y components.", transform);
}
mjcf.SetAttribute(
"size", $"{Radius * transform.lossyScale.x} {HalfHeight * transform.lossyScale.y}");
}
public void FromMjcf(XmlElement mjcf) {
var components = mjcf.GetFloatArrayAttribute("size", defaultValue: new float[] { 0.5f, 1.0f });
Radius = components[0];
HalfHeight = components[1];
Vector3 fromPoint, toPoint;
if (MjEngineTool.ParseFromToMjcf(mjcf, out fromPoint, out toPoint)) {
HalfHeight = (toPoint - fromPoint).magnitude * 0.5f;
}
}
public Tuple<Vector3[], int[]> BuildMesh() {
return MeshGenerators.BuildCylinder(radius: Radius, height: HalfHeight * 2);
}
public Vector4 GetChangeStamp() {
return new Vector4(Radius, HalfHeight, 1, 0);
}
public void DebugDraw(Transform transform) {
Gizmos.matrix = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
MjGizmos.DrawWireCylinder(
Vector3.zero, Radius * transform.lossyScale.x, HalfHeight * 2 * transform.lossyScale.y);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: bd3a2b00a0a2f4ba9be8bd2f054a3835
timeCreated: 1547647081
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+48
View File
@@ -0,0 +1,48 @@
// 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 {
public class MjDistance : MjBaseConstraint {
public MjGeom Geom1;
public MjGeom Geom2;
protected override string _constraintName => "distance";
protected override void FromMjcf(XmlElement mjcf) {
Geom1 = mjcf.GetObjectReferenceAttribute<MjGeom>("geom1");
Geom2 = mjcf.GetObjectReferenceAttribute<MjGeom>("geom2");
}
protected override void ToMjcf(XmlElement mjcf) {
if (Geom1 == null || Geom2 == null) {
throw new NullReferenceException($"Both geoms in distance {name} must be assigned.");
}
mjcf.SetAttribute("geom1", Geom1.MujocoName);
mjcf.SetAttribute("geom2", Geom2.MujocoName);
}
public void OnValidate() {
if (Geom1 != null && Geom1 == Geom2) {
Debug.LogError("Geom1 and Geom2 can't be the same - resetting Geom2.", this);
Geom2 = null;
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ad0689a32f102481fb189836c11931f9
timeCreated: 1552309644
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,58 @@
// 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 {
[Serializable]
public class MjEllipsoidShape : IMjShape {
public Vector3 Radiuses = Vector3.one * 0.5f;
public void ToMjcf(XmlElement mjcf, Transform transform) {
var scaledRadiuses = MjEngineTool.MjExtents(Vector3.Scale(Radiuses, transform.lossyScale));
mjcf.SetAttribute("size", MjEngineTool.Vector3ToMjcf(scaledRadiuses));
}
public void FromMjcf(XmlElement mjcf) {
Radiuses = MjEngineTool.UnityExtents(
mjcf.GetVector3Attribute("size", defaultValue: Vector3.one * 0.5f));
Vector3 fromPoint, toPoint;
if (MjEngineTool.ParseFromToMjcf(mjcf, out fromPoint, out toPoint)) {
var radius = (toPoint - fromPoint).magnitude * 0.5f;
Radiuses = Vector3.one * radius;
}
}
public Tuple<Vector3[], int[]> BuildMesh() {
return MeshGenerators.BuildSphere(scale: Radiuses);
}
public Vector4 GetChangeStamp() {
return Radiuses;
}
public void DebugDraw(Transform transform) {
Gizmos.matrix = Matrix4x4.TRS(
transform.position,
transform.rotation,
Vector3.Scale(Radiuses, transform.lossyScale));
Gizmos.DrawWireSphere(Vector3.zero, 1.0f);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 8316fb5037f4a450c9270267e5428567
timeCreated: 1547647081
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+54
View File
@@ -0,0 +1,54 @@
// 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 {
public class MjExclude : MjComponent {
[Tooltip("A body whose contacts with other body are ignored")]
public MjBody Body1;
[Tooltip("Other body whose contacts with first body are ignored")]
public MjBody Body2;
public override MujocoLib.mjtObj ObjectType => MujocoLib.mjtObj.mjOBJ_EXCLUDE;
protected override void OnParseMjcf(XmlElement mjcf) {
var body1Name = mjcf.GetStringAttribute("body1", defaultValue: string.Empty);
if (!string.IsNullOrEmpty(body1Name)) {
Body1 = MjHierarchyTool.FindComponentOfTypeAndName<MjBody>(body1Name);
}
var body2Name = mjcf.GetStringAttribute("body2", defaultValue: string.Empty);
if (!string.IsNullOrEmpty(body2Name)) {
Body2 = MjHierarchyTool.FindComponentOfTypeAndName<MjBody>(body2Name);
}
}
// Generate implementation specific XML element.
protected override XmlElement OnGenerateMjcf(XmlDocument doc) {
if (Body1 == null || Body2 == null) {
throw new NullReferenceException($"Both bodies in {name} must be assigned.");
}
var mjcf = (XmlElement)doc.CreateElement("exclude");
mjcf.SetAttribute("body1", Body1.MujocoName);
mjcf.SetAttribute("body2", Body2.MujocoName);
return mjcf;
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 8d271c366e5c04a259f36a15b42f78a5
timeCreated: 1550051492
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+81
View File
@@ -0,0 +1,81 @@
// 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 {
[Serializable]
public class FixedTendonEntry {
[Tooltip("Only scalar joints can be used - hinge and slide.")]
public MjBaseJoint Joint;
[Tooltip("Multiplier for the value of the joint.")]
public float Coefficient = 1.0f;
}
public class MjFixedTendon : MjBaseTendon {
[Tooltip("In (scaled) radians/meters.")]
public float RangeLower;
[Tooltip("In (scaled) radians/meters.")]
public float RangeUpper;
public List<FixedTendonEntry> JointList = new List<FixedTendonEntry>() {};
protected override void FromMjcf(XmlElement mjcf) {
foreach (var child in mjcf.Cast<XmlNode>().OfType<XmlElement>()) {
var fixedTendonEntry = new FixedTendonEntry();
fixedTendonEntry.Joint = child.GetObjectReferenceAttribute<MjBaseJoint>("joint");
fixedTendonEntry.Coefficient = child.GetFloatAttribute("coef", defaultValue: 1.0f);
JointList.Add(fixedTendonEntry);
}
var rangeValues = mjcf.GetFloatArrayAttribute("range", defaultValue: new float[] { 0, 0 });
RangeLower = rangeValues[0];
RangeUpper = rangeValues[1];
}
protected override XmlElement ToMjcf(XmlDocument doc) {
if (JointList.Count < 1) {
throw new ArgumentOutOfRangeException($"Fixed tendon {name} needs at least one joint.");
}
var mjcf = doc.CreateElement("fixed");
foreach (FixedTendonEntry fixedTendonEntry in JointList) {
var jointMjcf = doc.CreateElement("joint");
jointMjcf.SetAttribute("joint", fixedTendonEntry.Joint.MujocoName);
jointMjcf.SetAttribute("coef", $"{fixedTendonEntry.Coefficient}");
mjcf.AppendChild(jointMjcf);
}
if (RangeLower > RangeUpper) {
throw new ArgumentException("Lower range value can't be bigger than Higher");
}
mjcf.SetAttribute("range", $"{RangeLower} {RangeUpper}");
return mjcf;
}
public void OnValidate() {
foreach (FixedTendonEntry fixedTendonEntry in JointList) {
if (!(fixedTendonEntry.Joint is MjHingeJoint) &&
!(fixedTendonEntry.Joint is MjSlideJoint)) {
Debug.LogError("Only scalar joints (hinge or slide) are allowed.", this);
fixedTendonEntry.Joint = null;
}
}
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: e49529c3d80714a368699287bbdd8f72
timeCreated: 1549990455
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+29
View File
@@ -0,0 +1,29 @@
// 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.Xml;
using UnityEngine;
namespace Mujoco {
// The component represents a joint with 6 degrees of freedom.
public class MjFreeJoint : MjBaseJoint {
protected override void OnParseMjcf(XmlElement mjcf) {}
protected override XmlElement OnGenerateMjcf(XmlDocument doc) {
return (XmlElement)doc.CreateElement("freejoint");
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 7ef627bd3075d42d886135a149a187a2
timeCreated: 1537540954
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+96
View File
@@ -0,0 +1,96 @@
// 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 {
// The component represents a physical shape and models its inertia and material properties.
public class MjGeom : MjShapeComponent {
[Tooltip("If larger than zero, Density has no effect.")]
public float Mass = 0.0f;
[Tooltip("Material density. Set to 0 for a zero-mass geom.")]
public float Density = 1000.0f;
[Tooltip("Advanced settings.")]
public MjGeomSettings Settings = MjGeomSettings.Default;
public override MujocoLib.mjtObj ObjectType => MujocoLib.mjtObj.mjOBJ_GEOM;
private MjTransformation _geomInGlobalFrame = new MjTransformation();
private MjTransformation _comTransform;
protected override void OnParseMjcf(XmlElement mjcf) {
ShapeFromMjcf(mjcf);
Mass = mjcf.GetFloatAttribute("mass", defaultValue: 0.0f);
Density = mjcf.GetFloatAttribute("density", defaultValue: 1000.0f);
MjEngineTool.ParseTransformMjcf(mjcf, transform);
Settings.FromMjcf(mjcf);
}
// The MuJoCo compiler shifts meshes' frames, so we need to cache this transformation at init and
// apply it at runtime.
protected override unsafe void OnBindToRuntime(MujocoLib.mjModel_* model, MujocoLib.mjData_* data) {
var MjParent = MjHierarchyTool.FindParentComponent<MjBaseBody>(this);
if (MjParent != null) {
var comInParentFrame = new MjTransformation(
translation: MjEngineTool.UnityVector3(model->geom_pos, MujocoId),
rotation: MjEngineTool.UnityQuaternion(model->geom_quat, MujocoId));
// We don't want to bother calculating global transform in mujoco from mjModel,
// so we'll assume it's the same as the Unity transfor (it's the beginning of simulation after
// all).
var globalParentFrame = MjTransformation.LoadGlobal(transform.parent);
var comInGlobalFrame = globalParentFrame * comInParentFrame;
var globalFrame = MjTransformation.LoadGlobal(transform);
_comTransform = comInGlobalFrame.Inverse() * globalFrame;
}
}
protected override XmlElement OnGenerateMjcf(XmlDocument doc) {
var mjcf = (XmlElement)doc.CreateElement("geom");
if (Mass > 0) {
mjcf.SetAttribute("mass", $"{Mass}");
} else {
mjcf.SetAttribute("density", $"{Density}");
}
ShapeToMjcf(mjcf, transform);
MjEngineTool.PositionRotationToMjcf(mjcf, this);
Settings.ToMjcf(mjcf);
return mjcf;
}
public override unsafe void OnSyncState(MujocoLib.mjData_* data) {
if (ShapeType == ShapeTypes.Mesh) {
_geomInGlobalFrame.Set(
translation: MjEngineTool.UnityVector3(data->geom_xpos, MujocoId),
rotation: MjEngineTool.UnityQuaternionFromMatrix(data->geom_xmat, MujocoId));
var comInGlobalFrame = _geomInGlobalFrame * _comTransform;
comInGlobalFrame.StoreGlobal(transform);
} else {
transform.position = MjEngineTool.UnityVector3(data->geom_xpos, MujocoId);
transform.rotation = MjEngineTool.UnityQuaternionFromMatrix(data->geom_xmat, MujocoId);
}
}
public void OnDrawGizmosSelected() {
Gizmos.color = Color.blue;
DrawGizmos(transform);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: ba8757a90afe442d9acfbd600758b843
timeCreated: 1536743564
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,47 @@
// 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 {
public class MjGeomQuaternionSensor : MjBaseSensor {
public MjGeom Geom;
public Quaternion SensorReading { get; private set; }
protected override XmlElement ToMjcf(XmlDocument doc) {
if (Geom == null) {
throw new NullReferenceException("Missing a reference to a MjGeom.");
}
var mjcf = doc.CreateElement("framequat");
mjcf.SetAttribute("objtype", "geom");
mjcf.SetAttribute("objname", Geom.MujocoName);
return mjcf;
}
protected override void FromMjcf(XmlElement mjcf) {
Geom = mjcf.GetObjectReferenceAttribute<MjGeom>("objname");
if (Geom == null) {
throw new NullReferenceException("Missing a reference to a MjGeom.");
}
}
public override unsafe void OnSyncState(MujocoLib.mjData_* data) {
SensorReading = MjEngineTool.UnityQuaternion(data->sensordata, _sensorAddress);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 71b28b4bc4c27423aaba5b98863e0c92
timeCreated: 1548170856
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+148
View File
@@ -0,0 +1,148 @@
// 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.Xml;
using UnityEngine;
namespace Mujoco {
// Advanced settings of MjGeom component.
[Serializable]
public struct MjGeomSettings {
[Tooltip("Solver accuracy settings.")]
public GeomSolver Solver;
[Tooltip("Collision filtering settings.")]
public CollisionFiltering Filtering;
[Tooltip("Contact friction parameters for dynamically generated contact pairs.")]
public GeomFriction Friction;
// Default geom settings.
public static MjGeomSettings Default = new MjGeomSettings() {
Solver = GeomSolver.Default,
Filtering = CollisionFiltering.Default,
Friction = GeomFriction.Default
};
public void FromMjcf(XmlElement mjcf) {
// Contact filtering settings.
Filtering.Contype = (int)mjcf.GetFloatAttribute("contype", CollisionFiltering.Default.Contype);
Filtering.Conaffinity = (int)mjcf.GetFloatAttribute(
"conaffinity", CollisionFiltering.Default.Conaffinity);
Filtering.Group = (int)mjcf.GetFloatAttribute("group", CollisionFiltering.Default.Group);
// Solver settings.
Solver.ConDim = (int)mjcf.GetFloatAttribute("condim", GeomSolver.Default.ConDim);
Solver.SolMix = mjcf.GetFloatAttribute("solmix", GeomSolver.Default.SolMix);
var solref = mjcf.GetFloatArrayAttribute(
"solref", new float[] { GeomSolver.Default.SolRef.TimeConst,
GeomSolver.Default.SolRef.DampRatio });
Solver.SolRef.TimeConst = solref[0];
Solver.SolRef.DampRatio = solref[1];
var solimp = mjcf.GetFloatArrayAttribute(
"solimp", new float[] { GeomSolver.Default.SolImp.DMin, GeomSolver.Default.SolImp.DMax,
GeomSolver.Default.SolImp.Width });
Solver.SolImp.DMin = solimp[0];
Solver.SolImp.DMax = solimp[1];
Solver.SolImp.Width = solimp[2];
Solver.Margin = mjcf.GetFloatAttribute("margin", GeomSolver.Default.Margin);
Solver.Gap = mjcf.GetFloatAttribute("gap", GeomSolver.Default.Gap);
// Inertia and friction settings.
var friction = mjcf.GetFloatArrayAttribute(
"friction", new float[] { GeomFriction.Default.Sliding, GeomFriction.Default.Torsional,
GeomFriction.Default.Rolling });
Friction.Sliding = friction[0];
Friction.Torsional = friction[1];
Friction.Rolling = friction[2];
}
public void ToMjcf(XmlElement mjcf) {
// Contact filtering settings.
mjcf.SetAttribute("contype", $"{Filtering.Contype}");
mjcf.SetAttribute("conaffinity", $"{Filtering.Conaffinity}");
mjcf.SetAttribute("group", $"{Filtering.Group}");
// Solver settings.
mjcf.SetAttribute("condim", $"{Solver.ConDim}");
mjcf.SetAttribute("solmix", $"{Solver.SolMix}");
mjcf.SetAttribute("solref", $"{Solver.SolRef.TimeConst} {Solver.SolRef.DampRatio}");
mjcf.SetAttribute(
"solimp", $"{Solver.SolImp.DMin} {Solver.SolImp.DMax} {Solver.SolImp.Width}");
mjcf.SetAttribute("margin", $"{Solver.Margin}");
mjcf.SetAttribute("gap", $"{Solver.Gap}");
// Inertia and friction settings.
mjcf.SetAttribute("friction", $"{Friction.Sliding} {Friction.Torsional} {Friction.Rolling}");
}
}
[Serializable]
public struct GeomFriction {
public float Sliding;
public float Torsional;
public float Rolling;
public static GeomFriction Default = new GeomFriction {
Sliding = 1.0f, Torsional = 0.005f, Rolling = 0.0001f
};
}
[Serializable]
public struct CollisionFiltering {
[Tooltip("Bitmasks used for contact filtering of dynamically generated contact pairs.")]
public int Contype;
[Tooltip("Bitmask for contact filtering")]
public int Conaffinity;
// The only effect on the physics is at compile time, when body masses and inertias are inferred
// from geoms selected based on their group.
[Tooltip("Group to which the geom belongs.")]
public int Group;
public static CollisionFiltering Default = new CollisionFiltering() {
Contype = 1, Conaffinity = 1, Group = 0
};
}
[Serializable]
public struct GeomSolver {
[Tooltip("The dimensionality of the contact space.")]
public int ConDim;
[Tooltip("Weight used for averaging of constraint solver parameters.")]
public float SolMix;
[Tooltip("Solver function d(r) reference parameters.")]
public SolverReference SolRef;
[Tooltip("Solver function d(r) impedance parameters.")]
public SolverImpedance SolImp;
[Tooltip("Distance threshold below which contacts are detected.")]
public float Margin;
[Tooltip("Positive value enables generation of inactive contacts.")]
public float Gap;
public static GeomSolver Default = new GeomSolver() {
ConDim = 3, SolMix = 1.0f, SolRef = SolverReference.Default, SolImp = SolverImpedance.Default,
Margin = 0.0f, Gap = 0.0f
};
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: d472b07eedb654ed5a83bfd6822587a3
timeCreated: 1538655839
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,63 @@
// 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 {
public class MjGeomVectorSensor : MjBaseSensor {
// NOTE: These names must match sensor names listed on mujoco.org.
// Changing them is justified only when Mujoco is upgraded to a new version.
public enum AvailableSensors {
FramePos,
FrameXAxis,
FrameYAxis,
FrameZAxis,
FrameLinVel,
FrameAngVel,
FrameLinAcc,
FrameAngAcc,
}
public AvailableSensors SensorType;
public MjGeom Geom;
public Vector3 SensorReading { get; private set; }
protected override XmlElement ToMjcf(XmlDocument doc) {
if (Geom == null) {
throw new NullReferenceException("Missing a reference to a MjGeom.");
}
var mjcf = doc.CreateElement(SensorType.ToString().ToLower());
mjcf.SetAttribute("objtype", "geom");
mjcf.SetAttribute("objname", Geom.MujocoName);
return mjcf;
}
protected override void FromMjcf(XmlElement mjcf) {
if (!Enum.TryParse(mjcf.Name, ignoreCase: true, result: out SensorType)) {
throw new ArgumentException($"Unknown sensor type {mjcf.Name}.");
}
Geom = mjcf.GetObjectReferenceAttribute<MjGeom>("objname");
}
public override unsafe void OnSyncState(MujocoLib.mjData_* data) {
SensorReading = MjEngineTool.UnityVector3(data->sensordata, _sensorAddress);
}
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: c63b35fff51454145b2c5cabf4a81669
timeCreated: 1548107238
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+116
View File
@@ -0,0 +1,116 @@
// 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 {
// Additional Debug Gizmos.
public static class MjGizmos {
private const float _vectorPerpendicularityMagnitudeThreshold = 1e-6f;
public static int NumCircleSegments = 32;
private static Vector3[] _circumferenceVertices = new Vector3[] {
Vector3.right, -Vector3.right, Vector3.forward, -Vector3.forward,
};
public static Action<Vector3 /*from*/, Vector3 /*to*/> DrawLine { get; set; } = Gizmos.DrawLine;
public static Action<Vector3 /*position*/, float /*radius*/, Vector3 /*axis*/, float /*length*/,
float /*startAngle*/> DrawArc { get; set; } = MjGizmos.DrawArcInternal;
// Draws a wireframe capsule of the specified radius and height.
// The capsule's height will be aligned with the global Y axis.
// The specified height represents the height of the cylinder between two hemisphere bases.
public static void DrawWireCapsule(Vector3 position, float radius, float height) {
var offset = DrawHollowWireCylinder(position, radius, height);
// Arches marking the bases of the capsule.
DrawArc(position - offset, radius, Vector3.right, 0.5f, 0.0f);
DrawArc(position + offset, radius, Vector3.right, 0.5f, 180.0f);
DrawArc(position + offset, radius, Vector3.forward, 0.5f, -90.0f);
DrawArc(position - offset, radius, Vector3.forward, 0.5f, 90.0f);
}
// Draws a wireframe cylinder of the specified radius and height.
// The cylinder's height will be aligned with the global Y axis.
public static void DrawWireCylinder(Vector3 position, float radius, float height) {
var offset = DrawHollowWireCylinder(position, radius, height);
// Lines marking the bases of the cylinder.
for (var i = 0; i < _circumferenceVertices.Length / 2; ++i) {
var vertex = _circumferenceVertices[i * 2] * radius;
var nextVertex = _circumferenceVertices[i * 2 + 1] * radius;
DrawLine(position + offset + vertex, position + offset + nextVertex);
DrawLine(position - offset + vertex, position - offset + nextVertex);
}
}
private static Vector3 DrawHollowWireCylinder(Vector3 position, float radius, float height) {
var offset = Vector3.up * (height * 0.5f);
// Cylinder bases.
DrawArc(position + offset, radius, Vector3.up, 1.0f, 0.0f);
DrawArc(position - offset, radius, Vector3.up, 1.0f, 0.0f);
// Lines marking the sides of the cylinder.
for (var i = 0; i < _circumferenceVertices.Length; ++i) {
// Lines marking the sides of the cylinder.
var vertex = _circumferenceVertices[i] * radius;
DrawLine(position + offset + vertex, position - offset + vertex);
}
return offset;
}
// Draws an arc in the XZ plane.
// 'length' parameter, in range (0, 1], allows to define how much of the circumference should be
// drawn. In other words, it allows to draw arbitrary arcs.
// Two additional parameters, 'axis' and 'startAngle', allow to orient the figure without having
// to manipulate the value of Gizmos.matrix.
public static void DrawArcInternal(
Vector3 position, float radius, Vector3 axis, float length, float startAngle) {
var deltaAngle = 360.0f / NumCircleSegments;
var numSegments = Math.Ceiling(length * NumCircleSegments);
var perpendicular = Vector3.Cross(axis, Vector3.right);
if (perpendicular.magnitude <= _vectorPerpendicularityMagnitudeThreshold) {
perpendicular = Vector3.Cross(axis, Vector3.up);
}
perpendicular.Normalize();
var offset = perpendicular * radius;
var start = Quaternion.AngleAxis(startAngle, axis) * offset;
var angle = startAngle + deltaAngle;
for (var segment = 0; segment < numSegments; ++segment, angle += deltaAngle) {
var end = Quaternion.AngleAxis(angle, axis) * offset;
DrawLine(position + start, position + end);
start = end;
}
}
public static void DrawWirePlane(Vector3 position, float width, float height) {
var quadPoints = new Vector3[] {
new Vector3(-0.5f * width, 0, -0.5f * height),
new Vector3(0.5f * width, 0, -0.5f * height),
new Vector3(0.5f * width, 0, 0.5f * height),
new Vector3(-0.5f * width, 0, 0.5f * height),
new Vector3(-0.5f * width, 0, -0.5f * height),
new Vector3(0.5f * width, 0, 0.5f * height),
};
for (var i = 0; i < quadPoints.Length - 1; ++i) {
DrawLine(position + quadPoints[i], position + quadPoints[i + 1]);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More