unity/Assets/Scripts/RobotLoader.cs (648 lines of code) (raw):

using System.Collections.Generic; using UnityEngine; using System.Xml; using System.IO; using Parabox.STL; // This monstrosity loads a MuJoCo XML and rebuilds the robot. // It loads the required geoms from STL files and generates the // geometric primitives necessary. It recreates the joints, geometry, // cameras and sites defined in the XML. MuJoCo class inheritance // is supported for those entities. Far from everything in the MuJoCo // modelling reference is implemented. public class RobotLoader : KineticHierarchyController { public struct MeshDefinition { public string name; public Vector3 scale; public byte[] contents; public MeshDefinition(string name, Vector3 scale, byte[] contents) { this.name = name; this.scale = scale; this.contents = contents; } } public struct TextureDefinition { public string name; public byte[] contents; public TextureDefinition(string name, byte[] contents) { this.name = name; this.contents = contents; } } [SerializeField] public GameObject assembly_parts_ = null; [SerializeField] public Material default_material_ = null; [SerializeField] public bool convexify_meshes_ = false; private Dictionary<string, Material> materials_ = new Dictionary<string, Material>(); private int part_counter_ = 0; private int camera_counter_ = 0; private string asset_basedir_ = "."; private string mesh_dir_ = "."; private string texture_dir_ = "."; private List<string> geom_categories_ = new List<string>(); private GameObject mr_robot_ = null; // Main entry point, use the provided directory as base for // relative paths. public bool LoadRobot(string xml_file, string asset_basedir) { return LoadRobot(xml_file, asset_basedir, new List<TextureDefinition>(), new List<MeshDefinition>()); } public bool LoadRobot(string xml_file, string asset_basedir, IList<TextureDefinition> textures, IList<MeshDefinition> meshes) { this.asset_basedir_ = asset_basedir; xml_file = ConfigUtils.ResolveFile(asset_basedir_, xml_file); XmlDocument mr_robot_xml = new XmlDocument(); mr_robot_xml.Load(xml_file); // If the XML has includes, merge everything into one. ResolveXmlIncludes(Path.GetDirectoryName(xml_file), mr_robot_xml); XmlNode mujoco = XmlUtils.GetChildNode(mr_robot_xml, "mujoco"); if (mujoco == null) { Logger.Error("RobotLoader::LoadRobot::Cannot find mujoco node."); return false; } // Set up global properties. HandleCompilerDirectives(mujoco); // Preload textures and geom meshes. if (!PrepareAssets(mr_robot_xml, textures, meshes)) { Logger.Error("RobotLoader::LoadRobot::Failed to prepare assets."); return false; } XmlUtils.Defaults defaults = new XmlUtils.Defaults(XmlUtils.GetChildNode(mujoco, "default")); XmlNode worldbody = XmlUtils.GetChildNode(mujoco, "worldbody"); if (worldbody == null) { Logger.Error("RobotLoader::LoadRobot::No worldbody defined."); return false; } // Prepare geom names for semantic segmentation PrepareGeomCategories(worldbody); geom_categories_.Sort(); if (!AddRobotBody(this, worldbody, defaults)) { Logger.Error("RobotLoader::LoadRobot::Failed to build robot parts."); return false; } name = "robot"; // MuJoCo has a different world orientation than Unity. transform.localScale = new Vector3(-transform.localScale.x, transform.localScale.y, transform.localScale.z); return true; } private void PrepareGeomCategories(XmlNode node) { List<XmlNode> geom_nodes = XmlUtils.GetChildNodes(node, "geom"); foreach (XmlNode geom_node in geom_nodes) { string geom_category = GeomController.GetGeomCategoryFromXml(geom_node); if (geom_category != null && !geom_categories_.Contains(geom_category)) { geom_categories_.Add(geom_category); } } List<XmlNode> body_nodes = XmlUtils.GetChildNodes(node, "body"); foreach (XmlNode body_node in body_nodes) { PrepareGeomCategories(body_node); } } private void HandleCompilerDirectives(XmlNode node) { foreach (XmlNode child_node in node.ChildNodes) { HandleCompilerDirectives(child_node); } List<XmlNode> compiler_directives = XmlUtils.GetChildNodes(node, "compiler"); foreach (XmlNode compiler_directive in compiler_directives) { mesh_dir_ = XmlUtils.GetString(compiler_directive, "meshdir", mesh_dir_); texture_dir_ = XmlUtils.GetString(compiler_directive, "texturedir", texture_dir_); } } private bool PrepareAssets(XmlNode node, IList<TextureDefinition> textures, IList<MeshDefinition> meshes) { // Load assets that were passed directly in code. foreach (MeshDefinition mesh in meshes) { if (!LoadMeshDefinition(mesh)) { return false; } } foreach (TextureDefinition texture in textures) { if (!LoadTextureDefinition(texture)) { return false; } } // Load assets that are defined in the XML. return PrepareXmlAssets(node); } private bool LoadMeshDefinition(MeshDefinition mesh) { List<Mesh> meshes = pb_Stl_Importer.ImportBytes(mesh.contents); if (meshes == null) { Logger.Error("RobotLoader::LoadMeshDefinition::Cannot load: {} from MeshDefinition contents.", mesh.name); return false; } if (meshes.Count == 0) { Logger.Error("RobotLoader::LoadMeshDefinition::No meshes in MeshDefinition for: {}.", mesh.name); return false; } return CreateMeshAsset(assembly_parts_, mesh.name, mesh.scale, meshes); } private bool LoadTextureDefinition(TextureDefinition texture) { Logger.Warning("RobotLoader::LoadTextureDefinition::Ignoring texture: {0}.", texture.name); return true; } private bool PrepareXmlAssets(XmlNode node) { foreach (XmlNode child_node in node.ChildNodes) { if (!PrepareXmlAssets(child_node)) { return false; } } // Look in the <asset></asset> tag. List<XmlNode> asset_nodes = XmlUtils.GetChildNodes(node, "asset"); foreach (XmlNode asset_node in asset_nodes) { List<XmlNode> mesh_nodes = XmlUtils.GetChildNodes(asset_node, "mesh"); foreach (XmlNode mesh_node in mesh_nodes) { if (!LoadXmlMeshAsset(mesh_node)) { return false; } } List<XmlNode> material_nodes = XmlUtils.GetChildNodes(asset_node, "material"); foreach (XmlNode material_node in material_nodes) { if (!LoadXmlMaterialAsset(material_node)) { return false; } } } return true; } private bool LoadXmlMaterialAsset(XmlNode material_node) { string material_name = XmlUtils.GetString(material_node, "name", null); if (material_name == null) { Logger.Error("RobotLoader::LoadXmlMaterialAsset::Missing material name."); return false; } Material material = new Material(default_material_); material.name = material_name; if (material.HasProperty("_Color")) { material.color = XmlUtils.GetColor(material_node, "rgba", Color.grey); } if (material.HasProperty("_SpecColor")) { material.SetColor("_SpecColor", XmlUtils.GetColor(material_node, "specular_rgba", Color.grey)); } if (material.HasProperty("_Glossiness")) { material.SetFloat("_Glossiness", XmlUtils.GetFloat(material_node, "shininess", 0.5f)); } if (material.HasProperty("_Metallic")) { material.SetFloat("_Metallic", XmlUtils.GetFloat(material_node, "specular", 0.5f)); } // If the XML contains the emission parameter for this material, // turn emissive color on. float emission = XmlUtils.GetFloat(material_node, "emission", 0.0f); if (emission > 0.001f && material.HasProperty("_EmissionColor")) { Color finalEmission = emission * XmlUtils.GetColor(material_node, "rgba", Color.white); material.EnableKeyword("_EMISSION"); material.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive; material.SetColor("_EmissionColor", finalEmission); } materials_.Add(material_name, material); return true; } private bool LoadXmlMeshAsset(XmlNode mesh_node) { string mesh_name = XmlUtils.GetString(mesh_node, "name", null); if (mesh_name == null) { Logger.Error("RobotLoader::LoadXmlMeshAsset::Missing mesh name."); return false; } string mesh_file = XmlUtils.GetString(mesh_node, "file", null); if (mesh_file == null) { Logger.Error("RobotLoader::LoadXmlMeshAsset::Missing mesh file for: {}.", mesh_name); return false; } string mesh_path = ConfigUtils.ResolveFile(asset_basedir_, Path.Combine(mesh_dir_, mesh_file)); // Import STL for this mesh. Unity has a 64k vertices per mesh // limit (in order to fit indexes in short) so one STL might // produce multiple Meshes. IList<Mesh> meshes = pb_Stl_Importer.Import(mesh_path); // We can globaly turn convexification on. MuJoCo convexifies all // geoms for collision detection, this mode can help debug / inspect it. if (convexify_meshes_) { List<Mesh> convex_meshes = new List<Mesh>(); foreach (Mesh mesh in meshes) { convex_meshes.Add(ConvexHull.CreateConvexHull(mesh)); } meshes = convex_meshes; } if (meshes == null) { Logger.Error("RobotLoader::LoadXmlMeshAsset::Cannot load: {} from: {}.", mesh_name, mesh_path); return false; } if (meshes.Count == 0) { Logger.Error("RobotLoader::LoadXmlMeshAsset::No meshes in: {} for: {}.", mesh_path, mesh_name); return false; } Vector3 scale = XmlUtils.GetVector3(mesh_node, "scale", Vector3.one); return CreateMeshAsset(assembly_parts_, mesh_name, scale, meshes); } // Take the mesh and its name and put the prefab asset in // a proper place in the prefab hierarchy. private bool CreateMeshAsset(GameObject parent, string asset_name, Vector3 scale, IList<Mesh> meshes) { string[] split = asset_name.Split(new char[] { ':' }, 2, System.StringSplitOptions.RemoveEmptyEntries); Transform asset_prefab_transform = parent.transform.Find(split[0]); GameObject asset_prefab = null; if (asset_prefab_transform != null) { asset_prefab = asset_prefab_transform.gameObject; } if (asset_prefab == null) { asset_prefab = new GameObject(split[0]); asset_prefab.transform.parent = parent.transform; asset_prefab.transform.localPosition = Vector3.zero; asset_prefab.transform.localRotation = Quaternion.identity; asset_prefab.transform.localScale = Vector3.one; asset_prefab.AddComponent<GeomController>(); } if (split.Length == 2) { return CreateMeshAsset(asset_prefab, split[1], scale, meshes); } else { asset_prefab.transform.localScale = scale; int counter = 0; foreach (Mesh mesh in meshes) { GameObject asset_mesh = new GameObject(string.Format("{0}_mesh_{1}", asset_name, counter++)); asset_mesh.transform.parent = asset_prefab.transform; asset_mesh.transform.localPosition = Vector3.zero; asset_mesh.transform.localRotation = Quaternion.identity; asset_mesh.transform.localScale = Vector3.one; asset_mesh.AddComponent<MeshFilter>().sharedMesh = mesh; asset_mesh.AddComponent<MeshRenderer>().sharedMaterial = default_material_; } return true; } } private void ResolveXmlIncludes(string root_dir, XmlNode node) { foreach (XmlNode child in node.ChildNodes) { ResolveXmlIncludes(root_dir, child); } List<XmlNode> includes = XmlUtils.GetChildNodes(node, "include"); foreach (XmlNode include in includes) { XmlDocument loaded_include = LoadInclude(root_dir, include); if (loaded_include == null) { Logger.Error("RobotLoader::ResolveXMLIncludes::Cannot load include."); continue; } node.RemoveChild(include); XmlNode mujoco_root_node = XmlUtils.GetChildNode(loaded_include, "mujocoinclude"); if (mujoco_root_node == null) { Logger.Error("RobotLoader::ResolveXmlIncludes::Included file needs a mujocoinclude node."); continue; } foreach (XmlNode included_child in mujoco_root_node.ChildNodes) { XmlNode imported_child = node.OwnerDocument.ImportNode(included_child, true); node.AppendChild(imported_child); } } } private XmlDocument LoadInclude(string root_dir, XmlNode include_node) { string file = XmlUtils.GetString(include_node, "file", null); if (file == null) { Logger.Error("RobotLoader::LoadInclude::Missing file attribute in include node."); return null; } XmlDocument included_document = new XmlDocument(); included_document.Load(Path.Combine(root_dir, file)); ResolveXmlIncludes(root_dir, included_document); return included_document; } // Get the XML with a default class for a given entity class_name. private XmlNode FindDefault(XmlNode node, string class_name) { List<XmlNode> default_nodes = XmlUtils.GetChildNodes(node, "default"); foreach (XmlNode default_node in default_nodes) { string default_class_name = XmlUtils.GetString(default_node, "class", null); if (class_name.Equals(default_class_name)) { return default_node; } } return null; } // Handle creation of a MuJoCo XML body / worldbody entity. private bool AddRobotBody(KineticHierarchyController parent, XmlNode part_xml, XmlUtils.Defaults defaults) { defaults = defaults.Resolve(XmlUtils.GetString(part_xml, "childclass", null)); KineticHierarchyController robot_part_attachment = BuildRobotBodyAttachment(parent, part_xml, defaults); return BuildRobotBodyChildren(robot_part_attachment, part_xml, defaults); } // Create the hierarchy that handles local transformations // and joints for a given body. private KineticHierarchyController BuildRobotBodyAttachment(KineticHierarchyController parent, XmlNode part_xml, XmlUtils.Defaults defaults) { string part_name = XmlUtils.GetString(part_xml, "name", string.Format("part_{0}", part_counter_)); part_counter_++; // Build the body and set local position/rotation/scale relative to parent. BodyController body = SceneUtils.InstantiateWithController<BodyController>(part_name); body.Initialize(parent, part_name, XmlUtils.GetVector3(part_xml, "pos", Vector3.zero), XmlUtils.GetRotation(part_xml, Quaternion.identity)); List<XmlNode> joint_nodes = XmlUtils.GetChildNodes(part_xml, "joint"); // Add all the joints in a hierarchy, one after another // (XML order is important). KineticHierarchyController last_game_object = body; for (int i = 0; i < joint_nodes.Count; ++i) { last_game_object = BuildJoint(last_game_object, part_name, i, joint_nodes[i], defaults); } return last_game_object; } // Recursively add geoms, cameras, sites and other bodies. private bool BuildRobotBodyChildren(KineticHierarchyController parent, XmlNode part_xml, XmlUtils.Defaults defaults) { List<XmlNode> geom_nodes = XmlUtils.GetChildNodes(part_xml, "geom"); foreach (XmlNode geom_node in geom_nodes) { if (!AddRobotGeom(parent, geom_node, defaults)) { Logger.Error("RobotLoader::BuildRobotBodyChildren::Cannot add robot geom."); return false; } } List<XmlNode> camera_nodes = XmlUtils.GetChildNodes(part_xml, "camera"); foreach (XmlNode camera_node in camera_nodes) { if (!AddCamera(parent, camera_node, defaults)) { Logger.Error("RobotLoader::BuildRobotBodyChildren::Cannot add robot camera."); return false; } } List<XmlNode> site_nodes = XmlUtils.GetChildNodes(part_xml, "site"); foreach (XmlNode site_node in site_nodes) { if (!AddRobotSite(parent, site_node, defaults)) { Logger.Error("RobotLoader::BuildRobotBodyChildren::Cannot add robot site."); return false; } } List<XmlNode> child_nodes = XmlUtils.GetChildNodes(part_xml, "body"); foreach (XmlNode child_node in child_nodes) { if (!AddRobotBody(parent, child_node, defaults)) { Logger.Error("RobotLoader::BuildRobotBodyChildren::Cannot add robot body."); return false; } } return true; } private KineticHierarchyController BuildJoint(KineticHierarchyController parent, string part_name, int id, XmlNode joint_xml, XmlUtils.Defaults defaults) { string joint_name = XmlUtils.GetString(joint_xml, "name", string.Format("{0}_joint_{1}", part_name, id)); XmlUtils.Defaults joint_defaults = defaults.GetSubclass("joint"); JointController joint = SceneUtils.InstantiateWithController<JointController>(joint_name); joint.Initialize(parent, joint_name, XmlUtils.GetVector3WithDefaults(joint_xml, joint_defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(joint_xml, joint_defaults, Quaternion.identity), XmlUtils.GetVector3WithDefaults(joint_xml, joint_defaults, "axis", Vector3.up), XmlUtils.GetVector2WithDefaults(joint_xml, joint_defaults, "range", new Vector2(float.MinValue, float.MaxValue)), XmlUtils.GetStringWithDefaults(joint_xml, joint_defaults, "type", null)); return joint; } private bool AddCamera(KineticHierarchyController parent, XmlNode camera_xml, XmlUtils.Defaults defaults) { string camera_name = XmlUtils.GetString(camera_xml, "name", null); if (camera_name == null) { camera_name = string.Format("camera_{0}", camera_counter_++); } Camera camera_prototype = SceneUtils.Find<Camera>(assembly_parts_, camera_name, SceneUtils.Find<Camera>(assembly_parts_, "__camera_template", null)); if (camera_prototype == null) { Logger.Error("RobotLoader::AddCamera::Cannot find camera prefab for: {0}", camera_name); return false; } Camera robot_camera = Instantiate(camera_prototype); robot_camera.name = camera_name; robot_camera.transform.parent = parent.transform; robot_camera.transform.localRotation = XmlUtils.GetRotation(camera_xml, Quaternion.identity); robot_camera.transform.localPosition = XmlUtils.GetVector3(camera_xml, "pos", Vector3.zero); robot_camera.transform.LookAt(robot_camera.transform.position + robot_camera.transform.TransformDirection(Vector3.back), robot_camera.transform.TransformDirection(Vector3.up)); robot_camera.fieldOfView = XmlUtils.GetFloat(camera_xml, "fovy", robot_camera.fieldOfView); return true; } public GameObject GetRobot() { return mr_robot_; } // Get the camera template, that will be cloned when instantiating // cameras in the XML. private GameObject FindCameraPrefab(string prefab_name) { for (int i = 0; i < assembly_parts_.transform.childCount; ++i) { if (assembly_parts_.transform.GetChild(i).name == prefab_name) { return assembly_parts_.transform.GetChild(i).gameObject; } } if (prefab_name == "__camera_template") { Logger.Warning("RobotLoader::FindCameraPrefab::Cannot find default camera."); return null; } else { Logger.Warning("RobotLoader::FindCameraPrefab::Cannot find {0}, looking for default.", prefab_name); return FindCameraPrefab("__camera_template"); } } private bool AddRobotSite(KineticHierarchyController parent, XmlNode site_xml, XmlUtils.Defaults defaults) { defaults = defaults.Resolve(XmlUtils.GetString(site_xml, "class", null)).GetSubclass("site"); SiteController site = null; string type = XmlUtils.GetStringWithDefaults(site_xml, defaults, "type", "sphere"); if ("box".Equals(type)) { site = AddRobotBoxSite(parent, site_xml, defaults); } else if ("plane".Equals(type)) { site = AddRobotPlaneSite(parent, site_xml, defaults); } else if (type == null || "sphere".Equals(type)) { site = AddRobotSphereSite(parent, site_xml, defaults); } else if ("cylinder".Equals(type)) { site = AddRobotCylinderSite(parent, site_xml, defaults); } else if ("capsule".Equals(type)) { site = AddRobotCapsuleSite(parent, site_xml, defaults); } else { Logger.Error("RobotLoader::AddRobotSite::Unsupported site type: {0} in {1}.", type, site_xml.OuterXml); return false; } if (site == null) { Logger.Error("RobotLoader::AddRobotSite::Cannot instantiate site."); return false; } ResolveMaterial(site_xml, defaults, site); return true; } private SiteController AddRobotBoxSite(KineticHierarchyController parent, XmlNode site_xml, XmlUtils.Defaults defaults) { return SiteController.CreateBox( parent, XmlUtils.GetString(site_xml, "name", null), XmlUtils.GetVector3WithDefaults(site_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(site_xml, defaults, Quaternion.identity), 2 * XmlUtils.GetVector3WithDefaults(site_xml, defaults, "size", Vector3.one)); } private SiteController AddRobotSphereSite(KineticHierarchyController parent, XmlNode site_xml, XmlUtils.Defaults defaults) { Vector3 size = XmlUtils.GetVector3WithDefaults(site_xml, defaults, "size", Vector3.zero); return SiteController.CreateSphere( parent, XmlUtils.GetString(site_xml, "name", null), XmlUtils.GetVector3WithDefaults(site_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(site_xml, defaults, Quaternion.identity), Vector3.one * 2.0f * size.x); } private SiteController AddRobotPlaneSite(KineticHierarchyController parent, XmlNode site_xml, XmlUtils.Defaults defaults) { Vector3 mujoco_size = XmlUtils.GetVector3WithDefaults(site_xml, defaults, "size", Vector3.one); return SiteController.CreatePlane( parent, XmlUtils.GetString(site_xml, "name", null), XmlUtils.GetVector3WithDefaults(site_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(site_xml, defaults, Quaternion.identity) * Quaternion.Euler(90.0f, 0.0f, 0.0f), 2 * new Vector3(mujoco_size.x / 10.0f, 0.0f, mujoco_size.y / 10.0f)); } private SiteController AddRobotCylinderSite(KineticHierarchyController parent, XmlNode site_xml, XmlUtils.Defaults defaults) { Vector2 mujoco_size = XmlUtils.GetVector2WithDefaults(site_xml, defaults, "size", Vector2.one); return SiteController.CreateCylinder( parent, XmlUtils.GetString(site_xml, "name", null), XmlUtils.GetVector3WithDefaults(site_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(site_xml, defaults, Quaternion.identity), 2 * new Vector3(mujoco_size.x, mujoco_size.y / 2.0f, mujoco_size.x)); } // A capsule can be defined with a from-to pair, which // complicates the code a little. private SiteController AddRobotCapsuleSite(KineticHierarchyController parent, XmlNode site_xml, XmlUtils.Defaults defaults) { string capsule_name = XmlUtils.GetString(site_xml, "name", null); if (XmlUtils.HasAttribute(site_xml, "fromto")) { string from_to = XmlUtils.GetStringWithDefaults(site_xml, defaults, "fromto", "0 0 0 0 0 0"); string[] from_to_split = from_to.Split(new char[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries); if (from_to_split.Length != 6) { Logger.Error("RobotLoader::AddRobotCapsuleGeom::Malformed fromto: {0}", from_to); return null; } Vector3 from_position = new Vector3(float.Parse(from_to_split[0]), float.Parse(from_to_split[1]), float.Parse(from_to_split[2])); Vector3 to_position = new Vector3(float.Parse(from_to_split[3]), float.Parse(from_to_split[4]), float.Parse(from_to_split[5])); Vector3 center = (from_position + to_position) / 2.0f; float half_length = (to_position - from_position).magnitude / 2.0f; Quaternion rotation = Quaternion.LookRotation(to_position - from_position); return SiteController.CreateCapsule( parent, capsule_name, center, rotation, half_length, XmlUtils.GetFloat(site_xml, "size", 1.0f)); } else { Vector2 mujoco_size = XmlUtils.GetVector2WithDefaults(site_xml, defaults, "size", Vector2.one); return SiteController.CreateCapsule( parent, capsule_name, XmlUtils.GetVector3WithDefaults(site_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(site_xml, defaults, Quaternion.identity), mujoco_size.y, mujoco_size.x); } } private bool AddRobotGeom(KineticHierarchyController parent, XmlNode geom_xml, XmlUtils.Defaults defaults) { defaults = defaults.Resolve(XmlUtils.GetString(geom_xml, "class", null)).GetSubclass("geom"); GeomController geom = null; string mesh_name = XmlUtils.GetStringWithDefaults(geom_xml, defaults, "mesh", null); string type = XmlUtils.GetStringWithDefaults(geom_xml, defaults, "type", null); if (mesh_name != null) { geom = AddRobotMeshGeom(parent, geom_xml, defaults, mesh_name); } else if ("box".Equals(type)) { geom = AddRobotBoxGeom(parent, geom_xml, defaults); } else if ("plane".Equals(type)) { geom = AddRobotPlaneGeom(parent, geom_xml, defaults); } else if (type == null || "sphere".Equals(type)) { geom = AddRobotSphereGeom(parent, geom_xml, defaults); } else if ("cylinder".Equals(type)) { geom = AddRobotCylinderGeom(parent, geom_xml, defaults); } else if ("capsule".Equals(type)) { geom = AddRobotCapsuleGeom(parent, geom_xml, defaults); } else { Logger.Error("RobotLoader::AddRobotGeom::Unsupported geom type: {0} in {1}.", type, geom_xml.OuterXml); return false; } if (geom == null) { Logger.Error("RobotLoader::AddRobotGeom::Cannot instantiate geom."); return false; } // Set the geom category for semantic segmentation UpdateGeomCategory(geom, geom_xml); // Find the material in the preloaded assets. ResolveMaterial(geom_xml, defaults, geom); return true; } private void UpdateGeomCategory(GeomController geom, XmlNode geom_xml) { string geom_category = GeomController.GetGeomCategoryFromXml(geom_xml); if (geom_category != null) { // the background has category id = 0, hence the +1 geom.category_id_ = geom_categories_.IndexOf(geom_category) + 1; geom.SetCategoryRendererProperties(); } } private GeomController AddRobotBoxGeom(KineticHierarchyController parent, XmlNode geom_xml, XmlUtils.Defaults defaults) { return GeomController.CreateBox( parent, XmlUtils.GetString(geom_xml, "name", null), XmlUtils.GetVector3WithDefaults(geom_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(geom_xml, defaults, Quaternion.identity), 2 * XmlUtils.GetVector3WithDefaults(geom_xml, defaults, "size", Vector3.one)); } private GeomController AddRobotSphereGeom(KineticHierarchyController parent, XmlNode geom_xml, XmlUtils.Defaults defaults) { return GeomController.CreateSphere( parent, XmlUtils.GetString(geom_xml, "name", null), XmlUtils.GetVector3WithDefaults(geom_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(geom_xml, defaults, Quaternion.identity), 2 * XmlUtils.GetVector3WithDefaults(geom_xml, defaults, "size", Vector3.one)); } private GeomController AddRobotPlaneGeom(KineticHierarchyController parent, XmlNode geom_xml, XmlUtils.Defaults defaults) { Vector3 mujoco_size = XmlUtils.GetVector3WithDefaults(geom_xml, defaults, "size", Vector3.one); return GeomController.CreatePlane( parent, XmlUtils.GetString(geom_xml, "name", null), XmlUtils.GetVector3WithDefaults(geom_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(geom_xml, defaults, Quaternion.identity) * Quaternion.Euler(90.0f, 0.0f, 0.0f), 2 * new Vector3(mujoco_size.x / 10.0f, 0.0f, mujoco_size.y / 10.0f)); } private GeomController AddRobotCylinderGeom(KineticHierarchyController parent, XmlNode geom_xml, XmlUtils.Defaults defaults) { Vector2 mujoco_size = XmlUtils.GetVector2WithDefaults(geom_xml, defaults, "size", Vector2.one); return GeomController.CreateCylinder( parent, XmlUtils.GetString(geom_xml, "name", null), XmlUtils.GetVector3WithDefaults(geom_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(geom_xml, defaults, Quaternion.identity), 2 * new Vector3(mujoco_size.x, mujoco_size.y / 2.0f, mujoco_size.x)); } // A capsule can be defined with a from-to pair, which complicates // the code a little. private GeomController AddRobotCapsuleGeom(KineticHierarchyController parent, XmlNode geom_xml, XmlUtils.Defaults defaults) { string capsule_name = XmlUtils.GetString(geom_xml, "name", null); if (XmlUtils.HasAttribute(geom_xml, "fromto")) { string from_to = XmlUtils.GetStringWithDefaults(geom_xml, defaults, "fromto", "0 0 0 0 0 0"); string[] from_to_split = from_to.Split(new char[] { ' ' }, System.StringSplitOptions.RemoveEmptyEntries); if (from_to_split.Length != 6) { Logger.Error("RobotLoader::AddRobotCapsuleGeom::Malformed fromto: {0}", from_to); return null; } Vector3 from_position = new Vector3(float.Parse(from_to_split[0]), float.Parse(from_to_split[1]), float.Parse(from_to_split[2])); Vector3 to_position = new Vector3(float.Parse(from_to_split[3]), float.Parse(from_to_split[4]), float.Parse(from_to_split[5])); Vector3 center = (from_position + to_position) / 2.0f; float half_length = (to_position - from_position).magnitude / 2.0f; Quaternion rotation = Quaternion.LookRotation(to_position - from_position); return GeomController.CreateCapsule( parent, capsule_name, center, rotation, half_length, XmlUtils.GetFloat(geom_xml, "size", 1.0f)); } else { Vector2 mujoco_size = XmlUtils.GetVector2WithDefaults(geom_xml, defaults, "size", Vector2.one); return GeomController.CreateCapsule( parent, capsule_name, XmlUtils.GetVector3WithDefaults(geom_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(geom_xml, defaults, Quaternion.identity), mujoco_size.y, mujoco_size.x); } } private GeomController AddRobotMeshGeom(KineticHierarchyController parent, XmlNode geom_xml, XmlUtils.Defaults defaults, string mesh_name) { GeomController mesh_prefab = FindGeomPrefab(mesh_name); if (mesh_prefab == null) { Logger.Error("RobotLoader::AddRobotMeshGeom::Cannot find mesh prefab for: {0}", mesh_name); return null; } GeomController mesh_geom = Instantiate<GeomController>(mesh_prefab, parent.transform); mesh_geom.Initialize(parent, mesh_name, XmlUtils.GetVector3WithDefaults(geom_xml, defaults, "pos", Vector3.zero), XmlUtils.GetRotationWithDefaults(geom_xml, defaults, Quaternion.identity)); return mesh_geom; } // Find the preloaded material and override with geom colors // (if defined). private void ResolveMaterial(XmlNode node_xml, XmlUtils.Defaults defaults, KineticHierarchyController mesh) { MeshRenderer[] renderers = mesh.GetComponentsInChildren<MeshRenderer>(); string material_name = XmlUtils.GetStringWithDefaults(node_xml, defaults, "material", ""); if (material_name != null) { if (materials_.ContainsKey(material_name)) { foreach (MeshRenderer mesh_renderer in renderers) { mesh_renderer.material = materials_[material_name]; } } } if (XmlUtils.HasAttributeWithDefaults(node_xml, defaults, "rgba")) { Color color = XmlUtils.GetColorWithDefaults(node_xml, defaults, "rgba", Color.white); foreach (MeshRenderer mesh_renderer in renderers) { if (mesh_renderer.material.HasProperty("_Color")) { mesh_renderer.material.color = color; } } } } // When a geom references a mesh, it needs to be retreived // and cloned from the preloaded assets. private GeomController FindGeomPrefab(string prefab_name) { return FindGeomPrefabInPrefabSet(assembly_parts_.transform, prefab_name); } // Travel down preloaded assets hierarchy to find the prefab mesh. private GeomController FindGeomPrefabInPrefabSet(Transform prefab_set, string part_name) { string[] split_name = part_name.Split(new char[] { ':' }, 2); if (split_name.Length == 2) { Transform prefab_sub_set = SceneUtils.Find<Transform>(prefab_set, split_name[0], null); if (prefab_sub_set != null) { return FindGeomPrefabInPrefabSet(prefab_sub_set, split_name[1]); } else { Logger.Warning( "RobotLoader::FindAssemblyPartPrefabInPrefabSet::Cannot find: {0} subset, trying default.", split_name[0]); part_name = split_name[1]; } } GeomController geom_prefab = SceneUtils.Find<GeomController>(prefab_set, part_name, null); if (geom_prefab != null) { return geom_prefab; } Logger.Warning("RobotLoader::FindAssemblyPartPrefabInPrefabSet::Cannot find assembly part: {0}", part_name); return null; } void OnDrawGizmos() { Gizmos.color = Color.red; Gizmos.DrawWireCube(transform.position, new Vector3(0.3f, 0.0f, 0.3f)); } }