diff --git a/Triangle.NET/Triangle.Rendering/BoundingBox.cs b/Triangle.NET/Triangle.Rendering/BoundingBox.cs new file mode 100644 index 0000000..73a4ae5 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/BoundingBox.cs @@ -0,0 +1,68 @@ + +namespace TriangleNet.Rendering +{ + using System.Drawing; + + public class BoundingBox + { + public float Left; + public float Right; + public float Bottom; + public float Top; + + public float Width + { + get { return this.Right - this.Left; } + } + + public float Height + { + get { return this.Top - this.Bottom; } + } + + public BoundingBox() + { + Reset(); + } + + public BoundingBox(float left, float right, float bottom, float top) + { + this.Left = left; + this.Right = right; + this.Bottom = bottom; + this.Top = top; + } + + public void Update(Point pt) + { + this.Update(pt.X, pt.Y); + } + + public void Update(PointF pt) + { + this.Update(pt.X, pt.Y); + } + + public void Update(double x, double y) + { + Update((float)x, (float)y); + } + + public void Update(float x, float y) + { + // Update bounding box + if (this.Left > x) this.Left = x; + if (this.Right < x) this.Right = x; + if (this.Bottom > y) this.Bottom = y; + if (this.Top < y) this.Top = y; + } + + public void Reset() + { + this.Left = float.MaxValue; + this.Right = -float.MaxValue; + this.Bottom = float.MaxValue; + this.Top = -float.MaxValue; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/BufferBase.cs b/Triangle.NET/Triangle.Rendering/Buffer/BufferBase.cs new file mode 100644 index 0000000..27e9974 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Buffer/BufferBase.cs @@ -0,0 +1,41 @@ + +namespace TriangleNet.Rendering.Buffer +{ + public abstract class BufferBase : IBuffer where T : struct + { + protected T[] data; + protected int size; + + public BufferBase(int capacity, int size) + { + this.data = new T[capacity]; + this.size = size; + } + + public BufferBase(T[] data, int size) + { + this.data = data; + this.size = size; + } + + public T[] Data + { + get { return data; } + } + + public int Count + { + get { return data == null ? 0 : data.Length; } + } + + public abstract int Size + { + get; + } + + public abstract BufferTarget Target + { + get; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/ColorBuffer.cs b/Triangle.NET/Triangle.Rendering/Buffer/ColorBuffer.cs new file mode 100644 index 0000000..6ae7643 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Buffer/ColorBuffer.cs @@ -0,0 +1,29 @@ + +namespace TriangleNet.Rendering.Buffer +{ + using System; + using System.Drawing; + + public class ColorBuffer : BufferBase + { + public ColorBuffer(int capacity, int size) + : base(capacity, size) + { + } + + public ColorBuffer(Color[] data, int size) + : base(data, size) + { + } + + public override int Size + { + get { return 1; } + } + + public override BufferTarget Target + { + get { return BufferTarget.ColorBuffer; } + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/IBuffer.cs b/Triangle.NET/Triangle.Rendering/Buffer/IBuffer.cs new file mode 100644 index 0000000..a39ab3e --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Buffer/IBuffer.cs @@ -0,0 +1,34 @@ + +namespace TriangleNet.Rendering.Buffer +{ + public enum BufferTarget : byte + { + ColorBuffer, + IndexBuffer, + VertexBuffer + } + + public interface IBuffer where T : struct + { + /// + /// Gets the contents of the buffer. + /// + T[] Data { get; } + + /// + /// Gets the size of the buffer. + /// + int Count { get; } + + /// + /// Gets the size of one element in the buffer (i.e. 2 for 2D points + /// or 3 for triangles). + /// + int Size { get; } + + /// + /// Gets the buffer target (vertices or indices). + /// + BufferTarget Target { get; } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/IndexBuffer.cs b/Triangle.NET/Triangle.Rendering/Buffer/IndexBuffer.cs new file mode 100644 index 0000000..a98526f --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Buffer/IndexBuffer.cs @@ -0,0 +1,30 @@ + +namespace TriangleNet.Rendering.Buffer +{ + public class IndexBuffer : BufferBase + { + public IndexBuffer(int capacity, int size) + : base(capacity, size) + { + } + + public IndexBuffer(int[] data, int size) + : base(data, size) + { + } + + /// + /// Gets the number of indices for one element (i.e. 2 for segments + /// or 3 for triangles). + /// + public override int Size + { + get { return size; } + } + + public override BufferTarget Target + { + get { return BufferTarget.IndexBuffer; } + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Buffer/VertexBuffer.cs b/Triangle.NET/Triangle.Rendering/Buffer/VertexBuffer.cs new file mode 100644 index 0000000..fb8ec45 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Buffer/VertexBuffer.cs @@ -0,0 +1,30 @@ + +namespace TriangleNet.Rendering.Buffer +{ + public class VertexBuffer : BufferBase + { + public VertexBuffer(int capacity, int size = 2) + : base(capacity, size) + { + } + + public VertexBuffer(float[] data, int size = 2) + : base(data, size) + { + } + + /// + /// Gets the number of coordinates of one vertex in the buffer (i.e. 2 for + /// 2D points or 3D points). + /// + public override int Size + { + get { return size; } + } + + public override BufferTarget Target + { + get { return BufferTarget.VertexBuffer; } + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/ColorManager.cs b/Triangle.NET/Triangle.Rendering/ColorManager.cs new file mode 100644 index 0000000..ba37f33 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/ColorManager.cs @@ -0,0 +1,180 @@ + +namespace TriangleNet.Rendering +{ + using System.Collections.Generic; + using System.Drawing; + using TriangleNet.Rendering.Util; + + public class ColorManager + { + Color background; + SolidBrush point; + SolidBrush steinerPoint; + Pen line; + Pen segment; + Pen voronoiLine; + + #region Public properties + + /// + /// Gets or sets the background color. + /// + public Color Background + { + get { return background; } + set { background = value; } + } + + /// + /// Gets or sets the brush used for points. + /// + public SolidBrush Point + { + get { return point; } + set + { + if (point != null) point.Dispose(); + point = value; + } + } + + /// + /// Gets or sets the brush used for steiner points. + /// + public SolidBrush SteinerPoint + { + get { return steinerPoint; } + set + { + if (steinerPoint != null) steinerPoint.Dispose(); + steinerPoint = value; + } + } + + /// + /// Gets or sets the pen used for mesh edges. + /// + public Pen Line + { + get { return line; } + set + { + if (line != null) line.Dispose(); + line = value; + } + } + + /// + /// Gets or sets the pen used for mesh segments. + /// + public Pen Segment + { + get { return segment; } + set + { + if (segment != null) segment.Dispose(); + segment = value; + } + } + + /// + /// Gets or sets the pen used for Voronoi edges. + /// + public Pen VoronoiLine + { + get { return voronoiLine; } + set + { + if (voronoiLine != null) voronoiLine.Dispose(); + voronoiLine = value; + } + } + + #endregion + + /// + /// Gets or sets a dictionary which maps region ids (or partition indices) to a color. + /// + public Dictionary ColorDictionary { get; set; } + + /// + /// Gets or sets a colormap which is used for function plotting. + /// + public ColorMap ColorMap { get; set; } + + /// + /// Creates an instance of the class with default (dark) color scheme. + /// + public static ColorManager Default() + { + var colors = new ColorManager(); + + colors.Background = Color.FromArgb(0, 0, 0); + colors.Point = new SolidBrush(Color.Green); + colors.SteinerPoint = new SolidBrush(Color.Peru); + colors.Line = new Pen(Color.FromArgb(30, 30, 30)); + colors.Segment = new Pen(Color.DarkBlue); + colors.VoronoiLine = new Pen(Color.FromArgb(40, 50, 60)); + + return colors; + } + + public void CreateColorDictionary(int length) + { + var keys = new int[length]; + + for (int i = 0; i < length; i++) + { + keys[i] = i; + } + + CreateColorDictionary(keys, length); + } + + public void CreateColorDictionary(IEnumerable keys, int length) + { + this.ColorDictionary = new Dictionary(); + + int i = 0, n = regionColors.Length; + + foreach (var key in keys) + { + this.ColorDictionary.Add(key, regionColors[i]); + + i = (i + 1) % n; + } + } + + internal void Dispose(Dictionary brushes) + { + foreach (var brush in brushes.Values) + { + brush.Dispose(); + } + } + + internal Dictionary GetBrushDictionary() + { + var brushes = new Dictionary(); + + foreach (var item in ColorDictionary) + { + brushes.Add(item.Key, new SolidBrush(item.Value)); + } + + return brushes; + } + + // Change or add as many colors as you like... + private static Color[] regionColors = { + Color.FromArgb(200, 0, 0, 255), + Color.FromArgb(200, 255, 0, 0), + Color.FromArgb(200, 0, 255, 255), + Color.FromArgb(200, 255, 255, 0), + Color.FromArgb(200, 255, 0, 255), + Color.FromArgb(200, 0, 255, 0), + Color.FromArgb(200, 127, 0, 255), + Color.FromArgb(200, 0, 127, 255) + }; + } +} diff --git a/Triangle.NET/Triangle.Rendering/ExtensionMethods.cs b/Triangle.NET/Triangle.Rendering/ExtensionMethods.cs new file mode 100644 index 0000000..175d185 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/ExtensionMethods.cs @@ -0,0 +1,26 @@ + +namespace TriangleNet.Rendering +{ + using System.Drawing; + + internal static class ExtensionMethods + { + /// + /// Check if segment (a, b) intersects rectangle. + /// + public static bool Intersects(this RectangleF rect, PointF a, PointF b) + { + // TODO: implement intersection. + return rect.Contains(a) || rect.Contains(b); + } + + /// + /// Check if triangle (a, b, c) intersects rectangle. + /// + public static bool Intersects(this RectangleF rect, PointF a, PointF b, PointF c) + { + // TODO: implement intersection. + return rect.Contains(a) || rect.Contains(b) || rect.Contains(c); + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/FunctionRenderer.cs b/Triangle.NET/Triangle.Rendering/GDI/FunctionRenderer.cs new file mode 100644 index 0000000..7320b29 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/FunctionRenderer.cs @@ -0,0 +1,101 @@ + +namespace TriangleNet.Rendering.GDI +{ + using System; + using System.Drawing; + using TriangleNet.Rendering.GDI.Native; + + public class FunctionRenderer + { + TriVertex[] points; + GradientTriangle[] elements; + + public Graphics RenderTarget { get; set; } + + public IRenderContext Context { get; set; } + + public void Render(IRenderLayer layer) + { + Create(layer); + + var hdc = RenderTarget.GetHdc(); + + NativeMethods.GradientFill(hdc, + points, (uint)points.Length, elements, (uint)elements.Length, + GradientFillMode.GRADIENT_FILL_TRIANGLE); + + RenderTarget.ReleaseHdc(hdc); + } + + private void Create(IRenderLayer layer) + { + var zoom = Context.Zoom; + var colors = layer.Colors.Data; + + int length = colors.Length; + + int size = layer.Points.Size; + var data = layer.Points.Data; + + if (length != data.Length / size) + { + throw new Exception(); + } + + this.points = new TriVertex[length]; + + TriVertex vertex; + Color color; + PointF p = new PointF((float)data[0], (float)data[1]); + + zoom.WorldToScreen(ref p); + + // Get correction distance + float dx = (p.X - (int)p.X) * 2.0f; + float dy = (p.Y - (int)p.Y) * 2.0f; + + // Create vertices. + for (int i = 0; i < length; i++) + { + p.X = (float)data[size * i]; + p.Y = (float)data[size * i + 1]; + + zoom.WorldToScreen(ref p); + + color = colors[i]; + + vertex = new TriVertex(); + + vertex.x = (int)(p.X + dx); + vertex.y = (int)(p.Y + dy); + + vertex.Red = (ushort)(color.R << 8); + vertex.Green = (ushort)(color.G << 8); + vertex.Blue = (ushort)(color.B << 8); + vertex.Alpha = (ushort)(color.A << 8); + + this.points[i] = vertex; + } + + var triangles = layer.Indices.Data; + + length = triangles.Length / 3; + + this.elements = new GradientTriangle[length]; + + GradientTriangle e; + + // Create triangles. + for (int i = 0; i < length; i++) + { + e = new GradientTriangle(); + + e.Vertex1 = (uint)triangles[3 * i]; + e.Vertex2 = (uint)triangles[3 * i + 1]; + e.Vertex3 = (uint)triangles[3 * i + 2]; + + this.elements[i] = e; + } + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/LayerRenderer.cs b/Triangle.NET/Triangle.Rendering/GDI/LayerRenderer.cs new file mode 100644 index 0000000..da7da34 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/LayerRenderer.cs @@ -0,0 +1,110 @@ + +namespace TriangleNet.Rendering.GDI +{ + using System.Drawing; + + public class LayerRenderer : IRenderer + { + MeshRenderer meshRenderer; + FunctionRenderer functionRenderer; + + public IRenderContext Context { get; set; } + + public Graphics RenderTarget { get; set; } + + public LayerRenderer() + { + meshRenderer = new MeshRenderer(); + functionRenderer = new FunctionRenderer(); + } + + public void Render() + { + meshRenderer.Context = Context; + meshRenderer.RenderTarget = RenderTarget; + + functionRenderer.Context = Context; + functionRenderer.RenderTarget = RenderTarget; + + // 0 = mesh (filled) + // 1 = mesh (wireframe) + // 2 = polygon + // 3 = points + // 4 = voronoi overlay + // 5 = vector field + // 6 = contour lines + + int i = 0; + + foreach (var layer in this.Context.RenderLayers) + { + if (!layer.IsEmpty && layer.IsActive) + { + switch (i) + { + case 0: + RenderFilledMesh(layer); + break; + case 1: + RenderMesh(layer); + break; + case 2: + RenderPolygon(layer); + break; + case 3: + RenderPoints(layer); + break; + case 4: + RenderVoronoi(layer); + break; + case 5: + case 6: + default: + break; + } + } + + i++; + } + } + + private void RenderFilledMesh(IRenderLayer layer) + { + if (layer.Partition != null) + { + meshRenderer.RenderElements(layer.Points.Data, layer.Indices.Data, 3, layer.Partition.Data); + } + else if (layer.Colors != null) + { + functionRenderer.Render(layer); + } + } + + private void RenderMesh(IRenderLayer layer) + { + if (layer.Indices.Size == 3) + { + meshRenderer.RenderElements(layer.Points.Data, layer.Indices.Data, 3, null); + } + else + { + meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, Context.ColorManager.Line); + } + } + + private void RenderPolygon(IRenderLayer layer) + { + meshRenderer.RenderSegments(layer.Points.Data, layer.Indices.Data, Context.ColorManager.Segment); + } + + private void RenderPoints(IRenderLayer layer) + { + meshRenderer.RenderPoints(layer.Points.Data, layer.Points.Size, layer.Count); + } + + private void RenderVoronoi(IRenderLayer layer) + { + meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, Context.ColorManager.VoronoiLine); + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/MeshRenderer.cs b/Triangle.NET/Triangle.Rendering/GDI/MeshRenderer.cs new file mode 100644 index 0000000..e8d4348 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/MeshRenderer.cs @@ -0,0 +1,168 @@ +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering.GDI +{ + using System.Drawing; + using TriangleNet.Rendering.GDI.Native; + + /// + /// Renders a mesh. + /// + public class MeshRenderer + { + /// + /// Initializes a new instance of the class. + /// + public MeshRenderer() + { + } + + public Graphics RenderTarget { get; set; } + + public IRenderContext Context { get; set; } + + public void RenderPoints(float[] points, int size, int limit = 0) + { + int n = points.Length / size; + int m = limit > 0 ? limit : n; + + // Draw unchanged points + RenderPoints(points, size, 0, m, Context.ColorManager.Point); + + // Draw new (Steiner) points + if (limit > 0) + { + RenderPoints(points, size, m, n, Context.ColorManager.SteinerPoint); + } + } + + public void RenderPoints(float[] points, int size, int start, int end, Brush brush) + { + var g = this.RenderTarget; + var zoom = this.Context.Zoom; + + int i, k, n = points.Length / size; + PointF p = new PointF(); + + // Render points + for (i = start; i < end; i++) + { + k = size * i; + + p.X = points[k]; + p.Y = points[k + 1]; + + if (zoom.Viewport.Contains(p)) + { + zoom.WorldToScreen(ref p); + g.FillEllipse(brush, p.X - 1.5f, p.Y - 1.5f, 3, 3); + } + } + } + + public void RenderSegments(float[] points, int[] indices, Pen pen) + { + RenderLines(points, indices, pen); + } + + public void RenderEdges(float[] points, int[] indices, Pen pen) + { + RenderLines(points, indices, pen); + } + + public void RenderElements(float[] points, int[] indices, int size, int[] partition) + { + var g = this.RenderTarget; + var zoom = this.Context.Zoom; + + int n = indices.Length / size; + int k0, k1, k2; + + var tri = new PointF[size]; + + bool filled = partition != null; + + var brushes = filled ? Context.ColorManager.GetBrushDictionary() : null; + + // TODO: remove hardcoded color + var pen = new Pen(Color.FromArgb(20, 20, 20)); + + // Draw triangles + for (int i = 0; i < n; i++) + { + k0 = 2 * indices[3 * i]; + k1 = 2 * indices[3 * i + 1]; + k2 = 2 * indices[3 * i + 2]; + + tri[0].X = points[k0]; + tri[0].Y = points[k0 + 1]; + + tri[1].X = points[k1]; + tri[1].Y = points[k1 + 1]; + + tri[2].X = points[k2]; + tri[2].Y = points[k2 + 1]; + + if (zoom.Viewport.Intersects(tri[0], tri[1], tri[2])) + { + zoom.WorldToScreen(ref tri[0]); + zoom.WorldToScreen(ref tri[1]); + zoom.WorldToScreen(ref tri[2]); + + if (filled) + { + g.FillPolygon(brushes[partition[i]], tri); + } + else + { + g.DrawPolygon(pen, tri); + } + } + } + + pen.Dispose(); + + if (filled) + { + Context.ColorManager.Dispose(brushes); + } + } + + public void RenderLines(float[] points, int[] indices, Pen pen) + { + var g = this.RenderTarget; + var zoom = this.Context.Zoom; + + int n = indices.Length / 2; + int k0, k1; + + PointF p0 = new PointF(); + PointF p1 = new PointF(); + + // Draw edges + for (int i = 0; i < n; i++) + { + k0 = 2 * indices[2 * i]; + k1 = 2 * indices[2 * i + 1]; + + p0.X = points[k0]; + p0.Y = points[k0 + 1]; + + p1.X = points[k1]; + p1.Y = points[k1 + 1]; + + if (zoom.Viewport.Intersects(p0, p1)) + { + zoom.WorldToScreen(ref p0); + zoom.WorldToScreen(ref p1); + + g.DrawLine(pen, p0, p1); + } + } + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/GradientFillMode.cs b/Triangle.NET/Triangle.Rendering/GDI/Native/GradientFillMode.cs new file mode 100644 index 0000000..7b6ba25 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/Native/GradientFillMode.cs @@ -0,0 +1,35 @@ + +namespace TriangleNet.Rendering.GDI.Native +{ + using System; + + /// + /// Specifies gradient fill mode + /// + [Flags] + internal enum GradientFillMode : uint + { + /// + /// In this mode, two endpoints describe a rectangle. The rectangle is defined + /// to have a constant color (specified by the TRIVERTEX structure) for the + /// left and right edges. GDI interpolates the color from the left to right + /// edge and fills the interior + /// + GRADIENT_FILL_RECT_H = 0, + /// + /// In this mode, two endpoints describe a rectangle. The rectangle is + /// defined to have a constant color (specified by the TRIVERTEX structure) + /// for the top and bottom edges. GDI interpolates the color from the top + /// to bottom edge and fills the interior + /// + GRADIENT_FILL_RECT_V = 1, + /// + /// In this mode, an array of TRIVERTEX structures is passed to GDI + /// along with a list of array indexes that describe separate triangles. + /// GDI performs linear interpolation between triangle vertices and fills + /// the interior. Drawing is done directly in 24- and 32-bpp modes. + /// Dithering is performed in 16-, 8-, 4-, and 1-bpp mode + /// + GRADIENT_FILL_TRIANGLE = 2 + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/GradientRect.cs b/Triangle.NET/Triangle.Rendering/GDI/Native/GradientRect.cs new file mode 100644 index 0000000..f617645 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/Native/GradientRect.cs @@ -0,0 +1,28 @@ + +namespace TriangleNet.Rendering.GDI.Native +{ + using System.Runtime.InteropServices; + + /// + /// The GRADIENT_RECT structure specifies the index of two vertices in the + /// pVertex array in the GradientFill function. These two vertices form the + /// upper-left and lower-right boundaries of a rectangle. + /// + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144958.aspx + /// + [StructLayout(LayoutKind.Sequential)] + internal struct GradientRect + { + /// + /// The upper-left corner of a rectangle. + /// + public uint UpperLeft; + + /// + /// The lower-right corner of a rectangle. + /// + public uint LowerRight; + } + +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/GradientTriangle.cs b/Triangle.NET/Triangle.Rendering/GDI/Native/GradientTriangle.cs new file mode 100644 index 0000000..dd64867 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/Native/GradientTriangle.cs @@ -0,0 +1,32 @@ + +namespace TriangleNet.Rendering.GDI.Native +{ + using System.Runtime.InteropServices; + + /// + /// The GRADIENT_TRIANGLE structure specifies the index of three + /// vertices in the pVertex array in the GradientFill function. + /// These three vertices form one triangle + /// + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144959.aspx + /// + [StructLayout(LayoutKind.Sequential)] + internal struct GradientTriangle + { + /// + /// The first point of the triangle where sides intersect. + /// + public uint Vertex1; + + /// + /// The second point of the triangle where sides intersect. + /// + public uint Vertex2; + + /// + /// The third point of the triangle where sides intersect. + /// + public uint Vertex3; + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/NativeMethods.cs b/Triangle.NET/Triangle.Rendering/GDI/Native/NativeMethods.cs new file mode 100644 index 0000000..2a625f4 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/Native/NativeMethods.cs @@ -0,0 +1,90 @@ + +namespace TriangleNet.Rendering.GDI.Native +{ + using System; + using System.Runtime.InteropServices; + + /// + /// PInvoke signatures for GradientFill methods. + /// + /// + /// Minimum requirements: Windows 2000 Professional + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144957.aspx + /// + internal static class NativeMethods + { + /// + /// The GradientFill function fills rectangle and triangle structures + /// + /// Handle to the destination device contex + /// Array of TRIVERTEX structures that each define a triangle vertex + /// The number of vertices in pVertex + /// Array of elements + /// The number of elements in pMesh + /// Specifies gradient fill mode + /// If the function succeeds, the return value is true, false + public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, uint[] pMesh, uint nMesh, + GradientFillMode ulMode) + { + return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode); + } + + /// + /// The GradientFill function fills rectangle and triangle structures + /// + /// Handle to the destination device contex + /// Array of TRIVERTEX structures that each define a triangle vertex + /// The number of vertices in pVertex + /// Array of GRADIENT_TRIANGLE structures in triangle mode + /// The number of elements in pMesh + /// Specifies gradient fill mode + /// If the function succeeds, the return value is true, false + public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, GradientTriangle[] pMesh, + uint nMesh, GradientFillMode ulMode) + { + return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode); + } + + /// + /// The GradientFill function fills rectangle and triangle structures + /// + /// Handle to the destination device contex + /// Array of TRIVERTEX structures that each define a triangle vertex + /// The number of vertices in pVertex + /// an array of GRADIENT_RECT structures in rectangle mode + /// The number of elements in pMesh + /// Specifies gradient fill mode + /// If the function succeeds, the return value is true, false + public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, GradientRect[] pMesh, + uint nMesh, GradientFillMode ulMode) + { + return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode); + } + + #region Nested type: Native + + internal class Native + { + [DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GradientFill([In] IntPtr hdc, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex, + uint nVertex, uint[] pMesh, uint nMesh, GradientFillMode ulMode); + + [DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GradientFill([In] IntPtr hdc, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex, + uint nVertex, GradientRect[] pMesh, uint nMesh, GradientFillMode ulMode); + + [DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GradientFill([In] IntPtr hdc, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex, + uint nVertex, GradientTriangle[] pMesh, uint nMesh, GradientFillMode ulMode); + } + + #endregion + } +} \ No newline at end of file diff --git a/Triangle.NET/Triangle.Rendering/GDI/Native/TriVertex.cs b/Triangle.NET/Triangle.Rendering/GDI/Native/TriVertex.cs new file mode 100644 index 0000000..0279447 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/Native/TriVertex.cs @@ -0,0 +1,45 @@ + +namespace TriangleNet.Rendering.GDI.Native +{ + using System.Runtime.InteropServices; + + /// + /// The TRIVERTEX structure contains color information and position information. + /// + /// + /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145142.aspx + /// + [StructLayout(LayoutKind.Sequential)] + internal struct TriVertex + { + /// + /// The x-coordinate, in logical units, of the upper-left corner of the rectangle + /// + public int x; + + /// + /// The y-coordinate, in logical units, of the upper-left corner of the rectangle + /// + public int y; + + /// + /// The color information at the point of x, y + /// + public ushort Red; + + /// + /// The color information at the point of x, y + /// + public ushort Green; + + /// + /// The color information at the point of x, y + /// + public ushort Blue; + + /// + /// The color information at the point of x, y + /// + public ushort Alpha; + } +} diff --git a/Triangle.NET/Triangle.Rendering/GDI/RenderControl.cs b/Triangle.NET/Triangle.Rendering/GDI/RenderControl.cs new file mode 100644 index 0000000..d707e50 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/GDI/RenderControl.cs @@ -0,0 +1,220 @@ +// ----------------------------------------------------------------------- +// +// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/ +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering.GDI +{ + using System; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Text; + using System.Windows.Forms; + + /// + /// Renders a mesh using GDI. + /// + public class RenderControl : Control, IRenderControl + { + // Rendering stuff + private BufferedGraphics buffer; + private BufferedGraphicsContext context; + + //ColorManager renderColors; + + bool initialized = false; + + string coordinate = String.Empty; + + Timer timer; + + /// + /// Initializes a new instance of the class. + /// + public RenderControl() + { + //this.SetStyle(ControlStyles.UserPaint, true); + //this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false); + this.SetStyle(ControlStyles.ResizeRedraw, true); + + //renderColors = ColorManager.Default(); + + this.BackColor = Color.Black; + + context = BufferedGraphicsManager.Current;// new BufferedGraphicsContext(); + + timer = new Timer(); + timer.Interval = 3000; + timer.Tick += (sender, e) => + { + timer.Stop(); + coordinate = String.Empty; + this.Invalidate(); + }; + } + + public IRenderer Renderer { get; set; } + + /// + /// Initialize the graphics buffer (should be called in the forms load event). + /// + public void Initialize() + { + //zoom.Initialize(this.ClientRectangle); + InitializeBuffer(); + + initialized = true; + + this.Invalidate(); + } + + public override void Refresh() + { + this.Render(); + } + + public void HandleMouseClick(float x, float y, MouseButtons button) + { + if (!initialized) return; + + var zoom = this.Renderer.Context.Zoom; + + if (button == MouseButtons.Middle) + { + zoom.Reset(); + this.Render(); + } + else if (button == MouseButtons.Left) + { + timer.Stop(); + + var nfi = System.Globalization.CultureInfo.InvariantCulture.NumberFormat; + + PointF c = new PointF(x / this.Width, y / this.Height); + zoom.ScreenToWorld(ref c); + coordinate = String.Format(nfi, "X:{0} Y:{1}", c.X, c.Y); + + this.Invalidate(); + + timer.Start(); + } + } + + /// + /// Zoom to the given location. + /// + /// The zoom focus. + /// Indicates whether to zoom in or out. + public void HandleMouseWheel(float x, float y, int delta) + { + if (!initialized) return; + + var zoom = this.Renderer.Context.Zoom; + + if (zoom.Zoom(delta, x, y)) + { + // Redraw + this.Render(); + } + } + + /// + /// Update graphics buffer and zoom after a resize. + /// + public void HandleResize() + { + var zoom = this.Renderer.Context.Zoom; + var bounds = this.Renderer.Context.Bounds; + + zoom.Initialize(this.ClientRectangle, bounds); + InitializeBuffer(); + } + + private void InitializeBuffer() + { + if (this.Width > 0 && this.Height > 0) + { + if (buffer != null) + { + if (this.ClientRectangle == buffer.Graphics.VisibleClipBounds) + { + this.Invalidate(); + + // Bounds didn't change. Probably we just restored the + // window from minimized state. + return; + } + + buffer.Dispose(); + } + + //buffer = context.Allocate(Graphics.FromHwnd(this.Handle), this.ClientRectangle); + buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle); + + if (initialized) + { + this.Render(); + } + } + } + + private void Render() + { + coordinate = String.Empty; + + if (buffer == null) + { + return; + } + + var g = buffer.Graphics; + var renderer = this.Renderer as LayerRenderer; + + g.Clear(renderer.Context.ColorManager.Background); + + if (!initialized || renderer == null) + { + return; + } + + g.SmoothingMode = SmoothingMode.AntiAlias; + + renderer.RenderTarget = g; + renderer.Render(); + + this.Invalidate(); + } + + #region Protected overrides + + protected override void OnPaint(PaintEventArgs e) + { + if (!initialized) + { + base.OnPaint(e); + return; + } + + buffer.Render(); + + if (!String.IsNullOrEmpty(coordinate) && Renderer.Context.HasData) + { + Graphics g = e.Graphics; + g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; + g.DrawString(coordinate, this.Font, Brushes.White, 10, 10); + } + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + // Do nothing + if (!initialized) + { + base.OnPaintBackground(pevent); + } + } + + #endregion + } +} diff --git a/Triangle.NET/Triangle.Rendering/IRenderContext.cs b/Triangle.NET/Triangle.Rendering/IRenderContext.cs new file mode 100644 index 0000000..72d0d7e --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/IRenderContext.cs @@ -0,0 +1,30 @@ + +namespace TriangleNet.Rendering +{ + using System.Collections.Generic; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Tools; + + public interface IRenderContext + { + ColorManager ColorManager { get; } + + BoundingBox Bounds { get; } + + IList RenderLayers { get; } + + Projection Zoom { get; } + + IMesh Mesh { get; } + + bool HasData { get; } + + void Add(IPolygon data); + void Add(IMesh data, bool reset); + void Add(IVoronoi voronoi, bool reset); + + void Add(float[] values); + void Add(int[] partition); + } +} diff --git a/Triangle.NET/Triangle.Rendering/IRenderControl.cs b/Triangle.NET/Triangle.Rendering/IRenderControl.cs new file mode 100644 index 0000000..95a3b37 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/IRenderControl.cs @@ -0,0 +1,28 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering +{ + using System; + using System.Windows.Forms; + using System.Drawing; + + /// + /// TODO: Update summary. + /// + public interface IRenderControl + { + IRenderer Renderer { get; set; } + Rectangle ClientRectangle { get; } + + void Initialize(); + void Refresh(); + + void HandleMouseClick(float x, float y, MouseButtons button); + void HandleMouseWheel(float x, float y, int delta); + void HandleResize(); + } +} diff --git a/Triangle.NET/Triangle.Rendering/IRenderLayer.cs b/Triangle.NET/Triangle.Rendering/IRenderLayer.cs new file mode 100644 index 0000000..6437a63 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/IRenderLayer.cs @@ -0,0 +1,42 @@ + +namespace TriangleNet.Rendering +{ + using System.Drawing; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Rendering.Buffer; + using TriangleNet.Rendering.Util; + using TriangleNet.Tools; + + public interface IRenderLayer + { + int Count { get; } + + // Points can be set, because layers may share vertices. + IBuffer Points { get; } + IBuffer Indices { get; } + + bool IsActive { get; set; } + bool IsEmpty { get; } + + void Reset(bool clear); + + // TODO: add boolean: reset + BoundingBox SetPoints(IBuffer buffer); + BoundingBox SetPoints(IPolygon poly); + BoundingBox SetPoints(IMesh mesh); + BoundingBox SetPoints(IVoronoi voronoi); + void SetPolygon(IPolygon poly); + void SetPolygon(IMesh mesh); + void SetMesh(IMesh mesh, bool elements); + void SetMesh(IVoronoi voronoi); + + + // TODO: better put these into a subclass. + IBuffer Partition { get; } + IBuffer Colors { get; } + + void AttachLayerData(float[] values, ColorMap colormap); + void AttachLayerData(int[] partition); + } +} diff --git a/Triangle.NET/Triangle.Rendering/IRenderer.cs b/Triangle.NET/Triangle.Rendering/IRenderer.cs new file mode 100644 index 0000000..a77c7c5 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/IRenderer.cs @@ -0,0 +1,10 @@ + +namespace TriangleNet.Rendering +{ + public interface IRenderer + { + IRenderContext Context { get; set; } + + void Render(); + } +} diff --git a/Triangle.NET/Triangle.Rendering/Projection.cs b/Triangle.NET/Triangle.Rendering/Projection.cs new file mode 100644 index 0000000..43e37a9 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Projection.cs @@ -0,0 +1,214 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace TriangleNet.Rendering +{ + using System; + using System.Drawing; + + /// + /// Manages a world to screen transformation (2D orthographic projection). + /// + public class Projection + { + // The screen. + Rectangle screen; + + // The complete mesh. + RectangleF world; + + /// + /// Gets or sets the current viewport (visible mesh). + /// + public RectangleF Viewport { get; set; } + + /// + /// Gets the current scale. + /// + public float Scale + { + get { return screen.Width / Viewport.Width; } + } + + /// + /// Gets the zoom level. + /// + public int Level { get; private set; } + + /// + /// Gets or sets a clip margin (default is 5% of viewport width on each side). + /// + public float ClipMargin { get; set; } + + // The y-direction of windows screen coordinates is upside down, + // so inverY must be set to true. + bool invertY = false; + + int maxZoomLevel = 50; + + public Projection(Rectangle screen, bool invertY = true) + { + this.screen = screen; + this.world = screen; + this.Viewport = screen; + + this.Level = 1; + + this.ClipMargin = this.Viewport.Width * 0.05f; + + this.invertY = invertY; + } + + public void Initialize(BoundingBox world) + { + Initialize(this.screen, world); + } + + public void Initialize(Rectangle screen, BoundingBox world) + { + this.screen = screen; + + this.Level = 1; + + // Add a margin so there's some space around the border + float worldMargin = (world.Width < world.Height) ? world.Height * 0.05f : world.Width * 0.05f; + + // Get the initial viewport (complete mesh centered on the screen) + float screenRatio = screen.Width / (float)screen.Height; + float worldRatio = world.Width / world.Height; + + float scale = (world.Width + worldMargin) / screen.Width; + + if (screenRatio > worldRatio) + { + scale = (world.Height + worldMargin) / screen.Height; + } + + float centerX = world.Left + world.Width / 2; + float centerY = world.Bottom + world.Height / 2; + + // TODO: Add initial margin + this.Viewport = new RectangleF(centerX - screen.Width * scale / 2, + centerY - screen.Height * scale / 2, + screen.Width * scale, + screen.Height * scale); + + this.ClipMargin = this.Viewport.Width * 0.05f; + + this.world = this.Viewport; + } + + /// + /// Zoom in or out of the viewport. + /// + /// Zoom amount + /// Relative x point position + /// Relative y point position + public bool Zoom(int amount, float focusX, float focusY) + { + float width, height; + + if (invertY) + { + focusY = 1 - focusY; + } + + if (amount > 0) // Zoom in + { + this.Level++; + + if (this.Level > maxZoomLevel) + { + this.Level = maxZoomLevel; + return false; + } + + width = Viewport.Width / 1.1f; + height = Viewport.Height / 1.1f; + } + else + { + this.Level--; + + if (this.Level < 1) + { + this.Level = 1; + this.Viewport = this.world; + return false; + } + + width = Viewport.Width * 1.1f; + height = Viewport.Height * 1.1f; + } + + // Current focus on viewport + float x = Viewport.X + Viewport.Width * focusX; + float y = Viewport.Y + Viewport.Height * focusY; + + // New left and top positions + x = x - width * focusX; + y = y - height * focusY; + + // Check if outside of world + if (x < world.X) + { + x = world.X; + } + else if (x + width > world.Right) + { + x = world.Right - width; + } + + if (y < world.Y) + { + y = world.Y; + } + else if (y + height > world.Bottom) + { + y = world.Bottom - height; + } + + // Set new viewport + this.Viewport = new RectangleF(x, y, width, height); + + this.ClipMargin = this.Viewport.Width * 0.05f; + + return true; + } + + public void Reset() + { + this.Viewport = this.world; + this.Level = 1; + } + + public void WorldToScreen(ref PointF pt) + { + pt.X = (pt.X - Viewport.X) / Viewport.Width * screen.Width; + pt.Y = (1 - (pt.Y - Viewport.Y) / Viewport.Height) * screen.Height; + } + + public void ScreenToWorld(ref PointF pt) + { + pt.X = Viewport.X + Viewport.Width * pt.X; + pt.Y = Viewport.Y + Viewport.Height * (1 - pt.Y); + } + + [Obsolete] + public PointF WorldToScreen(float x, float y) + { + return new PointF((x - Viewport.X) / Viewport.Width * screen.Width, + (1 - (y - Viewport.Y) / Viewport.Height) * screen.Height); + } + + [Obsolete] + public PointF ScreenToWorld(float x, float y) + { + return new PointF(Viewport.X + Viewport.Width * x, + Viewport.Y + Viewport.Height * (1 - y)); + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Properties/AssemblyInfo.cs b/Triangle.NET/Triangle.Rendering/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2c12083 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Triangle.Rendering")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Triangle.Rendering")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("14f2491b-ee62-41e4-ab93-206540302ece")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Triangle.NET/Triangle.Rendering/RenderContext.cs b/Triangle.NET/Triangle.Rendering/RenderContext.cs new file mode 100644 index 0000000..50ef6dd --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/RenderContext.cs @@ -0,0 +1,142 @@ + +namespace TriangleNet.Rendering +{ + using System.Collections.Generic; + using System.Linq; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Tools; + + /// + /// The RenderContext class brings all the rendering parts together. + /// + public class RenderContext : IRenderContext + { + private ColorManager colorManager; + private BoundingBox bounds; + private Projection zoom; + private IMesh mesh; + + private List renderLayers; + + public RenderContext(Projection zoom, ColorManager colorManager) + { + bounds = new BoundingBox(); + + renderLayers = new List(6); + + renderLayers.Add(new RenderLayer()); // 0 = mesh (filled) + renderLayers.Add(new RenderLayer()); // 1 = mesh (wireframe) + renderLayers.Add(new RenderLayer()); // 2 = polygon + renderLayers.Add(new RenderLayer()); // 3 = points + renderLayers.Add(new RenderLayer()); // 4 = voronoi overlay + renderLayers.Add(new RenderLayer()); // 5 = vector field + renderLayers.Add(new RenderLayer()); // 6 = contour lines + + this.zoom = zoom; + this.colorManager = colorManager; + } + + public ColorManager ColorManager + { + get { return colorManager; } + } + + public BoundingBox Bounds + { + get { return bounds; } + } + + public IList RenderLayers + { + get { return renderLayers; } + } + + public Projection Zoom + { + get { return zoom; } + } + + public IMesh Mesh + { + get { return mesh; } + } + + public bool HasData + { + get + { + return renderLayers.Any(layer => !layer.IsEmpty); + } + } + + public void Add(IPolygon data) + { + foreach (var layer in RenderLayers) + { + layer.Reset(true); + } + + this.bounds = RenderLayers[2].SetPoints(data); + this.zoom.Initialize(bounds); + + RenderLayers[2].SetPolygon(data); + RenderLayers[2].IsActive = true; + + RenderLayers[3].SetPoints(RenderLayers[2].Points); + RenderLayers[3].IsActive = true; + } + + public void Add(IMesh data, bool reset) + { + foreach (var layer in RenderLayers) + { + layer.Reset(reset); + } + + // Save reference to mesh. + this.mesh = data; + + this.bounds = RenderLayers[1].SetPoints(data); + this.zoom.Initialize(bounds); + + RenderLayers[1].SetMesh(data, false); + RenderLayers[1].IsActive = true; + + RenderLayers[2].SetPoints(RenderLayers[1].Points); + RenderLayers[2].SetPolygon(data); + RenderLayers[2].IsActive = true; + + RenderLayers[3].SetPoints(RenderLayers[1].Points); + RenderLayers[3].IsActive = true; + } + + // Voronoi + public void Add(IVoronoi voronoi, bool reset) + { + RenderLayers[4].SetPoints(voronoi); + RenderLayers[4].SetMesh(voronoi); + RenderLayers[4].IsActive = true; + } + + public void Add(float[] data) + { + // Add function values for filled mesh. + RenderLayers[0].SetPoints(RenderLayers[1].Points); + RenderLayers[0].SetMesh(this.mesh, true); + RenderLayers[0].AttachLayerData(data, colorManager.ColorMap); + + RenderLayers[0].IsActive = true; + } + + public void Add(int[] data) + { + // Add partition data for filled mesh. + RenderLayers[0].SetPoints(RenderLayers[1].Points); + RenderLayers[0].SetMesh(this.mesh, true); + RenderLayers[0].AttachLayerData(data); + + RenderLayers[0].IsActive = true; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/RenderLayer.cs b/Triangle.NET/Triangle.Rendering/RenderLayer.cs new file mode 100644 index 0000000..f85999f --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/RenderLayer.cs @@ -0,0 +1,187 @@ + +namespace TriangleNet.Rendering +{ + using System.Drawing; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Rendering.Buffer; + using TriangleNet.Rendering.Util; + using TriangleNet.Tools; + + public class RenderLayer : IRenderLayer + { + int count; + + protected IBuffer points; + protected IBuffer indices; + + protected IBuffer partition; + protected IBuffer colors; + + public RenderLayer() + { + this.IsActive = false; + } + + public int Count + { + get { return count; } + } + + public IBuffer Points + { + get { return points; } + set { points = value; } + } + + public IBuffer Indices + { + get { return indices; } + } + + public IBuffer Partition + { + get { return partition; } + } + + public IBuffer Colors + { + get { return colors; } + } + + public bool IsActive { get; set; } + + public bool IsEmpty + { + get { return (points == null || points.Count == 0); } + } + + public void Reset(bool clear) + { + if (clear) + { + count = 0; + points = null; + } + + indices = null; + partition = null; + colors = null; + } + + public BoundingBox SetPoints(IBuffer buffer) + { + BoundingBox bounds = new BoundingBox(); + + if (points != null && points.Count < buffer.Count) + { + count = points.Count / points.Size; + } + else + { + count = buffer.Count / buffer.Size; + } + + this.points = buffer; + + return bounds; + } + + public BoundingBox SetPoints(IPolygon poly) + { + BoundingBox bounds = new BoundingBox(); + + points = BufferHelper.CreateVertexBuffer(poly.Points, ref bounds); + count = points.Count / points.Size; + + return bounds; + } + + public BoundingBox SetPoints(IMesh mesh) + { + BoundingBox bounds = new BoundingBox(); + + points = BufferHelper.CreateVertexBuffer(mesh.Vertices, ref bounds); + count = points.Count / points.Size; + + return bounds; + } + + public BoundingBox SetPoints(IVoronoi voronoi) + { + BoundingBox bounds = new BoundingBox(); + + points = BufferHelper.CreateVertexBuffer(voronoi.Points, ref bounds); + count = points.Count / points.Size; + + return bounds; + } + + public void SetPolygon(IPolygon poly) + { + indices = BufferHelper.CreateIndexBuffer(poly.Segments, 2); + } + + public void SetPolygon(IMesh mesh) + { + indices = BufferHelper.CreateIndexBuffer(mesh.Segments, 2); + } + + public void SetMesh(IVoronoi voronoi) + { + indices = BufferHelper.CreateIndexBuffer(voronoi.Edges, 2); + } + + public void SetMesh(IMesh mesh, bool elements) + { + mesh.Renumber(); + + if (!elements) + { + indices = BufferHelper.CreateIndexBuffer(mesh.Edges, 2); + } + + if (elements || indices.Count == 0) + { + indices = BufferHelper.CreateIndexBuffer(mesh.Triangles, 3); + } + } + + // TODO: remove colormap argument + public void AttachLayerData(float[] values, ColorMap colormap) + { + int length = values.Length; + + Color[] data = new Color[length]; + + double min = double.MaxValue; + double max = double.MinValue; + + // Find min and max of given values. + for (int i = 0; i < length; i++) + { + if (values[i] < min) + { + min = values[i]; + } + + if (values[i] > max) + { + max = values[i]; + } + } + + for (int i = 0; i < length; i++) + { + data[i] = colormap.GetColor(values[i], min, max); + } + + colors = new ColorBuffer(data, 1); + } + + public void AttachLayerData(int[] partition) + { + this.partition = new IndexBuffer(partition, 1); + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/RenderManager.cs b/Triangle.NET/Triangle.Rendering/RenderManager.cs new file mode 100644 index 0000000..8cdfaf9 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/RenderManager.cs @@ -0,0 +1,131 @@ + +namespace TriangleNet.Rendering +{ + using System.Windows.Forms; + using TriangleNet.Geometry; + using TriangleNet.Meshing; + using TriangleNet.Rendering.GDI; + using TriangleNet.Tools; + using System.Collections.Generic; + using TriangleNet.Rendering.Util; + + public class RenderManager + { + IRenderControl control; + IRenderContext context; + IRenderer renderer; + Projection zoom; + + public IRenderControl Control + { + get { return control; } + } + + public IRenderContext Context + { + get { return context; } + } + + public RenderManager() + { + } + + public RenderManager(IRenderControl control) + { + Initialize(control); + } + + public RenderManager(IRenderControl control, IRenderer renderer) + { + Initialize(control, renderer); + } + + public void Initialize(IRenderControl control) + { + Initialize(control, new LayerRenderer()); + } + + public void Initialize(IRenderControl control, IRenderer renderer) + { + this.zoom = new Projection(control.ClientRectangle); + + this.context = new RenderContext(zoom, ColorManager.Default()); + + this.renderer = renderer; + this.renderer.Context = context; + + this.control = control; + this.control.Initialize(); + this.control.Renderer = renderer; + } + + public bool TryCreateControl(string assemblyName, IEnumerable dependencies, + out IRenderControl control) + { + if (!ReflectionHelper.TryCreateControl(assemblyName, dependencies, out control)) + { + return false; + } + + return control is Control; + } + + public void Resize() + { + control.HandleResize(); + } + + public void Click(float x, float y, MouseButtons button) + { + control.HandleMouseClick(x, y, button); + } + + public void Zoom(float x, float y, int delta) + { + control.HandleMouseWheel(x, y, delta); + } + + public void Set(IPolygon data, bool refresh = true) + { + context.Add(data); + + if (refresh) + { + control.Refresh(); + } + } + + public void Set(IMesh data, bool reset, bool refresh = true) + { + context.Add(data, reset); + + if (refresh) + { + control.Refresh(); + } + } + + // Voronoi + public void Set(IVoronoi voronoi, bool reset, bool refresh = true) + { + context.Add(voronoi, reset); + + if (refresh) + { + control.Refresh(); + } + } + + public void Update(float[] values) + { + context.Add(values); + control.Refresh(); + } + + public void Update(int[] partition) + { + context.Add(partition); + control.Refresh(); + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj b/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj new file mode 100644 index 0000000..f0b5bae --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj @@ -0,0 +1,91 @@ + + + + + Debug + AnyCPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B} + Library + Properties + TriangleNet.Rendering + Triangle.Rendering + v4.0 + 512 + SAK + SAK + SAK + SAK + Client + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + + + + + + + + + + + + + + + + {f7907a0a-b75f-400b-9e78-bfad00db4d6b} + Triangle + + + + + \ No newline at end of file diff --git a/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj.vspscc b/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj.vspscc new file mode 100644 index 0000000..feffdec --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Triangle.Rendering.csproj.vspscc @@ -0,0 +1,10 @@ +"" +{ +"FILE_VERSION" = "9237" +"ENLISTMENT_CHOICE" = "NEVER" +"PROJECT_FILE_RELATIVE_PATH" = "" +"NUMBER_OF_EXCLUDED_FILES" = "0" +"ORIGINAL_PROJECT_FILE_PATH" = "" +"NUMBER_OF_NESTED_PROJECTS" = "0" +"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" +} diff --git a/Triangle.NET/Triangle.Rendering/Util/BufferHelper.cs b/Triangle.NET/Triangle.Rendering/Util/BufferHelper.cs new file mode 100644 index 0000000..1b517fd --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Util/BufferHelper.cs @@ -0,0 +1,142 @@ + +namespace TriangleNet.Rendering.Util +{ + using System.Collections.Generic; + using TriangleNet.Data; + using TriangleNet.Geometry; + using TriangleNet.Rendering.Buffer; + + internal static class BufferHelper + { + public static IBuffer CreateVertexBuffer(double[] points, ref BoundingBox bounds) + { + int length = points.Length; + + var buffer = new VertexBuffer(length); + + bounds.Reset(); + + var data = buffer.Data; + + float x, y; + + length = length >> 1; + + for (int i = 0; i < length; i++) + { + x = (float)points[2 * i]; + y = (float)points[2 * i + 1]; + + data[2 * i] = x; + data[2 * i + 1] = y; + + bounds.Update(x, y); + } + + return buffer as IBuffer; + } + + public static IBuffer CreateVertexBuffer(Point[] points, ref BoundingBox bounds) + { + var buffer = new VertexBuffer(2 * points.Length); + + bounds.Reset(); + + var data = buffer.Data; + + float x, y; + + int i = 0; + + foreach (var p in points) + { + x = (float)p.X; + y = (float)p.Y; + + data[2 * i] = x; + data[2 * i + 1] = y; + + bounds.Update(x, y); + + i++; + } + + return buffer as IBuffer; + } + + public static IBuffer CreateVertexBuffer(ICollection points, ref BoundingBox bounds) + { + var buffer = new VertexBuffer(2 * points.Count); + + bounds.Reset(); + + var data = buffer.Data; + + int i = 0; + + foreach (var p in points) + { + data[2 * i] = (float)p.X; + data[2 * i + 1] = (float)p.Y; + + bounds.Update(p.X, p.Y); + + i++; + } + + return buffer as IBuffer; + } + + public static IBuffer CreateIndexBuffer(IList segments, int size) + { + var buffer = new IndexBuffer(size * segments.Count, size); + + var data = buffer.Data; + + int i = 0; + + foreach (var e in segments) + { + data[size * i + 0] = e.P0; + data[size * i + 1] = e.P1; + + i++; + } + + return buffer as IBuffer; + } + + public static IBuffer CreateIndexBuffer(IEnumerable edges, int size) + { + var data = new List(); + + foreach (var e in edges) + { + data.Add(e.P0); + data.Add(e.P1); + } + + return new IndexBuffer(data.ToArray(), size) as IBuffer; + } + + public static IBuffer CreateIndexBuffer(ICollection elements, int size) + { + var buffer = new IndexBuffer(size * elements.Count, size); + + var data = buffer.Data; + + int i = 0; + + foreach (var e in elements) + { + data[size * i + 0] = e.P0; + data[size * i + 1] = e.P1; + data[size * i + 2] = e.P2; + + i++; + } + + return buffer as IBuffer; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Util/ColorMap.cs b/Triangle.NET/Triangle.Rendering/Util/ColorMap.cs new file mode 100644 index 0000000..ca53def --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Util/ColorMap.cs @@ -0,0 +1,112 @@ + +namespace TriangleNet.Rendering.Util +{ + using System; + using System.Drawing; + + public class ColorMap + { + #region Colormap definitions + + public static ColorMap Jet(int size) + { + ColorMap map = new ColorMap(size); + float v, step = 1.0f / (size - 1); + float[] rgb = new float[3]; + + for (int i = 0; i < size; i += 1) + { + v = 4 * i * step; + + rgb[0] = Math.Min(v - 1.5f, 4.5f - v); + rgb[1] = Math.Min(v - 0.5f, 3.5f - v); + rgb[2] = Math.Min(v + 0.5f, 2.5f - v); + + Clamp(rgb, 0.0f, 1.0f); + + map.colors[size - i - 1] = ColorFromRgb(rgb[0], rgb[1], rgb[2]); + } + + return map; + } + + public static ColorMap Hot(int size) + { + ColorMap map = new ColorMap(size); + float v, step = 1.0f / (size - 1); + float[] rgb = new float[3]; + + for (int i = 0; i < size; i += 1) + { + v = 2.5f * i * step; + + rgb[0] = v; + rgb[1] = v - 1; + rgb[2] = 2 * v - 4; + + Clamp(rgb, 0.0f, 1.0f); + + map.colors[i] = ColorFromRgb(rgb[0], rgb[1], rgb[2]); + } + + return map; + } + + #endregion + + #region Helper + + private static Color ColorFromRgb(float r, float g, float b) + { + byte max = byte.MaxValue; + + return Color.FromArgb((byte)(r * max), (byte)(g * max), (byte)(b * max)); + } + + private static void Clamp(float[] values, float min, float max) + { + int n = values.Length; + + for (int i = 0; i < n; i += 1) + { + values[i] = Math.Min(max, Math.Max(min, values[i])); + } + } + + private static int Clamp(int index, int max) + { + if (index < 0) + { + index = 0; + } + else if (index > max) + { + index = max; + } + + return index; + } + + #endregion + + private Color[] colors; + + private ColorMap(int size) + { + this.colors = new Color[size]; + } + + public ColorMap(Color[] colors) + { + this.colors = colors; + } + + public Color GetColor(double value, double min, double max) + { + int n = this.colors.Length; + int i = (int)Math.Floor(n * (max - value) / (max - min)); + + return this.colors[Clamp(i, n - 1)]; + } + } +} diff --git a/Triangle.NET/Triangle.Rendering/Util/ReflectionHelper.cs b/Triangle.NET/Triangle.Rendering/Util/ReflectionHelper.cs new file mode 100644 index 0000000..528fea3 --- /dev/null +++ b/Triangle.NET/Triangle.Rendering/Util/ReflectionHelper.cs @@ -0,0 +1,81 @@ + +namespace TriangleNet.Rendering.Util +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + + internal static class ReflectionHelper + { + public static bool TryCreateControl(string assemblyName, IEnumerable dependencies, + out IRenderControl control) + { + return TryCreateControl(assemblyName, dependencies, null, out control); + } + + public static bool TryCreateControl(string assemblyName, IEnumerable dependencies, + string className, out IRenderControl control) + { + control = null; + + if (!FilesExist(assemblyName, dependencies)) + { + return false; + } + + assemblyName = Path.GetFileNameWithoutExtension(assemblyName); + + // Try create render control instance. + try + { + // Load the assembly into the current application domain. + var assembly = Assembly.Load(assemblyName); + + // Get all types implementing the IRenderControl interface. + var type = typeof(IRenderControl); + var matches = assembly.GetTypes().Where(s => type.IsAssignableFrom(s)); + + var match = string.IsNullOrEmpty(className) ? matches.FirstOrDefault() + : matches.Where(s => s.Name == className).FirstOrDefault(); + + if (match != null) + { + // Create an instance. + control = (IRenderControl)Activator.CreateInstance(match); + } + } + catch (Exception) + { + return false; + } + + // Return true if render control was successfully created. + return (control != null); + } + + private static bool FilesExist(string assemblyName, IEnumerable dependencies) + { + // Check if assembly exists + if (!File.Exists(assemblyName)) + { + return false; + } + + // Check if dependencies exists + if (dependencies != null) + { + foreach (var item in dependencies) + { + if (!File.Exists(item)) + { + return false; + } + } + } + + return true; + } + } +} diff --git a/Triangle.NET/Triangle.sln b/Triangle.NET/Triangle.sln index f00acb1..ac37d78 100644 --- a/Triangle.NET/Triangle.sln +++ b/Triangle.NET/Triangle.sln @@ -1,15 +1,17 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Triangle", "Triangle\Triangle.csproj", "{F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mesh Explorer", "TestApp\Mesh Explorer.csproj", "{336AAF8A-5316-4303-9E73-5E38BD0B28AF}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MeshRenderer.Core", "MeshRenderer.Core\MeshRenderer.Core.csproj", "{9C5040DA-C739-43A1-8540-E6BD3ED6DB55}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Triangle.Rendering", "Triangle.Rendering\Triangle.Rendering.csproj", "{41022E0E-BD0F-439E-BC3A-AABB1B43471B}" +EndProject Global GlobalSection(TeamFoundationVersionControl) = preSolution - SccNumberOfProjects = 4 + SccNumberOfProjects = 5 SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} SccTeamFoundationServer = https://tfs.codeplex.com/tfs/tfs06 SccLocalPath0 = . @@ -22,6 +24,9 @@ Global SccProjectUniqueName3 = MeshRenderer.Core\\MeshRenderer.Core.csproj SccProjectName3 = MeshRenderer.Core SccLocalPath3 = MeshRenderer.Core + SccProjectUniqueName4 = Triangle.Rendering\\Triangle.Rendering.csproj + SccProjectName4 = Triangle.Rendering + SccLocalPath4 = Triangle.Rendering EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -62,6 +67,16 @@ Global {9C5040DA-C739-43A1-8540-E6BD3ED6DB55}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {9C5040DA-C739-43A1-8540-E6BD3ED6DB55}.Release|Mixed Platforms.Build.0 = Release|Any CPU {9C5040DA-C739-43A1-8540-E6BD3ED6DB55}.Release|x86.ActiveCfg = Release|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Debug|x86.ActiveCfg = Debug|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Any CPU.Build.0 = Release|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {41022E0E-BD0F-439E-BC3A-AABB1B43471B}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Triangle.NET/Triangle/Geometry/Rectangle.cs b/Triangle.NET/Triangle/Geometry/Rectangle.cs index 63f616a..b0df58a 100644 --- a/Triangle.NET/Triangle/Geometry/Rectangle.cs +++ b/Triangle.NET/Triangle/Geometry/Rectangle.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------- -// +// // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ // // ----------------------------------------------------------------------- @@ -150,7 +150,7 @@ namespace TriangleNet.Geometry /// Return true, if bounding box contains given point. public bool Contains(Point pt) { - return ((pt.x >= xmin) && (pt.x <= xmax) && (pt.y >= ymin) && (pt.y <= ymax)); + return ((pt.X >= xmin) && (pt.X <= xmax) && (pt.Y >= ymin) && (pt.Y <= ymax)); } /// diff --git a/Triangle.NET/Triangle/Log.cs b/Triangle.NET/Triangle/Log.cs index 41b2580..b5e577b 100644 --- a/Triangle.NET/Triangle/Log.cs +++ b/Triangle.NET/Triangle/Log.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------- -// +// // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ // // ----------------------------------------------------------------------- diff --git a/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs b/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs index fc06e39..0d4c716 100644 --- a/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs +++ b/Triangle.NET/Triangle/Meshing/Algorithm/Incremental.cs @@ -42,7 +42,7 @@ namespace TriangleNet.Meshing.Algorithm if (Log.Verbose) { Log.Instance.Warning("A duplicate vertex appeared and was ignored.", - "Incremental.IncrementalDelaunay()"); + "Incremental.Triangulate()"); } v.type = VertexType.UndeadVertex; mesh.undeads++; diff --git a/Triangle.NET/Triangle/Meshing/GenericMesher.cs b/Triangle.NET/Triangle/Meshing/GenericMesher.cs index b849eb0..e041155 100644 --- a/Triangle.NET/Triangle/Meshing/GenericMesher.cs +++ b/Triangle.NET/Triangle/Meshing/GenericMesher.cs @@ -78,6 +78,19 @@ namespace TriangleNet.Meshing return mesh; } + /// + /// Generates a structured mesh with bounds (0, 0, width, height). + /// + /// Width of the mesh (must be > 0). + /// Height of the mesh (must be > 0). + /// Number of segments in x direction. + /// Number of segments in y direction. + /// Mesh + public IMesh StructurdMesh(double width, double height, int nx, int ny) + { + return StructurdMesh(new Rectangle(0.0, 0.0, width, height), nx, ny); + } + /// /// Generates a structured mesh. /// diff --git a/Triangle.NET/Triangle/Meshing/IMesh.cs b/Triangle.NET/Triangle/Meshing/IMesh.cs index f72260b..33bb608 100644 --- a/Triangle.NET/Triangle/Meshing/IMesh.cs +++ b/Triangle.NET/Triangle/Meshing/IMesh.cs @@ -15,6 +15,7 @@ namespace TriangleNet.Meshing Rectangle Bounds { get; } + void Renumber(); void Refine(QualityOptions quality); } } diff --git a/Triangle.NET/Triangle/RobustPredicates.cs b/Triangle.NET/Triangle/RobustPredicates.cs index dba3243..66ba46b 100644 --- a/Triangle.NET/Triangle/RobustPredicates.cs +++ b/Triangle.NET/Triangle/RobustPredicates.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------- -// +// // Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html // Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/ // diff --git a/Triangle.NET/Triangle/Tools/BoundedVoronoi.cs b/Triangle.NET/Triangle/Tools/BoundedVoronoi.cs index efbe1ea..fa07383 100644 --- a/Triangle.NET/Triangle/Tools/BoundedVoronoi.cs +++ b/Triangle.NET/Triangle/Tools/BoundedVoronoi.cs @@ -70,6 +70,11 @@ namespace TriangleNet.Tools get { return regions; } } + public IEnumerable Edges + { + get { return EnumerateEdges(); } + } + /// /// Computes the bounded voronoi diagram. /// @@ -644,5 +649,41 @@ namespace TriangleNet.Tools // Success. return true; } + + // TODO: Voronoi enumerate edges + + private IEnumerable EnumerateEdges() + { + // Copy edges + Point first, last; + var edges = new List(this.Regions.Count * 2); + foreach (var region in this.Regions) + { + first = null; + last = null; + + foreach (var pt in region.Vertices) + { + if (first == null) + { + first = pt; + last = pt; + } + else + { + edges.Add(new Edge(last.ID, pt.ID)); + + last = pt; + } + } + + if (region.Bounded && first != null) + { + edges.Add(new Edge(last.ID, first.ID)); + } + } + + return edges; + } } } diff --git a/Triangle.NET/Triangle/Tools/IVoronoi.cs b/Triangle.NET/Triangle/Tools/IVoronoi.cs index 3b3c835..e50ca29 100644 --- a/Triangle.NET/Triangle/Tools/IVoronoi.cs +++ b/Triangle.NET/Triangle/Tools/IVoronoi.cs @@ -23,5 +23,10 @@ namespace TriangleNet.Tools /// Gets the list of Voronoi regions. /// ICollection Regions { get; } + + /// + /// Gets the list of edges. + /// + IEnumerable Edges { get; } } } diff --git a/Triangle.NET/Triangle/Tools/Voronoi.cs b/Triangle.NET/Triangle/Tools/Voronoi.cs index d48e4f4..23b7ebe 100644 --- a/Triangle.NET/Triangle/Tools/Voronoi.cs +++ b/Triangle.NET/Triangle/Tools/Voronoi.cs @@ -59,6 +59,11 @@ namespace TriangleNet.Tools get { return regions.Values; } } + public IEnumerable Edges + { + get { return EnumerateEdges(); } + } + /// /// Gets the Voronoi diagram as raw output data. /// @@ -336,5 +341,41 @@ namespace TriangleNet.Tools return true; } + + // TODO: Voronoi enumerate edges + + private IEnumerable EnumerateEdges() + { + // Copy edges + Point first, last; + var edges = new List(this.Regions.Count * 2); + foreach (var region in this.Regions) + { + first = null; + last = null; + + foreach (var pt in region.Vertices) + { + if (first == null) + { + first = pt; + last = pt; + } + else + { + edges.Add(new Edge(last.ID, pt.ID)); + + last = pt; + } + } + + if (region.Bounded && first != null) + { + edges.Add(new Edge(last.ID, first.ID)); + } + } + + return edges; + } } } diff --git a/Triangle.NET/Triangle/Triangle.csproj b/Triangle.NET/Triangle/Triangle.csproj index 4827075..ed444a4 100644 --- a/Triangle.NET/Triangle/Triangle.csproj +++ b/Triangle.NET/Triangle/Triangle.csproj @@ -108,9 +108,7 @@ - - - +