Exemple #1
0
    // import renderable objects
    private unsafe void ImportObjects(int nobject)
    {
        string cwd = Path.GetDirectoryName(modelFile);

        // make primitives
        PrimitiveType[] ptypes =
        {
            PrimitiveType.Plane,
            PrimitiveType.Sphere,
            PrimitiveType.Cylinder,
            PrimitiveType.Cube
        };
        GameObject[] primitives = new GameObject[4];
        for (int i = 0; i < 4; i++)
        {
            primitives[i] = GameObject.CreatePrimitive(ptypes[i]);
        }

        // allocate array
        objects = new GameObject[nobject];

        // process objects
        for (int i = 0; i < nobject; i++)
        {
            // get object name
            StringBuilder name = new StringBuilder(100);
            MJP.GetObjectName(i, name, 100);

            // create new GameObject, place under root
            objects[i] = new GameObject(name.ToString());
            MeshFilter     filt   = objects[i].AddComponent <MeshFilter>();
            MeshRenderer   rend   = objects[i].AddComponent <MeshRenderer>();
            InstancedColor colors = objects[i].AddComponent <InstancedColor>();

            objects[i].transform.parent = root.transform;
            // get MuJoCo object descriptor
            MJP.TObject obj;
            MJP.GetObject(i, &obj);

            // For Furniture Assembly Environment: do not visualize site
            if (obj.category == (int)MJP.TCategory.SITE && !objects[i].name.Contains("conn"))
            {
                objects[i].layer = 9;
            }
            if (objects[i].name.StartsWith("noviz", StringComparison.Ordinal))
            {
                objects[i].layer = 10;
            }
            if (objects[i].name.StartsWith("floor", StringComparison.Ordinal))
            {
                objects[i].layer = 10;
            }

            // set mesh
            switch ((MJP.TGeom)obj.geomtype)
            {
            case MJP.TGeom.PLANE:
                filt.sharedMesh = primitives[0].GetComponent <MeshFilter>().sharedMesh;
                break;

            case MJP.TGeom.SPHERE:
                filt.sharedMesh = primitives[1].GetComponent <MeshFilter>().sharedMesh;
                break;

            case MJP.TGeom.CYLINDER:
                filt.sharedMesh = primitives[2].GetComponent <MeshFilter>().sharedMesh;
                break;

            case MJP.TGeom.BOX:
                filt.sharedMesh = primitives[3].GetComponent <MeshFilter>().sharedMesh;
                break;

            case MJP.TGeom.HFIELD:
                int nrow = obj.hfield_nrow;
                int ncol = obj.hfield_ncol;
                int r, c;

                // allocate
                Vector3[] hfvertices = new Vector3[nrow * ncol + 4 * nrow + 4 * ncol];
                Vector2[] hfuv       = new Vector2[nrow * ncol + 4 * nrow + 4 * ncol];
                int[]     hffaces0   = new int[3 * 2 * (nrow - 1) * (ncol - 1)];
                int[]     hffaces1   = new int[3 * (4 * (nrow - 1) + 4 * (ncol - 1))];

                // vertices and uv: surface
                for (r = 0; r < nrow; r++)
                {
                    for (c = 0; c < ncol; c++)
                    {
                        int   k  = r * ncol + c;
                        float wc = c / (float)(ncol - 1);
                        float wr = r / (float)(nrow - 1);

                        hfvertices[k].Set(-(wc - 0.5f), obj.hfield_data[k], -(wr - 0.5f));
                        hfuv[k].Set(wc, wr);
                    }
                }

                // vertices and uv: front and back
                for (r = 0; r < nrow; r += (nrow - 1))
                {
                    for (c = 0; c < ncol; c++)
                    {
                        int   k  = nrow * ncol + 2 * ((r > 0?ncol:0) + c);
                        float wc = c / (float)(ncol - 1);
                        float wr = r / (float)(nrow - 1);

                        hfvertices[k].Set(-(wc - 0.5f), -0.5f, -(wr - 0.5f));
                        hfuv[k].Set(wc, 0);
                        hfvertices[k + 1].Set(-(wc - 0.5f), obj.hfield_data[r * ncol + c], -(wr - 0.5f));
                        hfuv[k + 1].Set(wc, 1);
                    }
                }

                // vertices and uv: left and right
                for (c = 0; c < ncol; c += (ncol - 1))
                {
                    for (r = 0; r < nrow; r++)
                    {
                        int   k  = nrow * ncol + 4 * ncol + 2 * ((c > 0?nrow:0) + r);
                        float wc = c / (float)(ncol - 1);
                        float wr = r / (float)(nrow - 1);

                        hfvertices[k].Set(-(wc - 0.5f), -0.5f, -(wr - 0.5f));
                        hfuv[k].Set(wr, 0);
                        hfvertices[k + 1].Set(-(wc - 0.5f), obj.hfield_data[r * ncol + c], -(wr - 0.5f));
                        hfuv[k + 1].Set(wr, 1);
                    }
                }


                // faces: surface
                for (r = 0; r < nrow - 1; r++)
                {
                    for (c = 0; c < ncol - 1; c++)
                    {
                        int f = r * (ncol - 1) + c;
                        int k = r * ncol + c;

                        // first face in rectangle
                        hffaces0[3 * 2 * f]     = k;
                        hffaces0[3 * 2 * f + 2] = k + 1;
                        hffaces0[3 * 2 * f + 1] = k + ncol + 1;

                        // second face in rectangle
                        hffaces0[3 * 2 * f + 3] = k;
                        hffaces0[3 * 2 * f + 5] = k + ncol + 1;
                        hffaces0[3 * 2 * f + 4] = k + ncol;
                    }
                }

                // faces: front and back
                for (r = 0; r < 2; r++)
                {
                    for (c = 0; c < ncol - 1; c++)
                    {
                        int f = ((r > 0?(ncol - 1):0) + c);
                        int k = nrow * ncol + 2 * ((r > 0?ncol:0) + c);

                        // first face in rectangle
                        hffaces1[3 * 2 * f]     = k;
                        hffaces1[3 * 2 * f + 2] = k + (r > 0 ? 1 : 3);
                        hffaces1[3 * 2 * f + 1] = k + (r > 0 ? 3 : 1);

                        // second face in rectangle
                        hffaces1[3 * 2 * f + 3] = k;
                        hffaces1[3 * 2 * f + 5] = k + (r > 0 ? 3 : 2);
                        hffaces1[3 * 2 * f + 4] = k + (r > 0 ? 2 : 3);
                    }
                }

                // faces: left and right
                for (c = 0; c < 2; c++)
                {
                    for (r = 0; r < nrow - 1; r++)
                    {
                        int f = 2 * (ncol - 1) + ((c > 0?(nrow - 1):0) + r);
                        int k = nrow * ncol + 4 * ncol + 2 * ((c > 0?nrow:0) + r);

                        // first face in rectangle
                        hffaces1[3 * 2 * f]     = k;
                        hffaces1[3 * 2 * f + 2] = k + (c > 0 ? 3 : 1);
                        hffaces1[3 * 2 * f + 1] = k + (c > 0 ? 1 : 3);

                        // second face in rectangle
                        hffaces1[3 * 2 * f + 3] = k;
                        hffaces1[3 * 2 * f + 5] = k + (c > 0 ? 2 : 3);
                        hffaces1[3 * 2 * f + 4] = k + (c > 0 ? 3 : 2);
                    }
                }

                Debug.Log(ncol);
                Debug.Log(nrow);
                Debug.Log(Mathf.Min(hffaces1));
                Debug.Log(Mathf.Max(hffaces1));

                // create mesh with automatic normals and tangents
                filt.sharedMesh              = new Mesh();
                filt.sharedMesh.vertices     = hfvertices;
                filt.sharedMesh.uv           = hfuv;
                filt.sharedMesh.subMeshCount = 2;
                filt.sharedMesh.SetTriangles(hffaces0, 0);
                filt.sharedMesh.SetTriangles(hffaces1, 1);
                filt.sharedMesh.RecalculateNormals();
                filt.sharedMesh.RecalculateTangents();

                // set name
                StringBuilder hname = new StringBuilder(100);
                MJP.GetElementName(MJP.TElement.HFIELD, obj.dataid, hname, 100);
                filt.sharedMesh.name = hname.ToString();
                break;

            case MJP.TGeom.CAPSULE:
            case MJP.TGeom.MESH:
                // reuse shared mesh from earlier object
                if (obj.mesh_shared >= 0)
                {
                    filt.sharedMesh = objects[obj.mesh_shared].GetComponent <MeshFilter>().sharedMesh;
                }

                // create new mesh
                else
                {
                    string meshName;
                    // set name
                    if ((MJP.TGeom)obj.geomtype == MJP.TGeom.CAPSULE)
                    {
                        meshName = "Capsule mesh";
                    }
                    else
                    {
                        StringBuilder mname = new StringBuilder(100);
                        MJP.GetElementName(MJP.TElement.MESH, obj.dataid, mname, 100);
                        meshName = mname.ToString();
                    }

                    {
                        // copy vertices, normals, uv
                        Vector3[] vertices = new Vector3[obj.mesh_nvertex];
                        Vector3[] normals  = new Vector3[obj.mesh_nvertex];
                        Vector2[] uv       = new Vector2[obj.mesh_nvertex];
                        for (int k = 0; k < obj.mesh_nvertex; k++)
                        {
                            vertices[k].Set(-obj.mesh_position[3 * k],
                                            obj.mesh_position[3 * k + 2],
                                            -obj.mesh_position[3 * k + 1]);

                            normals[k].Set(-obj.mesh_normal[3 * k],
                                           obj.mesh_normal[3 * k + 2],
                                           -obj.mesh_normal[3 * k + 1]);

                            uv[k].Set(obj.mesh_texcoord[2 * k],
                                      obj.mesh_texcoord[2 * k + 1]);
                        }

                        // copy faces
                        int[] faces = new int[3 * obj.mesh_nface];
                        for (int k = 0; k < obj.mesh_nface; k++)
                        {
                            faces[3 * k]     = obj.mesh_face[3 * k];
                            faces[3 * k + 1] = obj.mesh_face[3 * k + 2];
                            faces[3 * k + 2] = obj.mesh_face[3 * k + 1];
                        }

                        // number of verices can be modified by uncompressed mesh
                        int nvert = obj.mesh_nvertex;


                        // replace with uncompressed mesh when UV needs to be recomputed
                        // ( recomputeUV && (MJP.TGeom)obj.geomtype==MJP.TGeom.MESH )
                        {
                            // make temporary mesh
                            Mesh temp = new Mesh();
                            temp.vertices  = vertices;
                            temp.normals   = normals;
                            temp.triangles = faces;

                            // generate uncompressed UV unwrapping

                            /* Vector2[] UV = Unwrapping.GeneratePerTriangleUV(temp);
                             * int N = UV.GetLength(0)/3;
                             * if( N!=obj.mesh_nface )
                             *   throw new System.Exception("Unexpected number of faces");
                             * nvert = 3*N;*/
                            int N = obj.mesh_nface;
                            nvert = 3 * N;
                            // create corresponding uncompressed vertices, normals, faces
                            Vector3[] Vertex = new Vector3[3 * N];
                            Vector3[] Normal = new Vector3[3 * N];
                            int[]     Face   = new int[3 * N];
                            for (int k = 0; k < N; k++)
                            {
                                Vertex[3 * k]     = vertices[faces[3 * k]];
                                Vertex[3 * k + 1] = vertices[faces[3 * k + 1]];
                                Vertex[3 * k + 2] = vertices[faces[3 * k + 2]];

                                Normal[3 * k]     = normals[faces[3 * k]];
                                Normal[3 * k + 1] = normals[faces[3 * k + 1]];
                                Normal[3 * k + 2] = normals[faces[3 * k + 2]];

                                Face[3 * k]     = 3 * k;
                                Face[3 * k + 1] = 3 * k + 1;
                                Face[3 * k + 2] = 3 * k + 2;
                            }

                            // create uncompressed mesh
                            filt.sharedMesh             = new Mesh();
                            filt.sharedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
                            filt.sharedMesh.vertices    = Vertex;
                            filt.sharedMesh.normals     = Normal;
                            filt.sharedMesh.triangles   = Face;
                            filt.sharedMesh.name        = meshName;

                            // filt.sharedMesh.uv = UV;
                        }

                        // otherwise create mesh directly

                        /*   else
                         * {
                         *     filt.sharedMesh = new Mesh();
                         *     filt.sharedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
                         *     filt.sharedMesh.vertices = vertices;
                         *     filt.sharedMesh.normals = normals;
                         *     filt.sharedMesh.triangles = faces;
                         *     filt.sharedMesh.uv = uv;
                         *     filt.sharedMesh.name = meshName;
                         *     filt.sharedMesh.RecalculateNormals(30);
                         * }*/

                        // optionally recompute normals for meshes

                        /*   if (recomputeNormal && (MJP.TGeom)obj.geomtype == MJP.TGeom.MESH)
                         *     filt.sharedMesh.RecalculateNormals(60);*/

                        filt.sharedMesh.RecalculateNormals(25);
                        // always calculate tangents (MuJoCo does not support tangents)
                        filt.sharedMesh.RecalculateTangents();
                    }

                    // print error if number of vertices or faces is over 65535

                    /* if( obj.mesh_nface>65535 || nvert>65535 )
                     *   Debug.LogError("MESH TOO BIG: " + filt.sharedMesh.name +
                     *                  ", vertices " + nvert + ", faces " + obj.mesh_nface);*/
                }
                break;
            }

            //TODO: Set segmentation color with Material.SetColor("_SegColor")
            // existing material

            byte segmentation = 0;
            // Try to get segmentation id. If nothing is found, the object will remain background (0)
            if (meshSegmentMap.TryGetValue(name.ToString(), out segmentation) == false)
            {
                meshSegmentMap.TryGetValue(filt.sharedMesh.name, out segmentation);
            }
            Material base_material = null;

            if (materials.TryGetValue(obj.material, out base_material))
            {
                rend.sharedMaterial = base_material;
                colors.Diffuse      = new Color(obj.color[0], obj.color[1], obj.color[2], obj.color[3]);
                colors.Segmentation = new Color32(segmentation, segmentation, segmentation, segmentation);
            }
            else // Missing material (shouldn't be possible?)
            {
                Debug.Log("Couldn't find a Material for id:" + obj.material);
            }


            // get MuJoCo object transform and set in Unity
            MJP.TTransform transform;
            int            visible;
            int            selected;
            MJP.GetObjectState(i, &transform, &visible, &selected);
            SetTransform(objects[i], transform);
        }

        // delete primitives
        for (int i = 0; i < 4; i++)
        {
            Destroy(primitives[i]);
        }
    }
Exemple #2
0
    // import renderable objects
    private unsafe void ImportObjects(int nobject)
    {
        // make primitives
        PrimitiveType[] ptypes =
        {
            PrimitiveType.Plane,
            PrimitiveType.Sphere,
            PrimitiveType.Cylinder,
            PrimitiveType.Cube
        };
        GameObject[] primitives = new GameObject[4];
        for (int i = 0; i < 4; i++)
        {
            primitives[i] = GameObject.CreatePrimitive(ptypes[i]);
        }

        // allocate array
        objects = new GameObject[nobject];

        // process objects
        for (int i = 0; i < nobject; i++)
        {
            // get object name
            StringBuilder name = new StringBuilder(100);
            MJP.GetObjectName(i, name, 100);

            // create new GameObject, place under root
            objects[i] = new GameObject(name.ToString());
            objects[i].AddComponent <MeshFilter>();
            objects[i].AddComponent <MeshRenderer>();
            objects[i].transform.parent = root.transform;

            // get components
            MeshFilter   filt = objects[i].GetComponent <MeshFilter>();
            MeshRenderer rend = objects[i].GetComponent <MeshRenderer>();

            // get MuJoCo object descriptor
            MJP.TObject obj;
            MJP.GetObject(i, &obj);

            // set mesh
            switch ((MJP.TGeom)obj.geomtype)
            {
            case MJP.TGeom.PLANE:
                filt.sharedMesh = primitives[0].GetComponent <MeshFilter>().sharedMesh;
                break;

            case MJP.TGeom.SPHERE:
                filt.sharedMesh = primitives[1].GetComponent <MeshFilter>().sharedMesh;
                break;

            case MJP.TGeom.CYLINDER:
                filt.sharedMesh = primitives[2].GetComponent <MeshFilter>().sharedMesh;
                break;

            case MJP.TGeom.BOX:
                filt.sharedMesh = primitives[3].GetComponent <MeshFilter>().sharedMesh;
                break;

            case MJP.TGeom.HFIELD:
                int nrow = obj.hfield_nrow;
                int ncol = obj.hfield_ncol;
                int r, c;

                // allocate
                Vector3[] hfvertices = new Vector3[nrow * ncol + 4 * nrow + 4 * ncol];
                Vector2[] hfuv       = new Vector2[nrow * ncol + 4 * nrow + 4 * ncol];
                int[]     hffaces0   = new int[3 * 2 * (nrow - 1) * (ncol - 1)];
                int[]     hffaces1   = new int[3 * (4 * (nrow - 1) + 4 * (ncol - 1))];

                // vertices and uv: surface
                for (r = 0; r < nrow; r++)
                {
                    for (c = 0; c < ncol; c++)
                    {
                        int   k  = r * ncol + c;
                        float wc = c / (float)(ncol - 1);
                        float wr = r / (float)(nrow - 1);

                        hfvertices[k].Set(-(wc - 0.5f), obj.hfield_data[k], -(wr - 0.5f));
                        hfuv[k].Set(wc, wr);
                    }
                }

                // vertices and uv: front and back
                for (r = 0; r < nrow; r += (nrow - 1))
                {
                    for (c = 0; c < ncol; c++)
                    {
                        int   k  = nrow * ncol + 2 * ((r > 0?ncol:0) + c);
                        float wc = c / (float)(ncol - 1);
                        float wr = r / (float)(nrow - 1);

                        hfvertices[k].Set(-(wc - 0.5f), -0.5f, -(wr - 0.5f));
                        hfuv[k].Set(wc, 0);
                        hfvertices[k + 1].Set(-(wc - 0.5f), obj.hfield_data[r * ncol + c], -(wr - 0.5f));
                        hfuv[k + 1].Set(wc, 1);
                    }
                }

                // vertices and uv: left and right
                for (c = 0; c < ncol; c += (ncol - 1))
                {
                    for (r = 0; r < nrow; r++)
                    {
                        int   k  = nrow * ncol + 4 * ncol + 2 * ((c > 0?nrow:0) + r);
                        float wc = c / (float)(ncol - 1);
                        float wr = r / (float)(nrow - 1);

                        hfvertices[k].Set(-(wc - 0.5f), -0.5f, -(wr - 0.5f));
                        hfuv[k].Set(wr, 0);
                        hfvertices[k + 1].Set(-(wc - 0.5f), obj.hfield_data[r * ncol + c], -(wr - 0.5f));
                        hfuv[k + 1].Set(wr, 1);
                    }
                }


                // faces: surface
                for (r = 0; r < nrow - 1; r++)
                {
                    for (c = 0; c < ncol - 1; c++)
                    {
                        int f = r * (ncol - 1) + c;
                        int k = r * ncol + c;

                        // first face in rectangle
                        hffaces0[3 * 2 * f]     = k;
                        hffaces0[3 * 2 * f + 2] = k + 1;
                        hffaces0[3 * 2 * f + 1] = k + ncol + 1;

                        // second face in rectangle
                        hffaces0[3 * 2 * f + 3] = k;
                        hffaces0[3 * 2 * f + 5] = k + ncol + 1;
                        hffaces0[3 * 2 * f + 4] = k + ncol;
                    }
                }

                // faces: front and back
                for (r = 0; r < 2; r++)
                {
                    for (c = 0; c < ncol - 1; c++)
                    {
                        int f = ((r > 0?(ncol - 1):0) + c);
                        int k = nrow * ncol + 2 * ((r > 0?ncol:0) + c);

                        // first face in rectangle
                        hffaces1[3 * 2 * f]     = k;
                        hffaces1[3 * 2 * f + 2] = k + (r > 0 ? 1 : 3);
                        hffaces1[3 * 2 * f + 1] = k + (r > 0 ? 3 : 1);

                        // second face in rectangle
                        hffaces1[3 * 2 * f + 3] = k;
                        hffaces1[3 * 2 * f + 5] = k + (r > 0 ? 3 : 2);
                        hffaces1[3 * 2 * f + 4] = k + (r > 0 ? 2 : 3);
                    }
                }

                // faces: left and right
                for (c = 0; c < 2; c++)
                {
                    for (r = 0; r < nrow - 1; r++)
                    {
                        int f = 2 * (ncol - 1) + ((c > 0?(nrow - 1):0) + r);
                        int k = nrow * ncol + 4 * ncol + 2 * ((c > 0?nrow:0) + r);

                        // first face in rectangle
                        hffaces1[3 * 2 * f]     = k;
                        hffaces1[3 * 2 * f + 2] = k + (c > 0 ? 3 : 1);
                        hffaces1[3 * 2 * f + 1] = k + (c > 0 ? 1 : 3);

                        // second face in rectangle
                        hffaces1[3 * 2 * f + 3] = k;
                        hffaces1[3 * 2 * f + 5] = k + (c > 0 ? 2 : 3);
                        hffaces1[3 * 2 * f + 4] = k + (c > 0 ? 3 : 2);
                    }
                }

                Debug.Log(ncol);
                Debug.Log(nrow);
                Debug.Log(Mathf.Min(hffaces1));
                Debug.Log(Mathf.Max(hffaces1));

                // create mesh with automatic normals and tangents
                filt.sharedMesh              = new Mesh();
                filt.sharedMesh.vertices     = hfvertices;
                filt.sharedMesh.uv           = hfuv;
                filt.sharedMesh.subMeshCount = 2;
                filt.sharedMesh.SetTriangles(hffaces0, 0);
                filt.sharedMesh.SetTriangles(hffaces1, 1);
                filt.sharedMesh.RecalculateNormals();
                filt.sharedMesh.RecalculateTangents();

                // set name
                StringBuilder hname = new StringBuilder(100);
                MJP.GetElementName(MJP.TElement.HFIELD, obj.dataid, hname, 100);
                filt.sharedMesh.name = hname.ToString();
                break;

            case MJP.TGeom.CAPSULE:
            case MJP.TGeom.MESH:
                // reuse shared mesh from earlier object
                if (obj.mesh_shared >= 0)
                {
                    filt.sharedMesh = objects[obj.mesh_shared].GetComponent <MeshFilter>().sharedMesh;
                }

                // create new mesh
                else
                {
                    // copy vertices, normals, uv
                    Vector3[] vertices = new Vector3[obj.mesh_nvertex];
                    Vector3[] normals  = new Vector3[obj.mesh_nvertex];
                    Vector2[] uv       = new Vector2[obj.mesh_nvertex];
                    for (int k = 0; k < obj.mesh_nvertex; k++)
                    {
                        vertices[k].Set(-obj.mesh_position[3 * k],
                                        obj.mesh_position[3 * k + 2],
                                        -obj.mesh_position[3 * k + 1]);

                        normals[k].Set(-obj.mesh_normal[3 * k],
                                       obj.mesh_normal[3 * k + 2],
                                       -obj.mesh_normal[3 * k + 1]);

                        uv[k].Set(obj.mesh_texcoord[2 * k],
                                  obj.mesh_texcoord[2 * k + 1]);
                    }

                    // copy faces
                    int[] faces = new int[3 * obj.mesh_nface];
                    for (int k = 0; k < obj.mesh_nface; k++)
                    {
                        faces[3 * k]     = obj.mesh_face[3 * k];
                        faces[3 * k + 1] = obj.mesh_face[3 * k + 2];
                        faces[3 * k + 2] = obj.mesh_face[3 * k + 1];
                    }

                    // number of verices can be modified by uncompressed mesh
                    int nvert = obj.mesh_nvertex;

                    // replace with uncompressed mesh when UV needs to be recomputed
                    if (recomputeUV && (MJP.TGeom)obj.geomtype == MJP.TGeom.MESH)
                    {
                        // make temporary mesh
                        Mesh temp = new Mesh();
                        temp.vertices  = vertices;
                        temp.normals   = normals;
                        temp.triangles = faces;

                        // generate uncompressed UV unwrapping
                        Vector2[] UV = Unwrapping.GeneratePerTriangleUV(temp);
                        int       N  = UV.GetLength(0) / 3;
                        if (N != obj.mesh_nface)
                        {
                            throw new System.Exception("Unexpected number of faces");
                        }
                        nvert = 3 * N;

                        // create corresponding uncompressed vertices, normals, faces
                        Vector3[] Vertex = new Vector3[3 * N];
                        Vector3[] Normal = new Vector3[3 * N];
                        int[]     Face   = new int[3 * N];
                        for (int k = 0; k < N; k++)
                        {
                            Vertex[3 * k]     = vertices[faces[3 * k]];
                            Vertex[3 * k + 1] = vertices[faces[3 * k + 1]];
                            Vertex[3 * k + 2] = vertices[faces[3 * k + 2]];

                            Normal[3 * k]     = normals[faces[3 * k]];
                            Normal[3 * k + 1] = normals[faces[3 * k + 1]];
                            Normal[3 * k + 2] = normals[faces[3 * k + 2]];

                            Face[3 * k]     = 3 * k;
                            Face[3 * k + 1] = 3 * k + 1;
                            Face[3 * k + 2] = 3 * k + 2;
                        }

                        // create uncompressed mesh
                        filt.sharedMesh           = new Mesh();
                        filt.sharedMesh.vertices  = Vertex;
                        filt.sharedMesh.normals   = Normal;
                        filt.sharedMesh.triangles = Face;
                        filt.sharedMesh.uv        = UV;
                    }

                    // otherwise create mesh directly
                    else
                    {
                        filt.sharedMesh           = new Mesh();
                        filt.sharedMesh.vertices  = vertices;
                        filt.sharedMesh.normals   = normals;
                        filt.sharedMesh.triangles = faces;
                        filt.sharedMesh.uv        = uv;
                    }

                    // optionally recompute normals for meshes
                    if (recomputeNormal && (MJP.TGeom)obj.geomtype == MJP.TGeom.MESH)
                    {
                        filt.sharedMesh.RecalculateNormals();
                    }

                    // always calculate tangents (MuJoCo does not support tangents)
                    filt.sharedMesh.RecalculateTangents();

                    // set name
                    if ((MJP.TGeom)obj.geomtype == MJP.TGeom.CAPSULE)
                    {
                        filt.sharedMesh.name = "Capsule mesh";
                    }
                    else
                    {
                        StringBuilder mname = new StringBuilder(100);
                        MJP.GetElementName(MJP.TElement.MESH, obj.dataid, mname, 100);
                        filt.sharedMesh.name = mname.ToString();
                    }

                    // print error if number of vertices or faces is over 65535
                    if (obj.mesh_nface > 65535 || nvert > 65535)
                    {
                        Debug.LogError("MESH TOO BIG: " + filt.sharedMesh.name +
                                       ", vertices " + nvert + ", faces " + obj.mesh_nface);
                    }
                }
                break;
            }

            // existing material
            if (obj.material >= 0)
            {
                // not modified
                if (obj.color[0] == 0.5f && obj.color[1] == 0.5f && obj.color[2] == 0.5f && obj.color[3] == 1)
                {
                    rend.sharedMaterial = materials[obj.material];
                }

                // color override
                else
                {
                    rend.sharedMaterial = new Material(materials[obj.material]);
                    AdjustMaterial(rend.sharedMaterial, obj.color[0], obj.color[1], obj.color[2], obj.color[3]);
                }
            }

            // new material
            else
            {
                rend.sharedMaterial = new Material(Shader.Find("Standard"));
                AdjustMaterial(rend.sharedMaterial, obj.color[0], obj.color[1], obj.color[2], obj.color[3]);
            }

            // get MuJoCo object transform and set in Unity
            MJP.TTransform transform;
            int            visible;
            int            selected;
            MJP.GetObjectState(i, &transform, &visible, &selected);
            SetTransform(objects[i], transform);
        }

        // delete primitives
        for (int i = 0; i < 4; i++)
        {
            DestroyImmediate(primitives[i]);
        }

        AssetDatabase.Refresh();
    }