// // 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); }
///////////////////////////////////////////////////// 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); }
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); }
/// <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); }
/// <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(); }
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 } }