/// <summary> /// Implementation of Marching Cubes based on http://paulbourke.net/geometry/polygonise/ /// </summary> /// <param name="volume">Volume intensity values</param> /// <param name="dims">Volume dimensions</param> /// <param name="angpix">Angstrom per pixel</param> /// <param name="threshold">Isosurface threshold</param> /// <returns></returns> public static Mesh FromVolume(float[] volume, int3 dims, float angpix, float threshold) { Triangle[][] CellTriangles = new Triangle[volume.Length][]; unsafe { fixed (float* volumePtr = volume) for (int z = 0; z < dims.Z - 1; z++) { float zz = (z - dims.Z / 2) * angpix; for (int y = 0; y < dims.Y - 1; y++) { float yy = (y - dims.Y / 2) * angpix; for (int x = 0; x < dims.X - 1; x++) { float xx = (x - dims.X / 2) * angpix; Vector3[] p = new Vector3[8]; float[] val = new float[8]; p[0] = new Vector3(xx, yy, zz); p[1] = new Vector3(xx + angpix, yy, zz); p[2] = new Vector3(xx + angpix, yy + angpix, zz); p[3] = new Vector3(xx, yy + angpix, zz); p[4] = new Vector3(xx, yy, zz + angpix); p[5] = new Vector3(xx + angpix, yy, zz + angpix); p[6] = new Vector3(xx + angpix, yy + angpix, zz + angpix); p[7] = new Vector3(xx, yy + angpix, zz + angpix); val[0] = volumePtr[((z + 0) * dims.Y + (y + 0)) * dims.X + (x + 0)]; val[1] = volumePtr[((z + 0) * dims.Y + (y + 0)) * dims.X + (x + 1)]; val[2] = volumePtr[((z + 0) * dims.Y + (y + 1)) * dims.X + (x + 1)]; val[3] = volumePtr[((z + 0) * dims.Y + (y + 1)) * dims.X + (x + 0)]; val[4] = volumePtr[((z + 1) * dims.Y + (y + 0)) * dims.X + (x + 0)]; val[5] = volumePtr[((z + 1) * dims.Y + (y + 0)) * dims.X + (x + 1)]; val[6] = volumePtr[((z + 1) * dims.Y + (y + 1)) * dims.X + (x + 1)]; val[7] = volumePtr[((z + 1) * dims.Y + (y + 1)) * dims.X + (x + 0)]; CellTriangles[(z * dims.Y + y) * dims.X + x] = Polygonize(p, val, threshold); } } } } Mesh NewMesh = new Mesh(); for (int i = 0; i < CellTriangles.Length; i++) { if (CellTriangles[i] == null) continue; foreach (var tri in CellTriangles[i]) { NewMesh.Vertices.Add(tri.V0); NewMesh.Vertices.Add(tri.V1); NewMesh.Vertices.Add(tri.V2); tri.ID = NewMesh.Triangles.Count; NewMesh.Triangles.Add(tri); } } NewMesh.UpdateGraph(); NewMesh.UpdateVertexIDs(); return NewMesh; }
public void CenterOn(Mesh model) { Vector3 MeshMin = model.GetMin(); Vector3 MeshMax = model.GetMax(); Vector3 MeshCenter = (MeshMin + MeshMax) / 2f; Move(MeshCenter - Target); float MaxExtent = Math.Max(Math.Max(MeshMax.X - MeshMin.X, MeshMax.Y - MeshMin.Y), MeshMax.Z - MeshMin.Z); if (!IsOrthogonal) Distance = MaxExtent * 1.5f; else OrthogonalSize = MaxExtent; }
/// <summary> /// Creates a Mesh object based on a Wavefront OBJ file. /// </summary> /// <param name="path">Path to the file</param> /// <returns></returns> public static Mesh FromOBJ(string path, bool center = false) { Mesh NewMesh = new Mesh(); // Parse vertices using (TextReader Reader = new StreamReader(File.OpenRead(path))) { string Line = Reader.ReadLine(); while (Line != null) { string[] Parts = Line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (Parts.Length > 0 && Parts[0] == "v") NewMesh.Vertices.Add(new Vertex(new Vector3(float.Parse(Parts[1]), float.Parse(Parts[2]), float.Parse(Parts[3])), new Vector3(1, 0, 0))); Line = Reader.ReadLine(); } } if (center) { Vector3 Center = Vector3.Zero; Vector3 CenterVolume = Vector3.Zero; foreach (var v in NewMesh.Vertices) { Center += v.Position; CenterVolume += v.VolumePosition; } Center /= NewMesh.Vertices.Count; CenterVolume /= NewMesh.Vertices.Count; foreach (var v in NewMesh.Vertices) { v.Position -= Center; v.VolumePosition -= CenterVolume; } } // Parse faces using (TextReader Reader = new StreamReader(File.OpenRead(path))) { string Line = Reader.ReadLine(); while (Line != null) { string[] Parts = Line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (Parts.Length > 0 && Parts[0] == "f") { string[] FaceParts0 = Parts[1].Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); string[] FaceParts1 = Parts[2].Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); string[] FaceParts2 = Parts[3].Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); NewMesh.Triangles.Add(new Triangle(NewMesh.Triangles.Count, NewMesh.Vertices[int.Parse(FaceParts0[0]) - 1], NewMesh.Vertices[int.Parse(FaceParts1[0]) - 1], NewMesh.Vertices[int.Parse(FaceParts2[0]) - 1])); } Line = Reader.ReadLine(); } } // Amira likes to put unused vertices into the OBJ NewMesh.Vertices.RemoveAll(v => v.Triangles.Count == 0); NewMesh.UpdateGraph(); NewMesh.UpdateVertexIDs(); return NewMesh; }
public void UpdateDepiction() { if (DepictionMesh != null) { DepictionMesh.Dispose(); DepictionMesh = null; } foreach (var point in Points) point.DropDepictionMesh(); //if (Points.Count == 0) //return; if (Depiction == PointDepiction.Mesh && File.Exists(DepictionMeshPath)) { FileInfo Info = new FileInfo(DepictionMeshPath); if (Info.Extension.ToLower().Contains("mrc")) { HeaderMRC VolumeHeader = (HeaderMRC)MapHeader.ReadFromFile(DepictionMeshPath); float[] VolumeData = IOHelper.ReadSmallMapFloat(DepictionMeshPath, new int2(1, 1), 0, typeof(float)); Mesh NewMesh = Mesh.FromVolume(VolumeData, VolumeHeader.Dimensions, VolumeHeader.Pixelsize.X, (float)DepictionMeshLevel); NewMesh.UsedComponents = MeshVertexComponents.Position | MeshVertexComponents.Normal; NewMesh.GLContext = MainWindow.Options.Viewport.GetControl(); NewMesh.UpdateBuffers(); _DepictionMesh = NewMesh; } else if (Info.Extension.ToLower().Contains("obj")) { Mesh NewMesh = Mesh.FromOBJ(DepictionMeshPath, true); NewMesh.UsedComponents = MeshVertexComponents.Position | MeshVertexComponents.Normal; NewMesh.GLContext = MainWindow.Options.Viewport.GetControl(); NewMesh.UpdateBuffers(); _DepictionMesh = NewMesh; } } else if (Depiction == PointDepiction.LocalSurface && MainWindow.Options.Membrane.TomogramTexture != null) { int3 DimsExtract = new int3((int)Size + 2, (int)Size + 2, (int)Size + 2); Parallel.ForEach(Points, point => { Vector3 TomoPos = (point.Position - MainWindow.Options.Membrane.TomogramTexture.Offset) / MainWindow.Options.PixelScale.X; int3 TomoPosInt = new int3((int)Math.Round(TomoPos.X), (int)Math.Round(TomoPos.Y), (int)Math.Round(TomoPos.Z)); float[] LocalVol = Helper.Extract(MainWindow.Options.Membrane.TomogramTexture.OriginalData, MainWindow.Options.Membrane.TomogramTexture.Size, TomoPosInt, DimsExtract); if (DepictionLocalSurfaceInvert) for (int i = 0; i < LocalVol.Length; i++) LocalVol[i] = -LocalVol[i]; for (int z = 0; z < DimsExtract.Z; z++) for (int y = 0; y < DimsExtract.Y; y++) for (int x = 0; x < DimsExtract.X; x++) if (z == 0 || y == 0 || x == 0 || z == DimsExtract.Z - 1 || y == DimsExtract.Y - 1 || x == DimsExtract.X - 1) LocalVol[(z * DimsExtract.Y + y) * DimsExtract.X + x] = -99999; bool[] Mask = new bool[LocalVol.Length]; float Threshold = (float)DepictionLocalSurfaceLevel; if (DepictionLocalSurfaceOnlyCenter) { int MostCentralID = -1; float MostCentralDist = DimsExtract.X * DimsExtract.X; // Find most central valid pixel in the local window to start mask expansion from there. for (int z = 1; z < DimsExtract.Z - 1; z++) { int zz = z - DimsExtract.Z / 2; zz *= zz; for (int y = 1; y < DimsExtract.Y - 1; y++) { int yy = y - DimsExtract.Y / 2; yy *= yy; for (int x = 1; x < DimsExtract.X - 1; x++) { if (LocalVol[(z * DimsExtract.Y + y) * DimsExtract.X + x] >= Threshold) { int xx = x - DimsExtract.X / 2; xx *= xx; float r = xx + yy + zz; if (r < MostCentralDist) { MostCentralDist = r; MostCentralID = (z * DimsExtract.Y + y) * DimsExtract.X + x; } } } } } if (MostCentralID < 0) // Volume doesn't contain voxels above threshold return; Mask[MostCentralID] = true; for (int mi = 0; mi < Size / 2; mi++) { bool[] NextMask = new bool[Mask.Length]; for (int z = 1; z < DimsExtract.Z - 1; z++) for (int y = 1; y < DimsExtract.Y - 1; y++) for (int x = 1; x < DimsExtract.X - 1; x++) { int ID = (z * DimsExtract.Y + y) * DimsExtract.X + x; if (LocalVol[ID] >= Threshold) if (Mask[ID] || Mask[ID + 1] || Mask[ID - 1] || Mask[ID + DimsExtract.X] || Mask[ID - DimsExtract.X] || Mask[ID + DimsExtract.Y * DimsExtract.X] || Mask[ID - DimsExtract.Y * DimsExtract.X]) NextMask[ID] = true; } Mask = NextMask; } } else for (int i = 0; i < Mask.Length; i++) Mask[i] = true; // Apply spherical mask int Size2 = (int)(Size * Size / 4); for (int z = 1; z < DimsExtract.Z - 1; z++) { int zz = z - DimsExtract.Z / 2; zz *= zz; for (int y = 1; y < DimsExtract.Y - 1; y++) { int yy = y - DimsExtract.Y / 2; yy *= yy; for (int x = 1; x < DimsExtract.X - 1; x++) { int xx = x - DimsExtract.X / 2; xx *= xx; int r2 = xx + yy + zz; Mask[(z * DimsExtract.Y + y) * DimsExtract.X + x] &= r2 < Size2; } } } for (int i = 0; i < Mask.Length; i++) if (!Mask[i]) LocalVol[i] = Math.Min(LocalVol[i], Threshold - 1e-5f); //IOHelper.WriteMapFloat("d_extract.mrc", HeaderMRC.ReadFromFile("test_extract.mrc"), LocalVol); //IOHelper.WriteMapFloat("d_original.mrc", HeaderMRC.ReadFromFile("Tomo1L1_bin4.mrc"), MainWindow.Options.Membrane.TomogramTexture.OriginalData); point.DepictionMesh = Mesh.FromVolume(LocalVol, DimsExtract, MainWindow.Options.PixelScale.X, (float)DepictionLocalSurfaceLevel); point.DepictionMesh.UsedComponents = MeshVertexComponents.Position | MeshVertexComponents.Normal; }); foreach (var point in Points) { if (point.DepictionMesh == null) continue; point.DepictionMesh.GLContext = MainWindow.Options.Viewport.GetControl(); point.DepictionMesh.UpdateBuffers(); } } MainWindow.Options.Viewport.Redraw(); }
public void LoadModel(string path) { if (string.IsNullOrEmpty(path)) return; if (SurfaceMesh != null) SurfaceMesh.FreeBuffers(); SurfaceMesh = Mesh.FromOBJ(path); SurfaceMesh.GLContext = Viewport.GetControl(); SurfaceMesh.UpdateProcessedGeometry(0f); Viewport.GetControl().MakeCurrent(); SurfaceMesh.UpdateBuffers(); SurfaceOffset = 0; Viewport.Camera.CenterOn(SurfaceMesh); PathModel = path; }
public SurfacePatch(Membrane membrane, string name, Color color, IEnumerable<Triangle> triangles) { Membrane = membrane; Name = name; Color = color; Dictionary<Vertex, Vertex> VertexToTransformed = new Dictionary<Vertex, Vertex>(); List<Triangle> NewTriangles = new List<Triangle>(triangles.Count()); foreach (var t in triangles) { foreach (var v in t.Vertices) if (!VertexToTransformed.ContainsKey(v)) VertexToTransformed.Add(v, new Vertex(v.VolumePosition, v.VolumeNormal)); Triangle NewTriangle = new Triangle(t.ID, VertexToTransformed[t.V0], VertexToTransformed[t.V1], VertexToTransformed[t.V2]); NewTriangles.Add(NewTriangle); OriginalToTransformed.Add(t, NewTriangle); TransformedToOriginal.Add(NewTriangle, t); } SurfaceMesh = new Mesh(); SurfaceMesh.Vertices.AddRange(VertexToTransformed.Values); SurfaceMesh.Triangles.AddRange(OriginalToTransformed.Values); SurfaceMesh.UpdateGraph(); SurfaceMesh.UpdateVertexIDs(); TurnUpsideUp(); // Don't update buffers because there is no OpenGL context yet. UpdateStats(); UpdatePlanarizationStats(); Membrane.DisplayedPatches.CollectionChanged += MembraneDisplayedPatches_CollectionChanged; Membrane.PointGroups.CollectionChanged += MembranePointGroups_CollectionChanged; MembranePointGroups_CollectionChanged(null, null); }
public void DropDepictionMesh() { if (DepictionMesh != null) { DepictionMesh.Dispose(); DepictionMesh = null; } }