예제 #1
0
//
// Rhino Utilities
//

        public static RhinoNamespace.Geometry.Mesh ToRhinoMesh(double[,] vertices, int[,] faces)
        {
            Mesh mesh = new RhinoNamespace.Geometry.Mesh();

            for (int i = 0; i < vertices.GetLength(0); i++)
            {
                mesh.Vertices.Add(vertices[i, 0], vertices[i, 1], vertices[i, 2]);
            }

            if (faces.GetLength(1) == 3)
            {
                for (int i = 0; i < faces.GetLength(0); i++)
                {
                    mesh.Faces.AddFace(faces[i, 0], faces[i, 1], faces[i, 2]);
                }
            }
            else // (faces.GetLength(1) == 4)
            {
                for (int i = 0; i < faces.GetLength(0); i++)
                {
                    mesh.Faces.AddFace(faces[i, 0], faces[i, 1], faces[i, 2], faces[i, 3]);
                }
            }

            mesh.Normals.ComputeNormals();
            mesh.Compact();

            return(mesh);
        }
예제 #2
0
    /////////////////////////////////////////////////////
    public static Mesh PointsToMesh(List <Point3d> pts1, List <Point3d> pts2, int nx, ref DataTree <LineCurve> listUCurves, ref DataTree <LineCurve> listVCurves)
    {
        int ny = pts1.Count;

        Rhino.Geometry.Mesh mesh = new Rhino.Geometry.Mesh();

        for (int iy = 0; iy < ny; iy++)
        {
            for (int ix = 0; ix < nx; ix++)
            {
                mesh.Vertices.Add(new Point3d(pts1[iy] + (pts2[iy] - pts1[iy]) * (double)ix / (double)(nx - 1)));
            }
        }
        int i0, i1, i2, i3;

        for (int ix = 0; ix < (nx - 1); ix++)
        {
            for (int iy = 0; iy < (ny - 1); iy++)
            {
                i0 = ix + iy * nx;
                i1 = (ix + 1) + iy * nx;
                i2 = (ix + 1) + (iy + 1) * nx;
                i3 = ix + (iy + 1) * nx;
                mesh.Faces.AddFace(i0, i1, i2, i3);
                listUCurves.Add(new LineCurve(mesh.Vertices[i0], mesh.Vertices[i1]), new GH_Path(iy));
                listVCurves.Add(new LineCurve(mesh.Vertices[i1], mesh.Vertices[i2]), new GH_Path(iy));
                listUCurves.Add(new LineCurve(mesh.Vertices[i2], mesh.Vertices[i3]), new GH_Path(iy + 1));
                listVCurves.Add(new LineCurve(mesh.Vertices[i3], mesh.Vertices[i0]), new GH_Path(iy));
            }
        }
        mesh.Normals.ComputeNormals();
        mesh.Compact();
        return(mesh);
    }
예제 #3
0
        private DataTree <object> CorrectMesh(
            Dictionary <string, Mesh> meshes,
            Dictionary <string, List <List <double> > > points,
            double distance
            )
        {
            var newMeshes = new DataTree <object>();
            var j         = 0;

            foreach (var key in meshes.Keys)
            {
                if (!points.ContainsKey(key))
                {
                    continue;
                }

                var patchPoints = points[key];
                var ghPoints    = patchPoints.Select(point => new Point3d(point[0], point[1], point[2])).ToList();
                var mesh        = new Mesh();

                // Check mesh normal. If the normal direction is fx Z, check that the points and mesh have the same value. If not throw an error.
                GH_Convert.ToMesh(meshes[key], ref mesh, GH_Conversion.Primary);
                var faceCenters = Enumerable.Range(0, mesh.Faces.Count())
                                  .Select(index => mesh.Faces.GetFaceCenter(index)).ToList();
                var faceIndices = RTree.Point3dClosestPoints(faceCenters, ghPoints, distance);

                var newMesh = new Mesh();
                newMesh.Vertices.AddVertices(mesh.Vertices);
                foreach (var face in faceIndices)
                {
                    if (face.Length > 0)
                    {
                        newMesh.Faces.AddFace(mesh.Faces[face[0]]);
                    }
                }

                newMesh.Normals.ComputeNormals();
                newMesh.UnifyNormals();
                newMesh.Compact();

                var path = new GH_Path(j);
                newMeshes.Add(newMesh, path);
                j++;
            }

            return(newMeshes);
        }
예제 #4
0
        /// <summary>
        /// Convert to Rhino mesh type.
        /// Recursively triangulates until only tri-/quad-faces remain.
        /// </summary>
        /// <returns>A Rhino mesh.</returns>
        public Rhino.Geometry.Mesh ToRhinoMesh()
        {
            Rhino.Geometry.Mesh target = new Rhino.Geometry.Mesh();

            // TODO: duplicate mesh and triangulate
            Mesh source = Duplicate();//.Triangulate();

            for (int i = 0; i < source.Faces.Count; i++)
            {
                if (source.Faces[i].Sides > 3)
                {
                    source.Faces.Triangulate(i, true);
                }
            }

            // Strip down to Face-Vertex structure
            Point3f[]    points      = source.ListVerticesByPoints();
            List <int>[] faceIndices = source.ListFacesByVertexIndices();
            // Add vertices
            for (int i = 0; i < points.Length; i++)
            {
                target.Vertices.Add(points[i]);
            }
            // Add faces
            foreach (List <int> f in faceIndices)
            {
                if (f.Count == 3)
                {
                    target.Faces.AddFace(f[0], f[1], f[2]);
                }
                else if (f.Count == 4)
                {
                    target.Faces.AddFace(f[0], f[1], f[2], f[3]);
                }
            }
            target.Compact();

            return(target);
        }
예제 #5
0
        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            Surface S = null;

            if (!DA.GetData(0, ref S))
            {
                return;
            }

            Point3d P = Point3d.Unset;

            if (!DA.GetData(1, ref P))
            {
                P = S.PointAt(S.Domain(0).Mid, S.Domain(1).Mid);
            }

            double R = Rhino.RhinoMath.UnsetValue;

            if (!DA.GetData(2, ref R))
            {
                return;
            }

            double A = Rhino.RhinoMath.UnsetValue;

            if (!DA.GetData(3, ref A))
            {
                return;
            }

            int max = 0;

            if (!DA.GetData(4, ref max))
            {
                return;
            }

            Boolean extend = false;

            if (!DA.GetData(5, ref extend))
            {
                return;
            }

            if (R <= 0)
            {
                this.AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Mesh edge length must be a positive, non-zero number.");
                return;
            }

            // Extend surface beyond boundaries to get a better coverage from the net
            if (extend)
            {
                S = S.Extend(IsoStatus.North, R, true);
                S = S.Extend(IsoStatus.East, R, true);
                S = S.Extend(IsoStatus.South, R, true);
                S = S.Extend(IsoStatus.West, R, true);
            }


            // starting point
            double u0, v0;

            S.ClosestPoint(P, out u0, out v0);
            // get two (four) orthogonal directions (in plane of surface at starting point)
            Plane plane = new Plane(S.PointAt(u0, v0), S.NormalAt(u0, v0));

            plane.Rotate(Rhino.RhinoMath.ToRadians(A), S.NormalAt(u0, v0));
            Vector3d[] dir = new Vector3d[] {
                plane.XAxis *  R,
                plane.YAxis *  R,
                plane.XAxis * -R,
                       plane.YAxis * -R
            };


            // for each direction, walk out (and store list of points)
            double u, v;

            List <Point3d>[] axis = new List <Point3d> [4];
            for (int i = 0; i < 4; i++)
            {
                // set u and v to starting point
                u = u0;
                v = v0;
                List <Point3d> pts = new List <Point3d>();
                for (int j = 0; j < max + 1; j++)
                {
                    // get point and normal for uv
                    Point3d  pt = S.PointAt(u, v);
                    Vector3d n  = S.NormalAt(u, v);
                    n *= R;
                    // add point to list
                    pts.Add(pt);
                    // create forward facing arc and find intersection point with surface (as uv)
                    Arc arc = new Arc(pt + n, pt + dir[i], pt - n);
                    CurveIntersections isct =
                        Intersection.CurveSurface(arc.ToNurbsCurve(), S, 0.01, 0.01);
                    if (isct.Count > 0)
                    {
                        isct[0].SurfacePointParameter(out u, out v);
                    }
                    else
                    {
                        break;
                    }
                    // adjust direction vector (new position - old position)
                    dir[i] = S.PointAt(u, v) - pt;
                }
                axis[i] = pts;
            }


            // now that we have the axes, start to build up the mesh quads in between
            GH_PreviewUtil preview = new GH_PreviewUtil(GetValue("Animate", false));

            Rhino.Geometry.Mesh mesh = new Rhino.Geometry.Mesh(); // target mesh
            for (int k = 0; k < 4; k++)
            {
                int k0      = (k + 1) % 4;
                int padding = 10;
                Rhino.Geometry.Mesh qmesh = new Rhino.Geometry.Mesh();                            // local mesh for quadrant
                Point3d[,] quad = new Point3d[axis[k].Count + padding, axis[k0].Count + padding]; // 2d array of points
                int[,] qindex   = new int[axis[k].Count + padding, axis[k0].Count + padding];     // 2d array of points' indices in local mesh
                int count = 0;
                for (int i = 0; i < axis[k0].Count; i++)
                {
                    // add axis vertex to mesh and store point and index in corresponding 2d arrays
                    quad[0, i] = axis[k0][i];
                    qmesh.Vertices.Add(axis[k0][i]);
                    qindex[0, i] = count++;
                }

                for (int i = 1; i < quad.GetLength(0); i++)
                {
                    if (i < axis[k].Count)
                    {
                        // add axis vertex
                        quad[i, 0] = axis[k][i];
                        qmesh.Vertices.Add(axis[k][i]);
                        qindex[i, 0] = count++;
                    }
                    // for each column attempt to locate a new vertex in the grid
                    for (int j = 1; j < quad.GetLength(1); j++)
                    {
                        // if quad[i - 1, j] doesn't exist, try to add it and continue (or else break the current row)
                        if (quad[i - 1, j] == new Point3d())
                        {
                            if (j < 2)
                            {
                                break;
                            }
                            CurveIntersections isct = this.ArcIntersect(S, quad[i - 1, j - 1], quad[i - 1, j - 2], R);
                            if (isct.Count > 0)
                            {
                                quad[i - 1, j] = isct[0].PointB;
                                qmesh.Vertices.Add(quad[i - 1, j]);
                                qindex[i - 1, j] = count++;
                            }
                            else
                            {
                                break;
                            }
                        }
                        // if quad[i, j - 1] doesn't exist, try to create quad[i, j] by projection and skip mesh face creation
                        if (quad[i, j - 1] == new Point3d())
                        {
                            if (i < 2)
                            {
                                break;
                            }
                            CurveIntersections isct = this.ArcIntersect(S, quad[i - 1, j], quad[i - 2, j], R);
                            if (isct.Count > 0)
                            {
                                quad[i, j] = isct[0].PointB;
                                qmesh.Vertices.Add(quad[i, j]);
                                qindex[i, j] = count++;
                                continue;
                            }
                        }
                        // construct a sphere at each neighbouring vertex ([i,j-1] and [i-1,j]) and intersect
                        Sphere sph1 = new Sphere(quad[i, j - 1], R);
                        Sphere sph2 = new Sphere(quad[i - 1, j], R);
                        Circle cir;
                        if (Intersection.SphereSphere(sph1, sph2, out cir) == SphereSphereIntersection.Circle)
                        {
                            // intersect circle with surface
                            CurveIntersections cin =
                                Intersection.CurveSurface(NurbsCurve.CreateFromCircle(cir), S, 0.01, 0.01);
                            // attempt to find the new vertex (i.e not [i-1,j-1])
                            foreach (IntersectionEvent ie in cin)
                            {
                                if ((ie.PointA - quad[i - 1, j - 1]).Length > 0.2 * R) // compare with a tolerance, rather than exact comparison
                                {
                                    quad[i, j] = ie.PointA;
                                    qmesh.Vertices.Add(quad[i, j]);
                                    qindex[i, j] = count++;
                                    // create quad-face
                                    qmesh.Faces.AddFace(qindex[i, j], qindex[i - 1, j], qindex[i - 1, j - 1], qindex[i, j - 1]);
                                    break;
                                }
                            }
                            if (preview.Enabled)
                            {
                                preview.Clear();
                                preview.AddMesh(mesh);
                                preview.AddMesh(qmesh);
                                preview.Redraw();
                            }
                        }
                    }
                }
                // add local mesh to target
                mesh.Append(qmesh);
            }

            // weld mesh to remove duplicate vertices along axes
            mesh.Weld(Math.PI);
            mesh.Compact();
            mesh.Normals.ComputeNormals();


            DA.SetData(0, mesh);

            preview.Clear();
        }
예제 #6
0
        public static void Load(string filename, RhinoDoc doc)
        {
            var model = Interface.LoadModel(filename);

            if (model != null)
            {
                // dictionary with deserialized buffer data
                var bufferData = new Dictionary <int, byte[]>();

                // accessorData contains a dictionary to the buffer data meant to be accessed by an accessor via a bufferView
                var accessorData = new Dictionary <int, dynamic>();

                // material data contains a dictionary with the material index and the Rhino material ID
                var materialData = new Dictionary <int, int>();

                // mesh data
                var meshData = new Dictionary <int, List <Rhino.Geometry.Mesh> >();

                var nodeXformData = new Dictionary <int, Transform>();

                var dir = Path.GetDirectoryName(filename);

                #region Read Buffers

                for (int i = 0; i < model.Buffers.Length; i++)
                {
                    var data = Interface.LoadBinaryBuffer(model, i, filename);
                    bufferData.Add(i, data);
                }

                #endregion

                #region Process Images

                //go through images in model
                //save them to disk if necessary

                const string EMBEDDEDPNG  = "data:image/png;base64,";
                const string EMBEDDEDJPEG = "data:image/jpeg;base64,";

                var imageData = new Dictionary <int, string>(); //image index, image path

                if (model.Images != null)
                {
                    for (int i = 0; i < model.Images.Length; i++)
                    {
                        var image       = model.Images[i];
                        var name        = image.Name ?? "embeddedImage_" + i;
                        var extension   = string.Empty;
                        var imageStream = Stream.Null;

                        if (image.BufferView.HasValue)
                        {
                            imageStream = Interface.OpenImageFile(model, i, filename);
                            if (image.MimeType.HasValue)
                            {
                                if (image.MimeType == glTFLoader.Schema.Image.MimeTypeEnum.image_jpeg)
                                {
                                    extension = ".jpg";
                                }
                                else if (image.MimeType == glTFLoader.Schema.Image.MimeTypeEnum.image_png)
                                {
                                    extension = ".png";
                                }
                            }

                            var imgPath = Path.Combine(dir, "EmbeddedImages");
                            if (!Directory.Exists(imgPath))
                            {
                                Directory.CreateDirectory(imgPath);
                            }
                            imgPath = Path.Combine(imgPath, name + extension);

                            using (var fileStream = File.Create(imgPath))
                            {
                                imageStream.Seek(0, SeekOrigin.Begin);
                                imageStream.CopyTo(fileStream);
                                imageData.Add(i, imgPath);
                            }
                        }

                        if (image.Uri != null && image.Uri.StartsWith("data:image/"))
                        {
                            if (image.Uri.StartsWith(EMBEDDEDPNG))
                            {
                                extension = ".png";
                            }
                            if (image.Uri.StartsWith(EMBEDDEDJPEG))
                            {
                                extension = ".jpg";
                            }

                            imageStream = Interface.OpenImageFile(model, i, filename);

                            var imgPath = Path.Combine(dir, "EmbeddedImages");
                            if (!Directory.Exists(imgPath))
                            {
                                Directory.CreateDirectory(imgPath);
                            }
                            imgPath = Path.Combine(imgPath, name + extension);

                            using (var fileStream = File.Create(imgPath))
                            {
                                imageStream.Seek(0, SeekOrigin.Begin);
                                imageStream.CopyTo(fileStream);
                                imageData.Add(i, imgPath);
                            }
                        }

                        if (image.Uri != null && File.Exists(Path.Combine(dir, image.Uri)))
                        {
                            imageData.Add(i, Path.Combine(dir, image.Uri));
                        }
                    }
                }

                #endregion

                #region Process Materials

                // TODO: Update for Rhino 7 PBR Materials

                if (model.Materials != null)
                {
                    for (int i = 0; i < model.Materials.Length; i++)
                    {
                        var mat      = model.Materials[i];
                        var rhinoMat = new Rhino.DocObjects.Material();

                        var texId    = -1;
                        int?sourceId = null;

                        if (mat.NormalTexture != null)
                        {
                        }
                        if (mat.OcclusionTexture != null)
                        {
                        }
                        if (mat.EmissiveTexture != null)
                        {
                        }

                        if (mat.PbrMetallicRoughness.BaseColorTexture != null)
                        {
                            texId    = mat.PbrMetallicRoughness.BaseColorTexture.Index;
                            sourceId = model.Textures[texId].Source.Value;
                            rhinoMat.SetBitmapTexture(imageData[sourceId.Value]);
                        }

                        if (mat.PbrMetallicRoughness.MetallicRoughnessTexture != null)
                        {
                            texId    = mat.PbrMetallicRoughness.MetallicRoughnessTexture.Index;
                            sourceId = model.Textures[texId].Source.Value;
                            rhinoMat.SetBumpTexture(imageData[sourceId.Value]);
                        }

                        rhinoMat.Name = mat.Name;

                        var id   = doc.Materials.Add(rhinoMat);
                        var rMat = Rhino.Render.RenderMaterial.CreateBasicMaterial(rhinoMat, doc);
                        doc.RenderMaterials.Add(rMat);

                        materialData.Add(i, id);
                    }
                }

                #endregion

                #region Access Buffers

                for (int i = 0; i < model.Accessors.Length; i++)
                {
                    var accessor = model.Accessors[i];

                    //process, afterwards, check if sparse

                    if (accessor.BufferView != null)
                    {
                        var bufferView = model.BufferViews[accessor.BufferView.Value];

                        var buffer = bufferData[bufferView.Buffer]; //byte[]

                        //calculate byte length
                        var elementBytes = GetTypeMultiplier(accessor.Type) * GetComponentTypeMultiplier(accessor.ComponentType);
                        var stride       = bufferView.ByteStride != null ? bufferView.ByteStride.Value : 0;
                        var strideDiff   = stride > 0 ? stride - elementBytes : 0;
                        var count        = (elementBytes + strideDiff) * accessor.Count;

                        var arr = new byte[count];

                        System.Buffer.BlockCopy(buffer, bufferView.ByteOffset + accessor.ByteOffset, arr, 0, count);

                        var res = AccessBuffer(accessor.Type, accessor.Count, accessor.ComponentType, stride, arr);

                        accessorData.Add(i, res);
                    }

                    // if accessor is sparse, need to modify the data. accessorData index is i

                    if (accessor.Sparse != null)
                    {
                        //TODO
                        //If a BufferView is specified in the accessor, sparse acts a a way to replace  values
                        //if a BufferView does not exist in the accessor, just process the sparse data

                        //access construct data

                        var bufferViewI = model.BufferViews[accessor.Sparse.Indices.BufferView];

                        var bufferI = bufferData[bufferViewI.Buffer]; //byte[]

                        var sparseComponentType = (Accessor.ComponentTypeEnum)accessor.Sparse.Indices.ComponentType;

                        //calculate count
                        var elementBytesI = GetTypeMultiplier(Accessor.TypeEnum.SCALAR) * GetComponentTypeMultiplier(sparseComponentType);
                        var strideI       = bufferViewI.ByteStride != null ? bufferViewI.ByteStride.Value : 0;
                        var strideDiffI   = strideI > 0 ? strideI - elementBytesI : 0;
                        var countI        = (elementBytesI + strideDiffI) * accessor.Sparse.Count;

                        var arrI = new byte[countI];
                        System.Buffer.BlockCopy(bufferI, accessor.Sparse.Indices.ByteOffset + bufferViewI.ByteOffset, arrI, 0, countI);

                        var resIndices = AccessBuffer(Accessor.TypeEnum.SCALAR, accessor.Sparse.Count, sparseComponentType, strideI, arrI);

                        /////////

                        var bufferViewV = model.BufferViews[accessor.Sparse.Values.BufferView];

                        var bufferV = bufferData[bufferViewV.Buffer]; //byte[]

                        //calculate count
                        var elementBytesV = GetTypeMultiplier(accessor.Type) * GetComponentTypeMultiplier(accessor.ComponentType);
                        var strideV       = bufferViewV.ByteStride != null ? bufferViewV.ByteStride.Value : 0;
                        var strideDiffV   = strideV > 0 ? strideV - elementBytesV : 0;
                        var countV        = (elementBytesV + strideDiffV) * accessor.Sparse.Count;

                        var arrV = new byte[countV];
                        System.Buffer.BlockCopy(bufferV, accessor.Sparse.Values.ByteOffset + bufferViewV.ByteOffset, arrV, 0, countV);

                        var resValues = AccessBuffer(accessor.Type, accessor.Sparse.Count, accessor.ComponentType, strideV, arrV);

                        //mod accessorData
                        var valueCnt = 0;

                        for (int j = 0; j < accessor.Sparse.Count; j++)
                        {
                            var index             = resIndices[j];
                            var mult              = GetTypeMultiplier(accessor.Type);
                            var indexAccessorData = index * mult;

                            for (int k = 0; k < mult; k++)
                            {
                                accessorData[i][indexAccessorData + k] = resValues[valueCnt];
                                valueCnt++;
                            }
                        }
                    }
                }

                #endregion

                #region Process Meshes

                //foreach (var m in model.Meshes)
                for (int j = 0; j < model.Meshes.Length; j++)
                {
                    var m = model.Meshes[j];

                    var groupId = doc.Groups.Add(m.Name);

                    var meshes = new List <Rhino.Geometry.Mesh>();

                    foreach (var mp in m.Primitives)
                    {
                        //Do I need to treat different MeshPrimitive.ModeEnum differently? Yes because if we get POINTS, LINES, LINE_LOOP, LINE_STRIP then it won't be a mesh

                        var meshPart = new Rhino.Geometry.Mesh();

                        foreach (var att in mp.Attributes)
                        {
                            var attributeData = accessorData[att.Value];

                            switch (att.Key)
                            {
                            case "POSITION":

                                var pts = new List <Point3d>();

                                for (int i = 0; i <= attributeData.Count - 3; i = i + 3)
                                {
                                    pts.Add(new Point3d(attributeData[i], attributeData[i + 1], attributeData[i + 2]));
                                }

                                meshPart.Vertices.AddVertices(pts);

                                break;

                            case "TEXCOORD_0":

                                var uvs = new List <Point2f>();

                                for (int i = 0; i <= attributeData.Count - 2; i = i + 2)
                                {
                                    uvs.Add(new Point2f(attributeData[i], attributeData[i + 1]));
                                }

                                meshPart.TextureCoordinates.AddRange(uvs.ToArray());

                                break;

                            case "NORMAL":

                                var normals = new List <Vector3f>();

                                for (int i = 0; i <= attributeData.Count - 3; i = i + 3)
                                {
                                    normals.Add(new Vector3f(attributeData[i], attributeData[i + 1], attributeData[i + 2]));
                                }

                                meshPart.Normals.AddRange(normals.ToArray());

                                break;

                            case "COLOR_0":

                                var colors = new List <Color>();

                                for (int i = 0; i <= attributeData.Count - 3; i = i + 3)
                                {
                                    colors.Add(ColorFromSingle(attributeData[i], attributeData[i + 1], attributeData[i + 2]));
                                }

                                meshPart.VertexColors.AppendColors(colors.ToArray());

                                break;

                            default:

                                RhinoApp.WriteLine("Rhino glTF Importer: Attribute {0} not supported in Rhino. Skipping.", att.Key);

                                /* NOT SUPPORTED IN RHINO ... yet
                                 *
                                 * - TANGENT
                                 * - TEXCOORD_1 //might be supported with multiple mapping channels?
                                 * - JOINTS_0
                                 * - WEIGHTS_0
                                 *
                                 */

                                break;
                            }
                        }

                        if (mp.Indices != null)
                        {
                            // Indices can be defined as UNSIGNED_BYTE 5121 or UNSIGNED_SHORT 5123, maybe even as UNSIGNED_INT 5125

                            var faceIds = accessorData[mp.Indices.Value];
                            var faces   = new List <MeshFace>();

                            for (int i = 0; i <= faceIds.Count - 3; i = i + 3)
                            {
                                faces.Add(new MeshFace((int)faceIds[i], (int)faceIds[i + 1], (int)faceIds[i + 2]));
                            }

                            meshPart.Faces.AddFaces(faces);
                        }

                        //meshPart.Weld(Math.PI);
                        // TODO: CLEANUP
                        var oa = new ObjectAttributes
                        {
                            MaterialSource = ObjectMaterialSource.MaterialFromObject,
                            MaterialIndex  = (mp.Material != null) ? materialData[mp.Material.Value] : 0,
                            Name           = m.Name
                        };

                        //if (mp.Material != null)
                        //oa.MaterialIndex = materialData[mp.Material.Value];

                        meshPart.Compact();

#if DEBUG
                        if (!meshPart.IsValid)
                        {
                            //meshPart.Weld(Math.PI);
                            //meshPart.Vertices.Align(0.0001);
                            //meshPart.Vertices.CombineIdentical(true, true);
                            //meshPart.Vertices.CullUnused();
                            //meshPart.FaceNormals.ComputeFaceNormals();
                            //meshPart.Normals.ComputeNormals();

                            if (!meshPart.IsValid)
                            {
                                for (int i = 0; i < meshPart.Vertices.Count; i++)
                                {
                                    doc.Objects.AddTextDot(i.ToString(), meshPart.Vertices[i]);
                                    var ptD = new Point3d(meshPart.Vertices[i]);
                                    ptD.Transform(Transform.Translation(meshPart.Normals[i]));
                                    doc.Objects.AddLine(meshPart.Vertices[i], ptD);
                                }
                                foreach (var mf in meshPart.Faces)
                                {
                                    RhinoApp.WriteLine("Rhino glTF: Mesh Face - {0}", mf);
                                }
                                doc.Objects.AddPoints(meshPart.Vertices.ToPoint3dArray());
                            }
                            else
                            {
                                doc.Objects.AddMesh(meshPart, oa);
                            }
                        }
#endif

                        // var guid = doc.Objects.AddMesh(meshPart, oa);
                        // doc.Groups.AddToGroup(groupId, guid);

                        meshes.Add(meshPart);
                    }

                    meshData.Add(j, meshes);
                }

                #endregion

                #region Process Nodes Transforms

                for (int i = 0; i < model.Nodes.Length; i++)
                {
                    nodeXformData.Add(i, ProcessNode(model.Nodes[i]));
                }

                //for (int i = 0; i < model.Nodes.Length; i++)
                //TraverseNode(model, model.Nodes[i], Transform.Unset, meshData);

                TraverseNode(model, model.Nodes[model.Scenes[model.Scene.Value].Nodes[0]], Transform.Identity, meshData);

                #endregion

                #region Add to doc

                for (int i = 0; i < model.Nodes.Length; i++)
                {
                    var n = model.Nodes[i];
                    if (n.Mesh.HasValue)
                    {
                        int j = 0;
                        foreach (var meshes in meshData.Values)
                        {
                            var group = doc.Groups.Add(n.Name);
                            foreach (var m in meshes)
                            {
                                // TODO: Assign material
                                var oa = new ObjectAttributes
                                {
                                    Name = model.Meshes[j].Name
                                };
                                var guid = doc.Objects.AddMesh(m, oa);
                                doc.Groups.AddToGroup(group, guid);
                            }
                            j++;
                        }
                    }
                }

                #endregion
            }
        }