diff --git a/Triangle.NET/TestApp/Controls/ButtonDark.cs b/Triangle.NET/TestApp/Controls/ButtonDark.cs
new file mode 100644
index 0000000..83daa9a
--- /dev/null
+++ b/Triangle.NET/TestApp/Controls/ButtonDark.cs
@@ -0,0 +1,185 @@
+
+
+namespace TestApp.Controls
+{
+ using System;
+ using System.Collections.Generic;
+ using System.ComponentModel;
+ using System.Drawing;
+ using System.Drawing.Drawing2D;
+ using System.Text;
+ using System.Windows.Forms;
+
+ public class ButtonDark : Button
+ {
+ #region Designer
+
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ }
+
+ #endregion
+
+ #endregion
+
+ enum eButtonState { Normal, MouseOver, Down }
+ eButtonState m_State = eButtonState.Normal;
+
+ // make sure the control is invalidated(repainted) when the text is changed
+ public override string Text
+ {
+ get { return base.Text; }
+ set { base.Text = value; this.Invalidate(); }
+ }
+
+ //--------------------------------------------------------------------------------
+ public ButtonDark()
+ {
+ InitializeComponent();
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
+ e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
+
+ float down = 0.0f;
+
+ // Colors and brushes
+ Pen brushBorder = null;
+ LinearGradientMode mode = LinearGradientMode.Vertical;
+ LinearGradientBrush brushOuter = null;
+ LinearGradientBrush brushInner = null;
+
+ Rectangle newRect = new Rectangle(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width - 1, ClientRectangle.Height - 1);
+ Color text_color = Color.White;
+
+ if (Enabled)
+ {
+ if (base.Focused)
+ brushBorder = new Pen(Color.FromArgb(24, 24, 24), 1f);
+ else
+ brushBorder = new Pen(Color.FromArgb(56, 56, 56), 1f);
+
+ switch (m_State)
+ {
+ case eButtonState.Normal:
+ brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(123, 123, 123), Color.FromArgb(77, 77, 77), mode);
+ brushInner = new LinearGradientBrush(newRect, Color.FromArgb(104, 104, 104), Color.FromArgb(71, 71, 71), mode);
+ e.Graphics.FillRectangle(brushOuter, newRect);
+ newRect.Inflate(-1, -1);
+ e.Graphics.FillRectangle(brushInner, newRect);
+ newRect.Inflate(1, 1);
+ break;
+
+ case eButtonState.MouseOver:
+ brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(140, 140, 140), Color.FromArgb(87, 87, 87), mode);
+ brushInner = new LinearGradientBrush(newRect, Color.FromArgb(118, 118, 118), Color.FromArgb(81, 81, 81), mode);
+ e.Graphics.FillRectangle(brushOuter, newRect);
+ newRect.Inflate(-1, -1);
+ e.Graphics.FillRectangle(brushInner, newRect);
+ newRect.Inflate(1, 1);
+ break;
+
+ case eButtonState.Down:
+ down = 1.0f;
+ brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(108, 108, 108), Color.FromArgb(68, 68, 68), mode);
+ brushInner = new LinearGradientBrush(newRect, Color.FromArgb(92, 92, 92), Color.FromArgb(62, 62, 62), mode);
+ e.Graphics.FillRectangle(brushOuter, newRect);
+ newRect.Inflate(-1, -1);
+ e.Graphics.FillRectangle(brushInner, newRect);
+ newRect.Inflate(1, 1);
+ break;
+ }
+
+ e.Graphics.DrawRectangle(brushBorder, newRect);
+ }
+ else
+ {
+ text_color = Color.FromArgb(110, 110, 110);
+ brushBorder = new Pen(Color.FromArgb(48, 48, 48), 1f);
+ brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(82, 82, 82), Color.FromArgb(67, 67, 67), mode);
+ brushInner = new LinearGradientBrush(newRect, Color.FromArgb(76, 76, 76), Color.FromArgb(65, 65, 65), mode);
+ e.Graphics.FillRectangle(brushOuter, newRect);
+ newRect.Inflate(-1, -1);
+ e.Graphics.FillRectangle(brushInner, newRect);
+ newRect.Inflate(1, 1);
+ e.Graphics.DrawRectangle(brushBorder, newRect);
+ }
+
+
+ string largetext = this.Text;
+
+ SizeF szL = e.Graphics.MeasureString(this.Text, base.Font, this.Width);
+ if (Enabled)
+ {
+ e.Graphics.DrawString(largetext, base.Font, Brushes.Black,
+ new RectangleF(new PointF((this.Width - szL.Width) / 2, (this.Height - szL.Height) / 2 + 1 + down), szL));
+ }
+ e.Graphics.DrawString(largetext, base.Font, new SolidBrush(text_color),
+ new RectangleF(new PointF((this.Width - szL.Width) / 2, (this.Height - szL.Height) / 2 + down), szL));
+
+ brushOuter.Dispose();
+ brushInner.Dispose();
+ brushBorder.Dispose();
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnMouseLeave(System.EventArgs e)
+ {
+ m_State = eButtonState.Normal;
+ this.Invalidate();
+ base.OnMouseLeave(e);
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnMouseEnter(System.EventArgs e)
+ {
+ m_State = eButtonState.MouseOver;
+ this.Invalidate();
+ base.OnMouseEnter(e);
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
+ {
+ m_State = eButtonState.MouseOver;
+ this.Invalidate();
+ base.OnMouseUp(e);
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
+ {
+ m_State = eButtonState.Down;
+ this.Invalidate();
+ base.OnMouseDown(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Triangle.NET/TestApp/Controls/CheckBoxDark.cs b/Triangle.NET/TestApp/Controls/CheckBoxDark.cs
new file mode 100644
index 0000000..a7b0cfd
--- /dev/null
+++ b/Triangle.NET/TestApp/Controls/CheckBoxDark.cs
@@ -0,0 +1,206 @@
+// -----------------------------------------------------------------------
+//
+// TODO: Update copyright text.
+//
+// -----------------------------------------------------------------------
+
+namespace TestApp.Controls
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Drawing;
+ using System.Drawing.Drawing2D;
+ using System.Windows.Forms;
+
+ ///
+ /// TODO: Update summary.
+ ///
+ public class CheckBoxDark : ButtonBase
+ {
+ #region Designer
+
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ }
+
+ #endregion
+
+ #endregion
+
+ enum eButtonState { Normal, MouseOver, Down }
+ eButtonState m_State = eButtonState.Normal;
+
+ // make sure the control is invalidated(repainted) when the text is changed
+ public override string Text
+ {
+ get { return base.Text; }
+ set { base.Text = value; this.Invalidate(); }
+ }
+
+ int boxSize = 13;
+
+ bool isChecked = false;
+ public bool Checked
+ {
+ get { return isChecked; }
+ set { isChecked = value; this.Invalidate(); }
+ }
+
+ //--------------------------------------------------------------------------------
+ public CheckBoxDark()
+ {
+ this.BackColor = Color.FromArgb(76, 76, 76);
+ InitializeComponent();
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ //base.OnPaint(e);
+ e.Graphics.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);
+
+ Pen checkMark = new Pen(Color.White, 1.8f);
+ checkMark.StartCap = LineCap.Round;
+ checkMark.EndCap = LineCap.Round;
+
+ // Colors and brushes
+ Pen brushBorder = null;
+ LinearGradientMode mode = LinearGradientMode.Vertical;
+ LinearGradientBrush brushOuter = null;
+ LinearGradientBrush brushInner = null;
+
+ int y = (this.Height - boxSize) / 2;
+
+ Rectangle newRect = new Rectangle(1, y, boxSize, boxSize);
+ Color text_color = Color.White;
+
+ if (Enabled)
+ {
+ if (base.Focused)
+ brushBorder = new Pen(Color.FromArgb(60, 60, 60), 1f);
+ else
+ brushBorder = new Pen(Color.FromArgb(38, 38, 38), 1f);
+
+ brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(82, 82, 82), Color.FromArgb(96, 96, 96), mode);
+ e.Graphics.FillRectangle(brushOuter, newRect);
+
+ newRect = new Rectangle(2, y + 1, boxSize - 3, boxSize - 3);
+
+ switch (m_State)
+ {
+ case eButtonState.Normal:
+ brushInner = new LinearGradientBrush(newRect, Color.FromArgb(111, 111, 111), Color.FromArgb(80, 80, 80), mode);
+ e.Graphics.FillRectangle(brushInner, newRect);
+ break;
+
+ case eButtonState.MouseOver:
+ brushInner = new LinearGradientBrush(newRect, Color.FromArgb(118, 118, 118), Color.FromArgb(81, 81, 81), mode);
+ e.Graphics.FillRectangle(brushInner, newRect);
+ break;
+
+ case eButtonState.Down:
+ brushInner = new LinearGradientBrush(newRect, Color.FromArgb(92, 92, 92), Color.FromArgb(62, 62, 62), mode);
+ e.Graphics.FillRectangle(brushInner, newRect);
+ break;
+ }
+
+ e.Graphics.DrawRectangle(brushBorder, newRect);
+
+ }
+ else
+ {
+ text_color = Color.FromArgb(110, 110, 110);
+ brushBorder = new Pen(Color.FromArgb(48, 48, 48), 1f);
+ brushOuter = new LinearGradientBrush(newRect, Color.FromArgb(82, 82, 82), Color.FromArgb(67, 67, 67), mode);
+ brushInner = new LinearGradientBrush(newRect, Color.FromArgb(76, 76, 76), Color.FromArgb(65, 65, 65), mode);
+ e.Graphics.FillRectangle(brushOuter, newRect);
+ newRect.Inflate(-1, -1);
+ e.Graphics.FillRectangle(brushInner, newRect);
+ newRect.Inflate(1, 1);
+ e.Graphics.DrawRectangle(brushBorder, newRect);
+ }
+
+ e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
+ e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
+
+ if (this.isChecked)
+ {
+ e.Graphics.DrawLine(checkMark, 4, newRect.Bottom - boxSize / 2, newRect.Left + boxSize / 2.5f, newRect.Bottom - 2);
+ e.Graphics.DrawLine(checkMark, newRect.Left + boxSize / 2.6f, newRect.Bottom - 2, newRect.Right, newRect.Top);
+ }
+
+ SizeF szL = e.Graphics.MeasureString(this.Text, base.Font, this.Width);
+ e.Graphics.DrawString(this.Text, base.Font, new SolidBrush(text_color), boxSize + 4, (this.Height - szL.Height) / 2);
+
+ if (brushOuter != null) brushOuter.Dispose();
+ if (brushInner != null) brushInner.Dispose();
+ if (brushBorder != null) brushBorder.Dispose();
+ if (checkMark != null) checkMark.Dispose();
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnMouseLeave(System.EventArgs e)
+ {
+ m_State = eButtonState.Normal;
+ this.Invalidate();
+ base.OnMouseLeave(e);
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnMouseEnter(System.EventArgs e)
+ {
+ m_State = eButtonState.MouseOver;
+ this.Invalidate();
+ base.OnMouseEnter(e);
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
+ {
+ m_State = eButtonState.MouseOver;
+ this.Invalidate();
+ base.OnMouseUp(e);
+ }
+
+ //--------------------------------------------------------------------------------
+ protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
+ {
+ m_State = eButtonState.Down;
+ this.Invalidate();
+ base.OnMouseDown(e);
+ }
+
+ protected override void OnClick(EventArgs e)
+ {
+ this.isChecked = !this.isChecked;
+ this.Invalidate();
+ base.OnClick(e);
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Controls/Histogram.cs b/Triangle.NET/TestApp/Controls/Histogram.cs
new file mode 100644
index 0000000..eb65b9a
--- /dev/null
+++ b/Triangle.NET/TestApp/Controls/Histogram.cs
@@ -0,0 +1,154 @@
+// -----------------------------------------------------------------------
+//
+// TODO: Update copyright text.
+//
+// -----------------------------------------------------------------------
+
+namespace TestApp.Controls
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Drawing;
+ using System.Drawing.Drawing2D;
+ using System.Windows.Forms;
+
+ ///
+ /// TODO: Update summary.
+ ///
+ public class Histogram : Control
+ {
+ #region Designer
+
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ }
+
+ #endregion
+
+ #endregion
+
+ int[] data;
+ int max = 0;
+
+ public Histogram()
+ {
+ this.BackColor = Color.FromArgb(76, 76, 76);
+ InitializeComponent();
+ }
+
+ public void SetData(int[] data)
+ {
+ if (data != null)
+ {
+ this.data = data;
+ this.max = 0;
+
+ for (int i = 0; i < data.Length; i++)
+ {
+ if (data[i] > max)
+ {
+ max = data[i];
+ }
+ }
+
+ if (max == 0)
+ {
+ this.data = null;
+ return;
+ }
+
+ double lg10 = Math.Ceiling(Math.Log10(max)) - 1;
+ int norm = (int)Math.Pow(10, lg10);
+ int mod = -max % norm;
+ max = max + mod + norm;
+
+ this.Invalidate();
+ }
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ Graphics g = e.Graphics;
+
+ g.FillRectangle(new SolidBrush(this.BackColor), this.ClientRectangle);
+
+ SizeF s1 = g.MeasureString("180", base.Font, this.Width);
+ SizeF s2 = g.MeasureString(max.ToString(), base.Font, this.Width);
+
+ // Draw bottom rect
+ g.FillRectangle(Brushes.DimGray, s2.Width, this.Height - s1.Height - 4, this.Width, s1.Height + 4);
+
+ // Draw Histogram
+ if (data != null)
+ {
+ int n = data.Length;
+ float width = (this.Width - s2.Width) / n;
+ float value = 0;
+
+ for (int i = 0; i < data.Length; i++)
+ {
+ if (data[i] > 0)
+ {
+ // Scale to control height
+ value = (this.Height - s1.Height - 4) * data[i] / max;
+
+ g.FillRectangle(Brushes.DarkGreen,
+ s2.Width + i * width + width / 8,
+ this.Height - s1.Height - 4 - value,
+ 3 * width / 4,
+ value);
+
+ if (value > 2)
+ {
+ g.FillRectangle(Brushes.Green,
+ s2.Width + i * width + width / 8,
+ this.Height - s1.Height - 4 - value,
+ 3 * width / 4,
+ 2);
+ }
+ }
+ }
+ }
+
+ e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
+
+ // Draw data keys
+ g.DrawString("0", this.Font, Brushes.White, s2.Width + 2, this.Height - s1.Height - 2);
+ g.DrawString("90", this.Font, Brushes.White, this.Width / 2 - s1.Width / 3, this.Height - s1.Height - 2);
+ g.DrawString("180", this.Font, Brushes.White, this.Width - s1.Width - 2, this.Height - s1.Height - 2);
+
+ // Draw data values
+ if (max > 0)
+ {
+ g.DrawString(max.ToString(), this.Font, Brushes.White, 2, 10);
+ g.DrawString((max / 2).ToString(), this.Font, Brushes.White, 2, (this.Height - s1.Height) / 2);
+ }
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Controls/MeshRenderer.cs b/Triangle.NET/TestApp/Controls/MeshRenderer.cs
new file mode 100644
index 0000000..553b0d9
--- /dev/null
+++ b/Triangle.NET/TestApp/Controls/MeshRenderer.cs
@@ -0,0 +1,304 @@
+// -----------------------------------------------------------------------
+//
+// TODO: Update copyright text.
+//
+// -----------------------------------------------------------------------
+
+namespace TestApp
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Windows.Forms;
+ using System.Drawing;
+ using System.Drawing.Imaging;
+ using System.Drawing.Drawing2D;
+ using TriangleNet;
+ using System.Diagnostics;
+ using TriangleNet.IO;
+ using TestApp.Rendering;
+
+ ///
+ /// Renders a mesh using GDI.
+ ///
+ public class MeshRenderer : System.Windows.Forms.Control
+ {
+ // Rendering stuff
+ private BufferedGraphics buffer;
+ private BufferedGraphicsContext context;
+
+ Pen lines = new Pen(Color.FromArgb(30, 30, 30));
+
+ Zoom zoom;
+ MeshDataInternal data;
+ bool initialized = false;
+
+ public long RenderTime { get; private set; }
+
+ public MeshRenderer()
+ {
+ SetStyle(ControlStyles.ResizeRedraw, true);
+
+ this.BackColor = Color.Black;
+
+ zoom = new Zoom();
+ context = new BufferedGraphicsContext();
+ data = new MeshDataInternal();
+ }
+
+ public void SetData(MeshData meshdata, bool input)
+ {
+ data.SetData(meshdata);
+
+ if (input)
+ {
+ // Reset the zoom on new data
+ zoom.Initialize(this.ClientRectangle, data.Bounds);
+ }
+
+ initialized = true;
+
+ this.Render();
+ }
+
+ public void SetData(Mesh mesh, bool input)
+ {
+ data.SetData(mesh);
+
+ if (input)
+ {
+ // Reset the zoom on new data
+ zoom.Initialize(this.ClientRectangle, data.Bounds);
+ }
+
+ initialized = true;
+
+ this.Render();
+ }
+
+ public void Zoom(Point location, int delta)
+ {
+ if (!initialized) return;
+
+ if (zoom.Update(delta, location.X / (float)this.Width, location.Y / (float)this.Height))
+ {
+ // Redraw
+ this.Render();
+ }
+ }
+
+ private void IntializeBuffer()
+ {
+ if (buffer != null)
+ {
+ buffer.Dispose();
+ }
+
+ buffer = context.Allocate(Graphics.FromHwnd(this.Handle), this.ClientRectangle);
+ }
+
+ private void RenderPoints(Graphics g)
+ {
+ PointF pt;
+ PointF[] pts = data.Points;
+ int i, n;
+
+ // Draw input points
+ n = data.NumberOfInputPoints;
+ for (i = 0; i < n; i++)
+ {
+ if (zoom.ViewportContains(pts[i]))
+ {
+ pt = zoom.WorldToScreen(pts[i]);
+ g.FillEllipse(Brushes.Green, pt.X - 1.5f, pt.Y - 1.5f, 3, 3);
+ //g.FillEllipse(Brushes.Black, pt.X - 2, pt.Y - 2, 4, 4);
+ //g.DrawEllipse(Pens.Green, pt.X - 2, pt.Y - 2, 4, 4);
+ }
+ }
+
+ // Draw Steiner points
+ n = pts.Length;
+ for (; i < n; i++)
+ {
+ if (zoom.ViewportContains(pts[i]))
+ {
+ pt = zoom.WorldToScreen(pts[i]);
+ g.FillEllipse(Brushes.Peru, pt.X - 1.5f, pt.Y - 1.5f, 3, 3);
+ //g.FillEllipse(Brushes.Black, pt.X - 2, pt.Y - 2, 4, 4);
+ //g.DrawEllipse(Pens.Peru, pt.X - 2, pt.Y - 2, 4, 4);
+ }
+ }
+ }
+
+ private void RenderTriangles(Graphics g)
+ {
+ PointF p0, p1, p2;
+ PointF[] pts = data.Points;
+
+ int[] tri;
+
+ // Draw triangles
+ int n = data.Triangles.Length;
+ for (int i = 0; i < n; i++)
+ {
+ tri = data.Triangles[i];
+
+ if (zoom.ViewportContains(pts[tri[0]]) ||
+ zoom.ViewportContains(pts[tri[1]]) ||
+ zoom.ViewportContains(pts[tri[2]]))
+ {
+ p0 = zoom.WorldToScreen(pts[tri[0]]);
+ p1 = zoom.WorldToScreen(pts[tri[1]]);
+ p2 = zoom.WorldToScreen(pts[tri[2]]);
+
+ g.DrawLine(lines, p0, p1);
+ g.DrawLine(lines, p1, p2);
+ g.DrawLine(lines, p2, p0);
+ }
+ }
+ }
+
+ private void RenderEdges(Graphics g)
+ {
+ PointF p0, p1;
+ PointF[] pts = data.Points;
+
+ int[] tri;
+
+ // Draw triangles
+ int n = data.Edges.Length;
+ for (int i = 0; i < n; i++)
+ {
+ tri = data.Edges[i];
+
+ if (zoom.ViewportContains(pts[tri[0]]) ||
+ zoom.ViewportContains(pts[tri[1]]))
+ {
+ p0 = zoom.WorldToScreen(pts[tri[0]]);
+ p1 = zoom.WorldToScreen(pts[tri[1]]);
+
+ g.DrawLine(lines, p0, p1);
+ }
+ }
+ }
+
+ private void RenderSegments(Graphics g)
+ {
+ PointF p0, p1;
+ PointF[] pts = data.Points;
+
+ int[] tri;
+
+ // Draw triangles
+ int n = data.Segments.Length;
+ for (int i = 0; i < n; i++)
+ {
+ tri = data.Segments[i];
+
+ if (zoom.ViewportContains(pts[tri[0]]) ||
+ zoom.ViewportContains(pts[tri[1]]))
+ {
+ p0 = zoom.WorldToScreen(pts[tri[0]]);
+ p1 = zoom.WorldToScreen(pts[tri[1]]);
+
+ g.DrawLine(Pens.DarkBlue, p0, p1);
+ }
+ }
+ }
+
+ private void Render()
+ {
+ Graphics g = buffer.Graphics;
+ g.Clear(this.BackColor);
+
+ if (!initialized)
+ {
+ return;
+ }
+
+ g.SmoothingMode = SmoothingMode.AntiAlias;
+
+ Stopwatch stopwatch = new Stopwatch();
+ stopwatch.Start();
+
+ if (data.Edges != null)
+ {
+ this.RenderEdges(g);
+ }
+ else if (data.Triangles != null)
+ {
+ this.RenderTriangles(g);
+ }
+
+ if (data.Segments != null)
+ {
+ this.RenderSegments(g);
+ }
+
+ this.RenderPoints(g);
+
+ stopwatch.Stop();
+
+ this.RenderTime = stopwatch.ElapsedMilliseconds;
+
+ this.Invalidate();
+ }
+
+ #region Control overrides
+
+ protected override void OnPaint(PaintEventArgs pe)
+ {
+ Graphics g = buffer.Graphics;
+ g.SmoothingMode = SmoothingMode.Default;
+
+ Pen pen1 = new Pen(Color.FromArgb(82, 82, 82));
+ Pen pen2 = new Pen(Color.FromArgb(40, 40, 40));
+
+ g.DrawLine(pen1, 0, 0, this.Width, 0);
+ g.DrawLine(pen2, 0, 1, this.Width, 1);
+
+ pen1.Dispose();
+ pen2.Dispose();
+
+ buffer.Render();
+ }
+
+ protected override void OnClientSizeChanged(EventArgs e)
+ {
+ if (buffer == null) return;
+
+ // Redraw
+
+ base.OnClientSizeChanged(e);
+ }
+
+ protected override void OnMouseClick(MouseEventArgs e)
+ {
+ if (!initialized) return;
+
+ if (e.Button == MouseButtons.Middle)
+ {
+ zoom.Reset();
+ this.Render();
+ }
+
+ base.OnMouseClick(e);
+ }
+
+ protected override void OnPaintBackground(PaintEventArgs pevent)
+ {
+ // Do nothing
+ }
+
+ protected override void OnResize(EventArgs e)
+ {
+ base.OnResize(e);
+
+ IntializeBuffer();
+
+ //_coordinates.SetBounds(this.ClientRectangle);
+ }
+
+ #endregion
+ }
+}
diff --git a/Triangle.NET/TestApp/Controls/TextBoxDark.cs b/Triangle.NET/TestApp/Controls/TextBoxDark.cs
new file mode 100644
index 0000000..cdc53d7
--- /dev/null
+++ b/Triangle.NET/TestApp/Controls/TextBoxDark.cs
@@ -0,0 +1,195 @@
+// -----------------------------------------------------------------------
+//
+// TODO: Update copyright text.
+//
+// -----------------------------------------------------------------------
+
+namespace TestApp.Controls
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Drawing;
+ using System.Drawing.Drawing2D;
+ using System.Windows.Forms;
+
+ public class TextBoxDark : Control
+ {
+
+ #region Designer
+
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Component Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+
+ this.textBox = new System.Windows.Forms.TextBox();
+ this.SuspendLayout();
+ //
+ // textBox
+ //
+ this.textBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
+ this.textBox.BorderStyle = System.Windows.Forms.BorderStyle.None;
+ this.textBox.Location = new System.Drawing.Point(3, 2);
+ this.textBox.Name = "textBox";
+ this.textBox.TabIndex = 0;
+ //
+ // TextBoxDark
+ //
+ this.BackColor = System.Drawing.Color.White;
+ this.Controls.Add(this.textBox);
+ this.Cursor = Cursors.IBeam;
+ this.Size = new System.Drawing.Size(150, 22);
+ this.ResumeLayout(false);
+ this.PerformLayout();
+ }
+
+ #endregion
+
+ #endregion
+
+ TextBox textBox;
+
+ public TextBoxDark()
+ {
+ InitializeComponent();
+
+ this.MouseClick += delegate(object sender, MouseEventArgs e)
+ {
+ if (e.Button == MouseButtons.Left) textBox.Focus();
+ };
+
+ textBox.Font = this.Font;
+ textBox.Location = new Point(3, (this.Height - textBox.Height) / 2 + 1);
+ textBox.Width = this.Width - 8;
+ textBox.TextAlign = HorizontalAlignment.Right;
+ textBox.ForeColor = this.ForeColor;
+ textBox.MaxLength = 6;
+
+ textBox.GotFocus += delegate(object sender, EventArgs e)
+ {
+ textBox.ForeColor = Color.White;
+ };
+ textBox.LostFocus += delegate(object sender, EventArgs e)
+ {
+ textBox.ForeColor = this.ForeColor;
+ };
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ Rectangle rect = this.ClientRectangle;
+
+ Brush brushOuter = new LinearGradientBrush(rect, Color.FromArgb(82, 82, 82), Color.FromArgb(96, 96, 96),
+ LinearGradientMode.Vertical);
+
+ Pen brushBorder = new Pen(Color.FromArgb(38, 38, 38), 1f);
+
+ e.Graphics.FillRectangle(brushOuter, rect);
+
+ rect = new Rectangle(1, 1, this.Width - 3, this.Height - 3);
+ e.Graphics.FillRectangle(new SolidBrush(this.BackColor), rect);
+
+ e.Graphics.DrawRectangle(brushBorder, rect);
+
+ brushOuter.Dispose();
+ brushBorder.Dispose();
+
+ base.OnPaint(e);
+ }
+
+ #region Property overrides
+
+ public override Font Font
+ {
+ get
+ {
+ return base.Font;
+ }
+ set
+ {
+ textBox.Font = value;
+ base.Font = value;
+ }
+ }
+
+ public override String Text
+ {
+ get
+ {
+ return textBox.Text;
+ }
+ set
+ {
+ textBox.Text = value;
+ }
+ }
+
+ public override Color ForeColor
+ {
+ get
+ {
+ return base.ForeColor;
+ }
+ set
+ {
+ textBox.ForeColor = value;
+ base.ForeColor = value;
+ }
+ }
+
+ public override Color BackColor
+ {
+ get
+ {
+ return base.BackColor;
+ }
+ set
+ {
+ textBox.BackColor = value;
+ base.BackColor = value;
+ }
+ }
+
+ #endregion
+
+ #region Textbox properties
+
+ public HorizontalAlignment TextAlign
+ {
+ get
+ {
+ return textBox.TextAlign;
+ }
+ set
+ {
+ textBox.TextAlign = value;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Triangle.NET/TestApp/Examples.cs b/Triangle.NET/TestApp/Examples.cs
new file mode 100644
index 0000000..e175a52
--- /dev/null
+++ b/Triangle.NET/TestApp/Examples.cs
@@ -0,0 +1,120 @@
+// -----------------------------------------------------------------------
+//
+// TODO: Update copyright text.
+//
+// -----------------------------------------------------------------------
+
+namespace TestApp
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using TriangleNet;
+ using TriangleNet.IO;
+
+ ///
+ /// Code of the online examples.
+ ///
+ ///
+ public static class Examples
+ {
+ // Make sure this path points to the polygon sample data.
+ static readonly string pathToData = @"..\..\..\Data\";
+
+ ///
+ /// Generating Delaunay triangulations
+ ///
+ public static void Example1()
+ {
+ ImageWriter.SetColorSchemeLight();
+
+ // Create a mesh instance.
+ Mesh mesh = new Mesh();
+
+ // Read spiral node file and gernerate the delaunay triangulation
+ // of the point set.
+ mesh.Triangulate(pathToData + "spiral.node");
+ ImageWriter.WritePng(mesh, "spiral.png", 180);
+
+ // Read face polygon file and gernerate the delaunay triangulation
+ // of the PSLG. We reuse the mesh instance here.
+ MeshData data = FileReader.ReadFile(pathToData + "face.poly");
+ mesh.Triangulate(data);
+ ImageWriter.WritePng(mesh, "face.png", 200);
+
+ // Generate a conforming delaunay triangulation of the face polygon.
+ mesh.SetOption(Options.ConformingDelaunay, true);
+ mesh.Triangulate(data);
+ ImageWriter.WritePng(mesh, "face-CDT.png", 200);
+ }
+
+ ///
+ /// Quality meshing: angle and size constraints
+ ///
+ public static void Example2()
+ {
+ ImageWriter.SetColorSchemeLight();
+
+ // Create a mesh instance.
+ Mesh mesh = new Mesh();
+
+ // Read spiral node file and gernerate the delaunay triangulation.
+ // Set the mesh quality option to true, which will set a default
+ // minimum angle of 20 degrees.
+ MeshData data = FileReader.ReadNodeFile(pathToData + "spiral.node");
+ mesh.SetOption(Options.Quality, true);
+ mesh.Triangulate(data);
+ ImageWriter.WritePng(mesh, "spiral-Angle-20.png", 200);
+
+ // Set a minimum angle of 30 degrees.
+ mesh.SetOption(Options.MinAngle, 35);
+ mesh.Triangulate(data);
+ ImageWriter.WritePng(mesh, "spiral-Angle-35.png", 200);
+
+ // Reset the minimum angle and add a global area constraint.
+ mesh.SetOption(Options.MinAngle, 20);
+ mesh.SetOption(Options.MaxArea, 0.2);
+ mesh.Triangulate(data);
+ ImageWriter.WritePng(mesh, "spiral-Area.png", 200);
+ }
+
+ ///
+ /// Refining preexisting meshes
+ ///
+ public static void Example3()
+ {
+ ImageWriter.SetColorSchemeLight();
+
+ // Create a mesh instance.
+ Mesh mesh = new Mesh();
+
+ // Gernerate a quality delaunay triangulation of box
+ // polygon, containing the convex hull.
+ mesh.SetOption(Options.Quality, true);
+ mesh.SetOption(Options.Convex, true);
+ mesh.Triangulate(pathToData + "box.poly");
+ ImageWriter.WritePng(mesh, "box.png", 200);
+
+ // Save the current mesh to .node and .ele files
+ FileWriter.WriteNodes(mesh, "box.1.node");
+ FileWriter.WriteElements(mesh, "box.1.ele");
+
+ // Refine the mesh by setting a global area constraint.
+ mesh.Refine(0.2);
+ ImageWriter.WritePng(mesh, "box-Refine-1.png", 200);
+
+ // Refine again by setting a smaller area constraint.
+ mesh.Refine(0.05);
+ ImageWriter.WritePng(mesh, "box-Refine-2.png", 200);
+
+ // Load the previously saved box.1 mesh. Since a box.1.area
+ // file exist, the variable area constraint option is set
+ // and will be applied for refinement.
+ mesh.Load(pathToData + "box.1.node");
+ mesh.SetOption(Options.MinAngle, 0);
+ mesh.Refine();
+ ImageWriter.WritePng(mesh, "box-Refine-3.png", 200);
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Form1.Designer.cs b/Triangle.NET/TestApp/Form1.Designer.cs
new file mode 100644
index 0000000..a9b1e83
--- /dev/null
+++ b/Triangle.NET/TestApp/Form1.Designer.cs
@@ -0,0 +1,176 @@
+namespace TestApp
+{
+ partial class Form1
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.btnStatistic = new TestApp.Controls.ButtonDark();
+ this.tbNumPoints = new TestApp.Controls.TextBoxDark();
+ this.cbConvex = new TestApp.Controls.CheckBoxDark();
+ this.cbQuality = new TestApp.Controls.CheckBoxDark();
+ this.btnRun = new TestApp.Controls.ButtonDark();
+ this.btnOpen = new TestApp.Controls.ButtonDark();
+ this.btnRandPts = new TestApp.Controls.ButtonDark();
+ this.meshRenderer1 = new TestApp.MeshRenderer();
+ this.lbTime = new System.Windows.Forms.Label();
+ this.SuspendLayout();
+ //
+ // btnStatistic
+ //
+ this.btnStatistic.Location = new System.Drawing.Point(847, 12);
+ this.btnStatistic.Name = "btnStatistic";
+ this.btnStatistic.Size = new System.Drawing.Size(75, 23);
+ this.btnStatistic.TabIndex = 11;
+ this.btnStatistic.Text = "Statistic";
+ this.btnStatistic.UseVisualStyleBackColor = true;
+ this.btnStatistic.Click += new System.EventHandler(this.btnStatistic_Click);
+ //
+ // tbNumPoints
+ //
+ this.tbNumPoints.BackColor = System.Drawing.Color.DimGray;
+ this.tbNumPoints.Cursor = System.Windows.Forms.Cursors.IBeam;
+ this.tbNumPoints.ForeColor = System.Drawing.Color.LightGray;
+ this.tbNumPoints.Location = new System.Drawing.Point(244, 13);
+ this.tbNumPoints.Name = "tbNumPoints";
+ this.tbNumPoints.Size = new System.Drawing.Size(60, 21);
+ this.tbNumPoints.TabIndex = 10;
+ this.tbNumPoints.Text = "1000";
+ this.tbNumPoints.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
+ //
+ // cbConvex
+ //
+ this.cbConvex.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76)))));
+ this.cbConvex.Checked = false;
+ this.cbConvex.Location = new System.Drawing.Point(506, 12);
+ this.cbConvex.Name = "cbConvex";
+ this.cbConvex.Size = new System.Drawing.Size(110, 23);
+ this.cbConvex.TabIndex = 9;
+ this.cbConvex.Text = "Convex polygon";
+ this.cbConvex.UseVisualStyleBackColor = false;
+ //
+ // cbQuality
+ //
+ this.cbQuality.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76)))));
+ this.cbQuality.Checked = false;
+ this.cbQuality.Location = new System.Drawing.Point(622, 12);
+ this.cbQuality.Name = "cbQuality";
+ this.cbQuality.Size = new System.Drawing.Size(138, 23);
+ this.cbQuality.TabIndex = 9;
+ this.cbQuality.Text = "Produce quality mesh";
+ this.cbQuality.UseVisualStyleBackColor = false;
+ //
+ // btnRun
+ //
+ this.btnRun.Location = new System.Drawing.Point(766, 12);
+ this.btnRun.Name = "btnRun";
+ this.btnRun.Size = new System.Drawing.Size(75, 23);
+ this.btnRun.TabIndex = 8;
+ this.btnRun.Text = "Triangulate";
+ this.btnRun.UseVisualStyleBackColor = true;
+ this.btnRun.Click += new System.EventHandler(this.btnRun_Click);
+ //
+ // btnOpen
+ //
+ this.btnOpen.Location = new System.Drawing.Point(12, 12);
+ this.btnOpen.Name = "btnOpen";
+ this.btnOpen.Size = new System.Drawing.Size(110, 23);
+ this.btnOpen.TabIndex = 7;
+ this.btnOpen.Text = "Open File";
+ this.btnOpen.UseVisualStyleBackColor = true;
+ this.btnOpen.Click += new System.EventHandler(this.btnOpen_Click);
+ //
+ // btnRandPts
+ //
+ this.btnRandPts.Location = new System.Drawing.Point(128, 12);
+ this.btnRandPts.Name = "btnRandPts";
+ this.btnRandPts.Size = new System.Drawing.Size(110, 23);
+ this.btnRandPts.TabIndex = 6;
+ this.btnRandPts.Text = "Random Points";
+ this.btnRandPts.UseVisualStyleBackColor = true;
+ this.btnRandPts.Click += new System.EventHandler(this.btnRandPts_Click);
+ //
+ // meshRenderer1
+ //
+ this.meshRenderer1.BackColor = System.Drawing.Color.Black;
+ this.meshRenderer1.Dock = System.Windows.Forms.DockStyle.Bottom;
+ this.meshRenderer1.Location = new System.Drawing.Point(0, 52);
+ this.meshRenderer1.Name = "meshRenderer1";
+ this.meshRenderer1.Size = new System.Drawing.Size(934, 600);
+ this.meshRenderer1.TabIndex = 5;
+ this.meshRenderer1.Text = "meshRenderer1";
+ //
+ // lbTime
+ //
+ this.lbTime.AutoSize = true;
+ this.lbTime.ForeColor = System.Drawing.SystemColors.HotTrack;
+ this.lbTime.Location = new System.Drawing.Point(323, 17);
+ this.lbTime.Name = "lbTime";
+ this.lbTime.Size = new System.Drawing.Size(0, 13);
+ this.lbTime.TabIndex = 2;
+ //
+ // Form1
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76)))));
+ this.ClientSize = new System.Drawing.Size(934, 652);
+ this.Controls.Add(this.btnStatistic);
+ this.Controls.Add(this.tbNumPoints);
+ this.Controls.Add(this.cbConvex);
+ this.Controls.Add(this.cbQuality);
+ this.Controls.Add(this.btnRun);
+ this.Controls.Add(this.btnOpen);
+ this.Controls.Add(this.btnRandPts);
+ this.Controls.Add(this.meshRenderer1);
+ this.Controls.Add(this.lbTime);
+ this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.ForeColor = System.Drawing.Color.White;
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.MaximizeBox = false;
+ this.Name = "Form1";
+ this.Text = "Delaunay Triangulation";
+ this.Load += new System.EventHandler(this.Form1_Load);
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private MeshRenderer meshRenderer1;
+ private Controls.ButtonDark btnRandPts;
+ private Controls.ButtonDark btnOpen;
+ private Controls.ButtonDark btnRun;
+ private Controls.CheckBoxDark cbQuality;
+ private Controls.TextBoxDark tbNumPoints;
+ private Controls.ButtonDark btnStatistic;
+ private Controls.CheckBoxDark cbConvex;
+ private System.Windows.Forms.Label lbTime;
+
+ }
+}
+
diff --git a/Triangle.NET/TestApp/Form1.cs b/Triangle.NET/TestApp/Form1.cs
new file mode 100644
index 0000000..d9dc928
--- /dev/null
+++ b/Triangle.NET/TestApp/Form1.cs
@@ -0,0 +1,281 @@
+using System;
+using System.Drawing;
+using System.Diagnostics;
+using System.Windows.Forms;
+using System.IO;
+using System.Globalization;
+using System.Collections.Generic;
+using TriangleNet;
+using TriangleNet.IO;
+using TestApp.Rendering;
+
+namespace TestApp
+{
+ public partial class Form1 : Form
+ {
+ Random rand = new Random(DateTime.Now.Millisecond);
+
+ // Triangulation IO
+ MeshData input;
+ Mesh mesh;
+
+ // Filter index of the "Open file" dialog
+ int dlgFilterIndex = 1;
+
+ // Startup directory of the "Open file" dialog
+ string dlgDirectory = Application.StartupPath;
+
+ Statistic statistic = new Statistic();
+ Form2 formStats;
+
+ public Form1()
+ {
+ InitializeComponent();
+ }
+
+ private double[][] CreateRandomPoints(int numPoints)
+ {
+ bool save = false;
+
+ double[][] points = new double[numPoints][];
+
+ int width = meshRenderer1.Width;
+ int height = meshRenderer1.Height;
+ if (save)
+ {
+ using (TextWriter fs = new StreamWriter(numPoints + ".txt"))
+ {
+ fs.WriteLine("{0} 2 0 0", numPoints);
+
+ for (int i = 0; i < numPoints; i++)
+ {
+ points[i] = new double[] {
+ rand.NextDouble() * width,
+ rand.NextDouble() * height };
+
+ fs.WriteLine(String.Format(CultureInfo.InvariantCulture.NumberFormat,
+ "{0} {1:0.0} {2:0.0}", (i + 1), points[i][0], points[i][1]));
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < numPoints; i++)
+ {
+ points[i] = new double[] {
+ rand.NextDouble() * width,
+ rand.NextDouble() * height };
+ }
+ }
+
+ return points;
+ }
+
+ private void Run()
+ {
+ if (input == null)
+ {
+ return;
+ }
+
+ Stopwatch sw = new Stopwatch();
+
+ mesh = new Mesh();
+ mesh.SetOption(Options.Quality, cbQuality.Checked);
+ mesh.SetOption(Options.Convex, cbConvex.Checked);
+
+ try
+ {
+ sw.Start();
+ mesh.Triangulate(input);
+ sw.Stop();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message);
+ }
+
+ meshRenderer1.SetData(mesh, false);
+
+ statistic.Update(mesh);
+
+ if (formStats != null && !formStats.IsDisposed)
+ {
+ formStats.UpdateSatistic(statistic, sw.ElapsedMilliseconds, meshRenderer1.RenderTime);
+ }
+ }
+
+ protected override void OnMouseWheel(MouseEventArgs e)
+ {
+ Point pt = e.Location;
+ pt.Offset(0, -meshRenderer1.Top);
+
+ if (meshRenderer1.ClientRectangle.Contains(pt))
+ {
+ meshRenderer1.Zoom(pt, e.Delta);
+ }
+ base.OnMouseWheel(e);
+ }
+
+ private void Form1_Load(object sender, EventArgs e)
+ {
+ if (Directory.Exists(@"..\..\..\Data\"))
+ {
+ dlgDirectory = @"..\..\..\Data\";
+
+ //Examples.Example1();
+ //Examples.Example2();
+ //Examples.Example3();
+ }
+ else if (Directory.Exists(@"Data\"))
+ {
+ dlgDirectory = @"Data\";
+ }
+
+ }
+
+ private void btnRandPts_Click(object sender, EventArgs e)
+ {
+ btnRun.Text = "Triangulate";
+
+ int n = 10;
+ int.TryParse(tbNumPoints.Text, out n);
+
+ input = new MeshData();
+
+ input.Points = CreateRandomPoints(n);
+
+ meshRenderer1.SetData(input, true);
+ }
+
+ private void btnOpen_Click(object sender, EventArgs e)
+ {
+ OpenFileDialog dlg = new OpenFileDialog();
+ dlg.InitialDirectory = dlgDirectory;
+ dlg.Filter = "Triangle polygon (*.node;*.poly)|*.node;*.poly|Polygon data (*.dat)|*.dat";
+ dlg.FilterIndex = dlgFilterIndex;
+
+ if (dlg.ShowDialog() == DialogResult.OK)
+ {
+ dlgDirectory = Path.GetDirectoryName(dlg.FileName);
+
+ string file = dlg.FileName;
+ string ext = Path.GetExtension(file);
+
+ if (ext == ".dat")
+ {
+ dlgFilterIndex = 2;
+ input = new MeshData();
+ input.Points = ParseDatFile(file);
+
+ int n = input.Points.Length;
+ int[][] segments = new int[n][];
+
+ for (int i = 0; i < n; i++)
+ {
+ segments[i] = new int[] { i, (i + 1) % n };
+
+ }
+ input.Segments = segments;
+ }
+ else
+ {
+ dlgFilterIndex = 1;
+ input = FileReader.ReadFile(file);
+ }
+
+ meshRenderer1.SetData(input, true);
+
+ btnRun.Text = "Triangulate";
+ }
+ }
+
+ private void btnRun_Click(object sender, EventArgs e)
+ {
+ if (cbQuality.Checked)
+ {
+ if (btnRun.Text == "Triangulate")
+ {
+ btnRun.Text = "Refine";
+
+ Run();
+ }
+ else
+ {
+ Stopwatch sw = new Stopwatch();
+
+ try
+ {
+ sw.Start();
+ mesh.Refine(statistic.LargestArea / 2);
+ sw.Stop();
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(ex.Message);
+ }
+
+ meshRenderer1.SetData(mesh, false);
+
+ statistic.Update(mesh);
+
+ if (formStats != null && !formStats.IsDisposed)
+ {
+ formStats.UpdateSatistic(statistic, sw.ElapsedMilliseconds, meshRenderer1.RenderTime);
+ }
+ }
+ }
+ else
+ {
+ btnRun.Text = "Triangulate";
+ Run();
+ }
+ }
+
+ private void btnStatistic_Click(object sender, EventArgs e)
+ {
+ if (formStats == null)
+ {
+ formStats = new Form2();
+ }
+
+ if (!formStats.Visible)
+ {
+ formStats.UpdateSatistic(this.statistic, -1, -1);
+ formStats.Show(this);
+ }
+ else
+ {
+ formStats.Hide();
+ }
+ }
+
+ public static double[][] ParseDatFile(string filename)
+ {
+ NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
+
+ List points = new List();
+
+ string line;
+ string[] split;
+
+ using (TextReader reader = new StreamReader(filename))
+ {
+ while ((line = reader.ReadLine()) != null)
+ {
+ split = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (split.Length == 2)
+ {
+ points.Add(new double[] {
+ double.Parse(split[0], nfi),
+ double.Parse(split[1], nfi)
+ });
+ }
+ }
+ }
+
+ return points.ToArray();
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Form1.resx b/Triangle.NET/TestApp/Form1.resx
new file mode 100644
index 0000000..29dcb1b
--- /dev/null
+++ b/Triangle.NET/TestApp/Form1.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Triangle.NET/TestApp/Form2.Designer.cs b/Triangle.NET/TestApp/Form2.Designer.cs
new file mode 100644
index 0000000..ea4c44e
--- /dev/null
+++ b/Triangle.NET/TestApp/Form2.Designer.cs
@@ -0,0 +1,489 @@
+namespace TestApp
+{
+ partial class Form2
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.label1 = new System.Windows.Forms.Label();
+ this.label2 = new System.Windows.Forms.Label();
+ this.label3 = new System.Windows.Forms.Label();
+ this.label4 = new System.Windows.Forms.Label();
+ this.label5 = new System.Windows.Forms.Label();
+ this.label6 = new System.Windows.Forms.Label();
+ this.label7 = new System.Windows.Forms.Label();
+ this.label8 = new System.Windows.Forms.Label();
+ this.label9 = new System.Windows.Forms.Label();
+ this.label10 = new System.Windows.Forms.Label();
+ this.label11 = new System.Windows.Forms.Label();
+ this.label12 = new System.Windows.Forms.Label();
+ this.label13 = new System.Windows.Forms.Label();
+ this.label14 = new System.Windows.Forms.Label();
+ this.label15 = new System.Windows.Forms.Label();
+ this.label16 = new System.Windows.Forms.Label();
+ this.label17 = new System.Windows.Forms.Label();
+ this.lbNumInput = new System.Windows.Forms.Label();
+ this.lbNumOutput = new System.Windows.Forms.Label();
+ this.lbNumTri = new System.Windows.Forms.Label();
+ this.lbNumEdge = new System.Windows.Forms.Label();
+ this.lbNumBoundary = new System.Windows.Forms.Label();
+ this.lbCalcTime = new System.Windows.Forms.Label();
+ this.lbRenderTime = new System.Windows.Forms.Label();
+ this.lbAreaMin = new System.Windows.Forms.Label();
+ this.lbAreaMax = new System.Windows.Forms.Label();
+ this.lbEdgeMin = new System.Windows.Forms.Label();
+ this.lbEdgeMax = new System.Windows.Forms.Label();
+ this.lbRatioMin = new System.Windows.Forms.Label();
+ this.lbRatioMax = new System.Windows.Forms.Label();
+ this.lbAngleMin = new System.Windows.Forms.Label();
+ this.lbAngleMax = new System.Windows.Forms.Label();
+ this.histogram1 = new TestApp.Controls.Histogram();
+ this.SuspendLayout();
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.ForeColor = System.Drawing.Color.Gray;
+ this.label1.Location = new System.Drawing.Point(11, 36);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(79, 13);
+ this.label1.TabIndex = 0;
+ this.label1.Text = "Input vertices:";
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.ForeColor = System.Drawing.Color.Gray;
+ this.label2.Location = new System.Drawing.Point(11, 59);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(79, 13);
+ this.label2.TabIndex = 0;
+ this.label2.Text = "Mesh vertices:";
+ //
+ // label3
+ //
+ this.label3.AutoSize = true;
+ this.label3.ForeColor = System.Drawing.Color.Gray;
+ this.label3.Location = new System.Drawing.Point(11, 81);
+ this.label3.Name = "label3";
+ this.label3.Size = new System.Drawing.Size(86, 13);
+ this.label3.TabIndex = 0;
+ this.label3.Text = "Mesh triangles:";
+ //
+ // label4
+ //
+ this.label4.AutoSize = true;
+ this.label4.ForeColor = System.Drawing.Color.Gray;
+ this.label4.Location = new System.Drawing.Point(11, 103);
+ this.label4.Name = "label4";
+ this.label4.Size = new System.Drawing.Size(72, 13);
+ this.label4.TabIndex = 0;
+ this.label4.Text = "Mesh edges:";
+ //
+ // label5
+ //
+ this.label5.AutoSize = true;
+ this.label5.ForeColor = System.Drawing.Color.Gray;
+ this.label5.Location = new System.Drawing.Point(11, 125);
+ this.label5.Name = "label5";
+ this.label5.Size = new System.Drawing.Size(136, 13);
+ this.label5.TabIndex = 0;
+ this.label5.Text = "Exterior boundary edges:";
+ //
+ // label6
+ //
+ this.label6.AutoSize = true;
+ this.label6.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.label6.ForeColor = System.Drawing.Color.White;
+ this.label6.Location = new System.Drawing.Point(12, 9);
+ this.label6.Name = "label6";
+ this.label6.Size = new System.Drawing.Size(39, 13);
+ this.label6.TabIndex = 0;
+ this.label6.Text = "Mesh:";
+ //
+ // label7
+ //
+ this.label7.AutoSize = true;
+ this.label7.ForeColor = System.Drawing.Color.Gray;
+ this.label7.Location = new System.Drawing.Point(12, 191);
+ this.label7.Name = "label7";
+ this.label7.Size = new System.Drawing.Size(79, 13);
+ this.label7.TabIndex = 0;
+ this.label7.Text = "Triangulation:";
+ //
+ // label8
+ //
+ this.label8.AutoSize = true;
+ this.label8.ForeColor = System.Drawing.Color.Gray;
+ this.label8.Location = new System.Drawing.Point(12, 212);
+ this.label8.Name = "label8";
+ this.label8.Size = new System.Drawing.Size(64, 13);
+ this.label8.TabIndex = 0;
+ this.label8.Text = "Rendering:";
+ //
+ // label9
+ //
+ this.label9.AutoSize = true;
+ this.label9.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.label9.ForeColor = System.Drawing.Color.White;
+ this.label9.Location = new System.Drawing.Point(11, 266);
+ this.label9.Name = "label9";
+ this.label9.Size = new System.Drawing.Size(47, 13);
+ this.label9.TabIndex = 0;
+ this.label9.Text = "Quality:";
+ //
+ // label10
+ //
+ this.label10.AutoSize = true;
+ this.label10.ForeColor = System.Drawing.Color.Gray;
+ this.label10.Location = new System.Drawing.Point(12, 296);
+ this.label10.Name = "label10";
+ this.label10.Size = new System.Drawing.Size(76, 13);
+ this.label10.TabIndex = 0;
+ this.label10.Text = "Triangle area:";
+ //
+ // label11
+ //
+ this.label11.AutoSize = true;
+ this.label11.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.label11.ForeColor = System.Drawing.Color.White;
+ this.label11.Location = new System.Drawing.Point(12, 163);
+ this.label11.Name = "label11";
+ this.label11.Size = new System.Drawing.Size(65, 13);
+ this.label11.TabIndex = 0;
+ this.label11.Text = "Stopwatch:";
+ //
+ // label12
+ //
+ this.label12.AutoSize = true;
+ this.label12.ForeColor = System.Drawing.Color.Gray;
+ this.label12.Location = new System.Drawing.Point(109, 266);
+ this.label12.Name = "label12";
+ this.label12.Size = new System.Drawing.Size(55, 13);
+ this.label12.TabIndex = 0;
+ this.label12.Text = "Minimum";
+ //
+ // label13
+ //
+ this.label13.AutoSize = true;
+ this.label13.ForeColor = System.Drawing.Color.Gray;
+ this.label13.Location = new System.Drawing.Point(199, 266);
+ this.label13.Name = "label13";
+ this.label13.Size = new System.Drawing.Size(56, 13);
+ this.label13.TabIndex = 0;
+ this.label13.Text = "Maximum";
+ //
+ // label14
+ //
+ this.label14.AutoSize = true;
+ this.label14.ForeColor = System.Drawing.Color.Gray;
+ this.label14.Location = new System.Drawing.Point(12, 320);
+ this.label14.Name = "label14";
+ this.label14.Size = new System.Drawing.Size(73, 13);
+ this.label14.TabIndex = 0;
+ this.label14.Text = "Edge length:";
+ //
+ // label15
+ //
+ this.label15.AutoSize = true;
+ this.label15.ForeColor = System.Drawing.Color.Gray;
+ this.label15.Location = new System.Drawing.Point(11, 343);
+ this.label15.Name = "label15";
+ this.label15.Size = new System.Drawing.Size(71, 13);
+ this.label15.TabIndex = 0;
+ this.label15.Text = "Aspect ratio:";
+ //
+ // label16
+ //
+ this.label16.AutoSize = true;
+ this.label16.ForeColor = System.Drawing.Color.Gray;
+ this.label16.Location = new System.Drawing.Point(11, 366);
+ this.label16.Name = "label16";
+ this.label16.Size = new System.Drawing.Size(40, 13);
+ this.label16.TabIndex = 0;
+ this.label16.Text = "Angle:";
+ //
+ // label17
+ //
+ this.label17.AutoSize = true;
+ this.label17.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.label17.ForeColor = System.Drawing.Color.White;
+ this.label17.Location = new System.Drawing.Point(11, 412);
+ this.label17.Name = "label17";
+ this.label17.Size = new System.Drawing.Size(97, 13);
+ this.label17.TabIndex = 0;
+ this.label17.Text = "Angle histogram:";
+ //
+ // lbNumInput
+ //
+ this.lbNumInput.AutoSize = true;
+ this.lbNumInput.ForeColor = System.Drawing.Color.White;
+ this.lbNumInput.Location = new System.Drawing.Point(171, 36);
+ this.lbNumInput.Name = "lbNumInput";
+ this.lbNumInput.Size = new System.Drawing.Size(11, 13);
+ this.lbNumInput.TabIndex = 0;
+ this.lbNumInput.Text = "-";
+ //
+ // lbNumOutput
+ //
+ this.lbNumOutput.AutoSize = true;
+ this.lbNumOutput.ForeColor = System.Drawing.Color.White;
+ this.lbNumOutput.Location = new System.Drawing.Point(171, 59);
+ this.lbNumOutput.Name = "lbNumOutput";
+ this.lbNumOutput.Size = new System.Drawing.Size(11, 13);
+ this.lbNumOutput.TabIndex = 0;
+ this.lbNumOutput.Text = "-";
+ //
+ // lbNumTri
+ //
+ this.lbNumTri.AutoSize = true;
+ this.lbNumTri.ForeColor = System.Drawing.Color.White;
+ this.lbNumTri.Location = new System.Drawing.Point(171, 81);
+ this.lbNumTri.Name = "lbNumTri";
+ this.lbNumTri.Size = new System.Drawing.Size(11, 13);
+ this.lbNumTri.TabIndex = 0;
+ this.lbNumTri.Text = "-";
+ //
+ // lbNumEdge
+ //
+ this.lbNumEdge.AutoSize = true;
+ this.lbNumEdge.ForeColor = System.Drawing.Color.White;
+ this.lbNumEdge.Location = new System.Drawing.Point(171, 103);
+ this.lbNumEdge.Name = "lbNumEdge";
+ this.lbNumEdge.Size = new System.Drawing.Size(11, 13);
+ this.lbNumEdge.TabIndex = 0;
+ this.lbNumEdge.Text = "-";
+ //
+ // lbNumBoundary
+ //
+ this.lbNumBoundary.AutoSize = true;
+ this.lbNumBoundary.ForeColor = System.Drawing.Color.White;
+ this.lbNumBoundary.Location = new System.Drawing.Point(171, 125);
+ this.lbNumBoundary.Name = "lbNumBoundary";
+ this.lbNumBoundary.Size = new System.Drawing.Size(11, 13);
+ this.lbNumBoundary.TabIndex = 0;
+ this.lbNumBoundary.Text = "-";
+ //
+ // lbCalcTime
+ //
+ this.lbCalcTime.AutoSize = true;
+ this.lbCalcTime.ForeColor = System.Drawing.Color.White;
+ this.lbCalcTime.Location = new System.Drawing.Point(171, 191);
+ this.lbCalcTime.Name = "lbCalcTime";
+ this.lbCalcTime.Size = new System.Drawing.Size(11, 13);
+ this.lbCalcTime.TabIndex = 0;
+ this.lbCalcTime.Text = "-";
+ //
+ // lbRenderTime
+ //
+ this.lbRenderTime.AutoSize = true;
+ this.lbRenderTime.ForeColor = System.Drawing.Color.White;
+ this.lbRenderTime.Location = new System.Drawing.Point(171, 212);
+ this.lbRenderTime.Name = "lbRenderTime";
+ this.lbRenderTime.Size = new System.Drawing.Size(11, 13);
+ this.lbRenderTime.TabIndex = 0;
+ this.lbRenderTime.Text = "-";
+ //
+ // lbAreaMin
+ //
+ this.lbAreaMin.ForeColor = System.Drawing.Color.White;
+ this.lbAreaMin.Location = new System.Drawing.Point(97, 296);
+ this.lbAreaMin.Name = "lbAreaMin";
+ this.lbAreaMin.Size = new System.Drawing.Size(68, 13);
+ this.lbAreaMin.TabIndex = 0;
+ this.lbAreaMin.Text = "-";
+ this.lbAreaMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+ //
+ // lbAreaMax
+ //
+ this.lbAreaMax.ForeColor = System.Drawing.Color.White;
+ this.lbAreaMax.Location = new System.Drawing.Point(179, 296);
+ this.lbAreaMax.Name = "lbAreaMax";
+ this.lbAreaMax.Size = new System.Drawing.Size(76, 13);
+ this.lbAreaMax.TabIndex = 0;
+ this.lbAreaMax.Text = "-";
+ this.lbAreaMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+ //
+ // lbEdgeMin
+ //
+ this.lbEdgeMin.ForeColor = System.Drawing.Color.White;
+ this.lbEdgeMin.Location = new System.Drawing.Point(97, 320);
+ this.lbEdgeMin.Name = "lbEdgeMin";
+ this.lbEdgeMin.Size = new System.Drawing.Size(68, 13);
+ this.lbEdgeMin.TabIndex = 0;
+ this.lbEdgeMin.Text = "-";
+ this.lbEdgeMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+ //
+ // lbEdgeMax
+ //
+ this.lbEdgeMax.ForeColor = System.Drawing.Color.White;
+ this.lbEdgeMax.Location = new System.Drawing.Point(179, 320);
+ this.lbEdgeMax.Name = "lbEdgeMax";
+ this.lbEdgeMax.Size = new System.Drawing.Size(76, 13);
+ this.lbEdgeMax.TabIndex = 0;
+ this.lbEdgeMax.Text = "-";
+ this.lbEdgeMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+ //
+ // lbRatioMin
+ //
+ this.lbRatioMin.ForeColor = System.Drawing.Color.White;
+ this.lbRatioMin.Location = new System.Drawing.Point(97, 343);
+ this.lbRatioMin.Name = "lbRatioMin";
+ this.lbRatioMin.Size = new System.Drawing.Size(68, 13);
+ this.lbRatioMin.TabIndex = 0;
+ this.lbRatioMin.Text = "-";
+ this.lbRatioMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+ //
+ // lbRatioMax
+ //
+ this.lbRatioMax.ForeColor = System.Drawing.Color.White;
+ this.lbRatioMax.Location = new System.Drawing.Point(179, 343);
+ this.lbRatioMax.Name = "lbRatioMax";
+ this.lbRatioMax.Size = new System.Drawing.Size(76, 13);
+ this.lbRatioMax.TabIndex = 0;
+ this.lbRatioMax.Text = "-";
+ this.lbRatioMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+ //
+ // lbAngleMin
+ //
+ this.lbAngleMin.ForeColor = System.Drawing.Color.White;
+ this.lbAngleMin.Location = new System.Drawing.Point(97, 366);
+ this.lbAngleMin.Name = "lbAngleMin";
+ this.lbAngleMin.Size = new System.Drawing.Size(68, 13);
+ this.lbAngleMin.TabIndex = 0;
+ this.lbAngleMin.Text = "-";
+ this.lbAngleMin.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+ //
+ // lbAngleMax
+ //
+ this.lbAngleMax.ForeColor = System.Drawing.Color.White;
+ this.lbAngleMax.Location = new System.Drawing.Point(179, 366);
+ this.lbAngleMax.Name = "lbAngleMax";
+ this.lbAngleMax.Size = new System.Drawing.Size(76, 13);
+ this.lbAngleMax.TabIndex = 0;
+ this.lbAngleMax.Text = "-";
+ this.lbAngleMax.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
+ //
+ // histogram1
+ //
+ this.histogram1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76)))));
+ this.histogram1.Location = new System.Drawing.Point(15, 428);
+ this.histogram1.Name = "histogram1";
+ this.histogram1.Size = new System.Drawing.Size(257, 212);
+ this.histogram1.TabIndex = 1;
+ this.histogram1.Text = "histogram1";
+ //
+ // Form2
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76)))));
+ this.ClientSize = new System.Drawing.Size(284, 652);
+ this.Controls.Add(this.histogram1);
+ this.Controls.Add(this.label8);
+ this.Controls.Add(this.label13);
+ this.Controls.Add(this.label12);
+ this.Controls.Add(this.label16);
+ this.Controls.Add(this.label15);
+ this.Controls.Add(this.label14);
+ this.Controls.Add(this.lbAngleMax);
+ this.Controls.Add(this.lbRatioMax);
+ this.Controls.Add(this.lbEdgeMax);
+ this.Controls.Add(this.lbAreaMax);
+ this.Controls.Add(this.lbAngleMin);
+ this.Controls.Add(this.lbRatioMin);
+ this.Controls.Add(this.lbEdgeMin);
+ this.Controls.Add(this.lbAreaMin);
+ this.Controls.Add(this.label10);
+ this.Controls.Add(this.label17);
+ this.Controls.Add(this.label9);
+ this.Controls.Add(this.label7);
+ this.Controls.Add(this.label11);
+ this.Controls.Add(this.label6);
+ this.Controls.Add(this.label5);
+ this.Controls.Add(this.label4);
+ this.Controls.Add(this.label3);
+ this.Controls.Add(this.label2);
+ this.Controls.Add(this.lbRenderTime);
+ this.Controls.Add(this.lbCalcTime);
+ this.Controls.Add(this.lbNumBoundary);
+ this.Controls.Add(this.lbNumEdge);
+ this.Controls.Add(this.lbNumTri);
+ this.Controls.Add(this.lbNumOutput);
+ this.Controls.Add(this.lbNumInput);
+ this.Controls.Add(this.label1);
+ this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
+ this.ForeColor = System.Drawing.Color.Gray;
+ this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
+ this.MaximizeBox = false;
+ this.MinimizeBox = false;
+ this.Name = "Form2";
+ this.ShowIcon = false;
+ this.ShowInTaskbar = false;
+ this.Text = "Mesh Statistic";
+ this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form2_FormClosing);
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.Label label3;
+ private System.Windows.Forms.Label label4;
+ private System.Windows.Forms.Label label5;
+ private System.Windows.Forms.Label label6;
+ private System.Windows.Forms.Label label7;
+ private System.Windows.Forms.Label label8;
+ private System.Windows.Forms.Label label9;
+ private System.Windows.Forms.Label label10;
+ private System.Windows.Forms.Label label11;
+ private System.Windows.Forms.Label label12;
+ private System.Windows.Forms.Label label13;
+ private System.Windows.Forms.Label label14;
+ private System.Windows.Forms.Label label15;
+ private System.Windows.Forms.Label label16;
+ private System.Windows.Forms.Label label17;
+ private System.Windows.Forms.Label lbNumInput;
+ private System.Windows.Forms.Label lbNumOutput;
+ private System.Windows.Forms.Label lbNumTri;
+ private System.Windows.Forms.Label lbNumEdge;
+ private System.Windows.Forms.Label lbNumBoundary;
+ private System.Windows.Forms.Label lbCalcTime;
+ private System.Windows.Forms.Label lbRenderTime;
+ private System.Windows.Forms.Label lbAreaMin;
+ private System.Windows.Forms.Label lbAreaMax;
+ private System.Windows.Forms.Label lbEdgeMin;
+ private System.Windows.Forms.Label lbEdgeMax;
+ private System.Windows.Forms.Label lbRatioMin;
+ private System.Windows.Forms.Label lbRatioMax;
+ private System.Windows.Forms.Label lbAngleMin;
+ private System.Windows.Forms.Label lbAngleMax;
+ private Controls.Histogram histogram1;
+ }
+}
\ No newline at end of file
diff --git a/Triangle.NET/TestApp/Form2.cs b/Triangle.NET/TestApp/Form2.cs
new file mode 100644
index 0000000..2c3c230
--- /dev/null
+++ b/Triangle.NET/TestApp/Form2.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using TriangleNet;
+using System.Globalization;
+
+namespace TestApp
+{
+ public partial class Form2 : Form
+ {
+ static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
+
+ public Form2()
+ {
+ InitializeComponent();
+
+ }
+
+ public void UpdateSatistic(Statistic stat, long calcTime, long renderTime)
+ {
+ UpdateMesh(stat);
+ UpdateQuality(stat);
+ UpdateTime(calcTime, renderTime);
+
+ histogram1.SetData(stat.AngleHistogram);
+ }
+
+ private void UpdateMesh(Statistic stat)
+ {
+ lbNumInput.Text = stat.InputVertices.ToString();
+ lbNumOutput.Text = stat.Vertices.ToString();
+ lbNumTri.Text = stat.Triangles.ToString();
+ lbNumEdge.Text = stat.Edges.ToString();
+ lbNumBoundary.Text = stat.BoundaryEdges.ToString();
+ }
+
+ private void UpdateQuality(Statistic stat)
+ {
+ lbAreaMin.Text = stat.SmallestArea.ToString("0.00000", nfi);
+ lbAreaMax.Text = stat.LargestArea.ToString("0.00000", nfi);
+ lbEdgeMin.Text = stat.ShortestEdge.ToString("0.00000", nfi);
+ lbEdgeMax.Text = stat.LongestEdge.ToString("0.00000", nfi);
+ lbRatioMin.Text = stat.ShortestAltitude.ToString("0.00000", nfi);
+ lbRatioMax.Text = stat.LargestAspectRatio.ToString("0.00000", nfi);
+ lbAngleMin.Text = stat.SmallestAngle.ToString("0.00000", nfi);
+ lbAngleMax.Text = stat.LargestAngle.ToString("0.00000", nfi);
+ }
+
+ private void UpdateTime(long calcTime, long renderTime)
+ {
+ if (calcTime > 0)
+ {
+ lbCalcTime.Text = calcTime + " ms";
+ }
+ else
+ {
+ lbCalcTime.Text = "-";
+ }
+
+ if (renderTime > 0)
+ {
+ lbRenderTime.Text = renderTime + " ms";
+ }
+ else
+ {
+ lbRenderTime.Text = "-";
+ }
+ }
+
+ private void Form2_FormClosing(object sender, FormClosingEventArgs e)
+ {
+ if (e.CloseReason != CloseReason.UserClosing) return;
+ e.Cancel = true;
+ this.Hide();
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Form2.resx b/Triangle.NET/TestApp/Form2.resx
new file mode 100644
index 0000000..29dcb1b
--- /dev/null
+++ b/Triangle.NET/TestApp/Form2.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Triangle.NET/TestApp/ImageWriter.cs b/Triangle.NET/TestApp/ImageWriter.cs
new file mode 100644
index 0000000..a0711f1
--- /dev/null
+++ b/Triangle.NET/TestApp/ImageWriter.cs
@@ -0,0 +1,297 @@
+// -----------------------------------------------------------------------
+//
+// TODO: Update copyright text.
+//
+// -----------------------------------------------------------------------
+
+namespace TestApp
+{
+ using System;
+ using System.Drawing;
+ using System.Drawing.Drawing2D;
+ using System.Drawing.Imaging;
+ using System.IO;
+ using TriangleNet;
+ using TriangleNet.Data;
+ using TriangleNet.IO;
+
+ ///
+ /// Writes an image of the mesh to disk.
+ ///
+ public static class ImageWriter
+ {
+ // Number of input points
+ static int NumberOfInputPoints = 0;
+
+ // Default color scheme (dark)
+ static Color bgColor = Color.Black;
+ static Color ptColor = Color.Green;
+ static Color spColor = Color.Peru;
+ static Color lnColor = Color.FromArgb(30, 30, 30);
+ static Color sgColor = Color.Blue;
+ static Color trColor = Color.FromArgb(30, 40, 50);
+
+ ///
+ /// Sets the color scheme.
+ ///
+ /// Background color.
+ /// Points color.
+ /// Steiner points color.
+ /// Line color.
+ /// Segment color.
+ public static void SetColorScheme(Color background, Color points, Color steiner,
+ Color lines, Color segments, Color triangles)
+ {
+ bgColor = background;
+ ptColor = points;
+ spColor = steiner;
+ lnColor = lines;
+ sgColor = segments;
+ trColor = triangles;
+ }
+
+ ///
+ /// Set a color scheme with white background.
+ ///
+ public static void SetColorSchemeLight()
+ {
+ bgColor = Color.White;
+ ptColor = Color.MidnightBlue;
+ spColor = Color.DarkGreen;
+ lnColor = Color.FromArgb(150, 150, 150);
+ sgColor = Color.SteelBlue;
+ trColor = Color.FromArgb(230, 240, 250);
+ }
+
+ ///
+ /// Set a color scheme with black background.
+ ///
+ public static void SetColorSchemeDark()
+ {
+ bgColor = Color.Black;
+ ptColor = Color.Green;
+ spColor = Color.Peru;
+ lnColor = Color.FromArgb(30, 30, 30);
+ sgColor = Color.Blue;
+ trColor = Color.FromArgb(30, 40, 50);
+ }
+
+ ///
+ /// Draws the mesh and writes the image file.
+ ///
+ /// The mesh to visualize.
+ public static void WritePng(Mesh mesh)
+ {
+ WritePng(mesh, "", 1000);
+ }
+
+ ///
+ /// Draws the mesh and writes the image file.
+ ///
+ /// The mesh to visualize.
+ /// The filename (only PNG supported).
+ public static void WritePng(Mesh mesh, string filename)
+ {
+ WritePng(mesh, filename, 1000);
+ }
+
+ ///
+ /// Draws the mesh and writes the image file.
+ ///
+ /// The mesh to visualize.
+ /// The target width of the image (pixel).
+ public static void WritePng(Mesh mesh, int width)
+ {
+ WritePng(mesh, "", width);
+ }
+
+ ///
+ /// Draws the mesh and writes the image file.
+ ///
+ /// The mesh to visualize.
+ /// The filename (only PNG supported).
+ /// The target width of the image (pixel).
+ public static void WritePng(Mesh mesh, string filename, int width)
+ {
+ NumberOfInputPoints = mesh.NumberOfInputPoints;
+
+ if (String.IsNullOrWhiteSpace(filename))
+ {
+ filename = String.Format("mesh-{0}.png", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss"));
+ }
+
+ MeshData data = mesh.GetMeshData(true, true, false);
+
+ // Mesh bounds
+ float minx = float.MaxValue;
+ float maxx = float.MinValue;
+ float miny = float.MaxValue;
+ float maxy = float.MinValue;
+
+ float x, y;
+
+ int n = data.Points.Length;
+
+ // Calculate bounds
+ for (int i = 0; i < n; i++)
+ {
+ x = (float)data.Points[i][0];
+ y = (float)data.Points[i][1];
+
+ // Update bounding box
+ if (minx > x) minx = x;
+ if (maxx < x) maxx = x;
+ if (miny > y) miny = y;
+ if (maxy < y) maxy = y;
+ }
+
+ Bitmap bitmap;
+
+ // Check if the specified width is reasonable
+ if (width < 2 * Math.Sqrt(n))
+ {
+ bitmap = new Bitmap(400, 200);
+ Graphics g = Graphics.FromImage(bitmap);
+ g.Clear(Color.Black);
+
+ string message = String.Format("Sorry, I won't render {0} points on such a small image!", n);
+
+ SizeF sz = g.MeasureString(message, SystemFonts.DefaultFont);
+
+ g.SmoothingMode = SmoothingMode.AntiAlias;
+ g.DrawString(message, SystemFonts.DefaultFont, Brushes.White,
+ 200 - sz.Width / 2, 100 - sz.Height / 2);
+
+ g.Dispose();
+ }
+ else
+ {
+ // World margin on each side
+ float margin = (maxy - miny) * 0.05f;
+ float scale = width / (maxx - minx + 2 * margin);
+
+ bitmap = new Bitmap(width, (int)((maxy - miny + 2 * margin) * scale), PixelFormat.Format32bppArgb);
+
+ Graphics g = Graphics.FromImage(bitmap);
+ g.Clear(bgColor);
+
+ // Transform world to screen
+ g.ScaleTransform(scale, -scale);
+ g.TranslateTransform(-minx + margin, -maxy - margin);
+
+ DrawMesh(g, data, scale);
+
+ g.Dispose();
+ }
+
+ if (Path.GetExtension(filename) != ".png")
+ {
+ filename += ".png";
+ }
+
+ bitmap.Save(filename, ImageFormat.Png);
+ }
+
+ ///
+ /// Draw mesh to the graphics object.
+ ///
+ private static void DrawMesh(Graphics g, MeshData mesh, float scale)
+ {
+ g.SmoothingMode = SmoothingMode.AntiAlias;
+ // Colors
+
+ Brush bgBrush = new SolidBrush(bgColor);
+ Brush ptBrush = new SolidBrush(ptColor);
+ Brush spBrush = new SolidBrush(spColor);
+ Brush trBrush = new SolidBrush(trColor);
+
+ // Scale the pens to 1 pixel width
+ //Pen ptBrush = new Pen(ptColor, 1 / scale);
+ //Pen spBrush = new Pen(spColor, 1 / scale);
+ Pen lnBrush = new Pen(lnColor, 1 / scale);
+ Pen sgBrush = new Pen(sgColor, 1 / scale);
+
+ PointF p1, p2, p3;
+
+ int[] tmp;
+
+ // Draw triangle edges
+ int n = mesh.Triangles == null ? 0 : mesh.Triangles.Length;
+
+ for (int i = 0; i < n; i++)
+ {
+ tmp = mesh.Triangles[i];
+
+ p1 = new PointF((float)mesh.Points[tmp[0]][0], (float)mesh.Points[tmp[0]][1]);
+ p2 = new PointF((float)mesh.Points[tmp[1]][0], (float)mesh.Points[tmp[1]][1]);
+ p3 = new PointF((float)mesh.Points[tmp[2]][0], (float)mesh.Points[tmp[2]][1]);
+
+ // Fill triangle
+ g.FillPolygon(trBrush, new PointF[] { p1, p2, p3 });
+ }
+
+ // Draw edges
+ n = mesh.Edges == null ? 0 : mesh.Edges.Length;
+
+ for (int i = 0; i < n; i++)
+ {
+ tmp = mesh.Edges[i];
+
+ p1 = new PointF((float)mesh.Points[tmp[0]][0], (float)mesh.Points[tmp[0]][1]);
+ p2 = new PointF((float)mesh.Points[tmp[1]][0], (float)mesh.Points[tmp[1]][1]);
+
+ // Draw line
+ g.DrawLine(lnBrush, p1, p2);
+ }
+
+ // Draw segments
+ n = mesh.Segments == null ? 0 : mesh.Segments.Length;
+
+ for (int i = 0; i < n; i++)
+ {
+ tmp = mesh.Segments[i];
+
+ p1 = new PointF((float)mesh.Points[tmp[0]][0], (float)mesh.Points[tmp[0]][1]);
+ p2 = new PointF((float)mesh.Points[tmp[1]][0], (float)mesh.Points[tmp[1]][1]);
+
+ // Draw line
+ g.DrawLine(sgBrush, p1, p2);
+ }
+
+ // Scale the points radius to 2 pixel.
+ float radius = 1.5f / scale, x, y;
+
+ // Draw points
+ n = mesh.Points.Length;
+
+ if (NumberOfInputPoints <= 0)
+ {
+ NumberOfInputPoints = n;
+ }
+
+ for (int i = 0; i < n; i++)
+ {
+ x = (float)mesh.Points[i][0];
+ y = (float)mesh.Points[i][1];
+
+ if (i < NumberOfInputPoints)
+ {
+ g.FillEllipse(ptBrush, x - radius, y - radius, 2 * radius, 2 * radius);
+ //g.DrawEllipse(ptBrush, x - radius, y - radius, 2 * radius, 2 * radius);
+ }
+ else
+ {
+ g.FillEllipse(spBrush, x - radius, y - radius, 2 * radius, 2 * radius);
+ //g.DrawEllipse(ptBrush, x - radius, y - radius, 2 * radius, 2 * radius);
+ }
+ }
+
+ bgBrush.Dispose();
+ ptBrush.Dispose();
+ spBrush.Dispose();
+ lnBrush.Dispose();
+ sgBrush.Dispose();
+ trBrush.Dispose();
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Program.cs b/Triangle.NET/TestApp/Program.cs
new file mode 100644
index 0000000..2236c86
--- /dev/null
+++ b/Triangle.NET/TestApp/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+
+namespace TestApp
+{
+ static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ [STAThread]
+ static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new Form1());
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Properties/AssemblyInfo.cs b/Triangle.NET/TestApp/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..8b41fd1
--- /dev/null
+++ b/Triangle.NET/TestApp/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("Example1")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Example1")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("7368d676-5415-47a5-b1a7-3d517e418b21")]
+
+// 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/TestApp/Properties/Resources.Designer.cs b/Triangle.NET/TestApp/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..70de15f
--- /dev/null
+++ b/Triangle.NET/TestApp/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.261
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace TestApp.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TestApp.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Properties/Resources.resx b/Triangle.NET/TestApp/Properties/Resources.resx
new file mode 100644
index 0000000..ffecec8
--- /dev/null
+++ b/Triangle.NET/TestApp/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Triangle.NET/TestApp/Properties/Settings.Designer.cs b/Triangle.NET/TestApp/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..aff0249
--- /dev/null
+++ b/Triangle.NET/TestApp/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.261
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace TestApp.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Properties/Settings.settings b/Triangle.NET/TestApp/Properties/Settings.settings
new file mode 100644
index 0000000..abf36c5
--- /dev/null
+++ b/Triangle.NET/TestApp/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/Triangle.NET/TestApp/Rendering/MeshDataInternal.cs b/Triangle.NET/TestApp/Rendering/MeshDataInternal.cs
new file mode 100644
index 0000000..b4a53e1
--- /dev/null
+++ b/Triangle.NET/TestApp/Rendering/MeshDataInternal.cs
@@ -0,0 +1,122 @@
+// -----------------------------------------------------------------------
+//
+// TODO: Update copyright text.
+//
+// -----------------------------------------------------------------------
+
+namespace TestApp.Rendering
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using TriangleNet.IO;
+ using System.Drawing;
+ using TriangleNet;
+
+ public class MeshDataInternal
+ {
+ public PointF[] Points;
+ public int[][] Triangles;
+ public int[][] Edges;
+ public int[][] Segments;
+ public int NumberOfInputPoints;
+ public RectangleF Bounds;
+
+ public void SetData(Mesh mesh)
+ {
+ NumberOfInputPoints = mesh.NumberOfInputPoints;
+
+ SetData(mesh.GetMeshData(true, true, false), mesh.NumberOfInputPoints);
+ }
+
+ public void SetData(MeshData data)
+ {
+ SetData(data, data.Points.Length);
+ }
+
+ public void SetData(MeshData data, int inputCount)
+ {
+ NumberOfInputPoints = inputCount;
+
+ int n = data.Points.Length;
+
+ // Reset
+ Triangles = null;
+ Edges = null;
+ Segments = null;
+
+ // Copy points
+ this.Points = new PointF[n];
+
+ // Bounds
+ float minx = float.MaxValue;
+ float maxx = float.MinValue;
+ float miny = float.MaxValue;
+ float maxy = float.MinValue;
+
+ float x, y;
+
+ for (int i = 0; i < n; i += 1)
+ {
+ x = (float)data.Points[i][0];
+ y = (float)data.Points[i][1];
+ // Update bounding box
+ if (minx > x) minx = x;
+ if (maxx < x) maxx = x;
+ if (miny > y) miny = y;
+ if (maxy < y) maxy = y;
+
+ this.Points[i] = new PointF(x, y);
+ }
+
+ this.Bounds = new RectangleF(minx, miny, maxx - minx, maxy - miny);
+
+ n = data.Edges == null ? 0 : data.Edges.Length;
+
+ // Copy edges
+ if (data.Edges != null && n > 0)
+ {
+ Edges = new int[n][];
+
+ for (int i = 0; i < n; i++)
+ {
+ Edges[i] = new int[2];
+ Edges[i][0] = data.Edges[i][0];
+ Edges[i][1] = data.Edges[i][1];
+ }
+ }
+
+ n = data.Segments == null ? 0 : data.Segments.Length;
+
+ // Copy segments
+ if (data.Segments != null && n > 0)
+ {
+ Segments = new int[n][];
+
+ for (int i = 0; i < n; i++)
+ {
+ Segments[i] = new int[2];
+ Segments[i][0] = data.Segments[i][0];
+ Segments[i][1] = data.Segments[i][1];
+ }
+ }
+
+ n = data.Triangles == null ? 0 : data.Triangles.Length;
+
+ // Copy triangles
+ if (data.Triangles != null && n > 0)
+ {
+ Triangles = new int[n][];
+
+ for (int i = 0; i < n; i++)
+ {
+ Triangles[i] = new int[3];
+ Triangles[i][0] = data.Triangles[i][0];
+ Triangles[i][1] = data.Triangles[i][1];
+ Triangles[i][2] = data.Triangles[i][2];
+ }
+ }
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/Rendering/Zoom.cs b/Triangle.NET/TestApp/Rendering/Zoom.cs
new file mode 100644
index 0000000..b3b8595
--- /dev/null
+++ b/Triangle.NET/TestApp/Rendering/Zoom.cs
@@ -0,0 +1,168 @@
+// -----------------------------------------------------------------------
+//
+// TODO: Update copyright text.
+//
+// -----------------------------------------------------------------------
+
+namespace TestApp.Rendering
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Drawing;
+
+ ///
+ /// Manages the current world to screen transformation
+ ///
+ class Zoom
+ {
+ // The complete mesh
+ int screenWidth;
+ int screenHeight;
+
+ // The complete mesh
+ RectangleF World { get; set; }
+
+ // The current viewport (visible mesh)
+ public RectangleF Viewport { get; set; }
+
+ // Current scale (zoom level)
+ public int Level { get; set; }
+
+ // Add a margin to clip region (5% of viewport width on each side)
+ public float ClipMargin { get; set; }
+
+ public Zoom()
+ {
+ Level = -1;
+ }
+
+ public void Initialize(Rectangle screen, RectangleF world)
+ {
+ this.screenWidth = screen.Width;
+ this.screenHeight = screen.Height;
+
+ this.World = world;
+ 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 = this.screenWidth / (float)this.screenHeight;
+ float worldRatio = world.Width / world.Height;
+
+ float scale = (world.Width + worldMargin) / this.screenWidth;
+
+ if (screenRatio > worldRatio)
+ {
+ scale = (world.Height + worldMargin) / this.screenHeight;
+ }
+
+ float centerX = world.X + world.Width / 2;
+ float centerY = world.Y + world.Height / 2;
+
+ // TODO: Add initial margin
+ this.Viewport = new RectangleF(centerX - this.screenWidth * scale / 2,
+ centerY - this.screenHeight * 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 Update(int amount, float focusX, float focusY)
+ {
+ float width, height;
+
+ if (amount > 0) // Zoom in
+ {
+ this.Level++;
+
+ if (this.Level > 50)
+ {
+ this.Level = 50;
+ 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 * (1 - focusY);
+
+ // New left and top positions
+ x = x - width * focusX;
+ y = y - height * (1 - 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 bool ViewportContains(PointF pt)
+ {
+ return (pt.X > Viewport.X && pt.X < Viewport.Right
+ && pt.Y > Viewport.Y && pt.Y < Viewport.Bottom);
+ }
+
+ public PointF WorldToScreen(PointF pt)
+ {
+ return new PointF((pt.X - Viewport.X) / Viewport.Width * screenWidth,
+ (1 - (pt.Y - Viewport.Y) / Viewport.Height) * screenHeight);
+ }
+
+ public void Reset()
+ {
+ this.Viewport = this.World;
+ this.Level = 1;
+ }
+ }
+}
diff --git a/Triangle.NET/TestApp/TestApp.csproj b/Triangle.NET/TestApp/TestApp.csproj
new file mode 100644
index 0000000..624c995
--- /dev/null
+++ b/Triangle.NET/TestApp/TestApp.csproj
@@ -0,0 +1,122 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}
+ WinExe
+ Properties
+ TestApp
+ TestApp
+ v4.0
+ Client
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+ Component
+
+
+ Component
+
+
+ Component
+
+
+ Component
+
+
+
+ Form
+
+
+ Form1.cs
+
+
+ Component
+
+
+ Form
+
+
+ Form2.cs
+
+
+
+
+
+
+
+ Form1.cs
+
+
+ Form2.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+ True
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}
+ Triangle
+
+
+
+
+
\ No newline at end of file
diff --git a/Triangle.NET/Triangle.sln b/Triangle.NET/Triangle.sln
new file mode 100644
index 0000000..804e1ab
--- /dev/null
+++ b/Triangle.NET/Triangle.sln
@@ -0,0 +1,54 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Triangle", "Triangle\Triangle.csproj", "{F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "TestApp\TestApp.csproj", "{336AAF8A-5316-4303-9E73-5E38BD0B28AF}"
+EndProject
+Global
+ GlobalSection(TeamFoundationVersionControl) = preSolution
+ SccNumberOfProjects = 3
+ SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
+ SccTeamFoundationServer = https://tfs.codeplex.com/tfs/tfs06
+ SccLocalPath0 = .
+ SccProjectUniqueName1 = TestApp\\TestApp.csproj
+ SccProjectName1 = TestApp
+ SccLocalPath1 = TestApp
+ SccProjectUniqueName2 = Triangle\\Triangle.csproj
+ SccProjectName2 = Triangle
+ SccLocalPath2 = Triangle
+ EndGlobalSection
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Mixed Platforms = Debug|Mixed Platforms
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|Mixed Platforms = Release|Mixed Platforms
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}.Release|x86.ActiveCfg = Release|Any CPU
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|x86.ActiveCfg = Debug|x86
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Debug|x86.Build.0 = Debug|x86
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|Any CPU.ActiveCfg = Release|x86
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|Mixed Platforms.Build.0 = Release|x86
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|x86.ActiveCfg = Release|x86
+ {336AAF8A-5316-4303-9E73-5E38BD0B28AF}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/Triangle.NET/Triangle/Algorithm/Dwyer.cs b/Triangle.NET/Triangle/Algorithm/Dwyer.cs
new file mode 100644
index 0000000..c8cfec6
--- /dev/null
+++ b/Triangle.NET/Triangle/Algorithm/Dwyer.cs
@@ -0,0 +1,885 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Algorithm
+{
+ using System;
+ using TriangleNet.Data;
+ using TriangleNet.Log;
+
+ ///
+ /// Builds a delaunay triangulation using the divide-and-conquer algorithm.
+ ///
+ ///
+ /// The divide-and-conquer bounding box
+ ///
+ /// I originally implemented the divide-and-conquer and incremental Delaunay
+ /// triangulations using the edge-based data structure presented by Guibas
+ /// and Stolfi. Switching to a triangle-based data structure doubled the
+ /// speed. However, I had to think of a few extra tricks to maintain the
+ /// elegance of the original algorithms.
+ ///
+ /// The "bounding box" used by my variant of the divide-and-conquer
+ /// algorithm uses one triangle for each edge of the convex hull of the
+ /// triangulation. These bounding triangles all share a common apical
+ /// vertex, which is represented by NULL and which represents nothing.
+ /// The bounding triangles are linked in a circular fan about this NULL
+ /// vertex, and the edges on the convex hull of the triangulation appear
+ /// opposite the NULL vertex. You might find it easiest to imagine that
+ /// the NULL vertex is a point in 3D space behind the center of the
+ /// triangulation, and that the bounding triangles form a sort of cone.
+ ///
+ /// This bounding box makes it easy to represent degenerate cases. For
+ /// instance, the triangulation of two vertices is a single edge. This edge
+ /// is represented by two bounding box triangles, one on each "side" of the
+ /// edge. These triangles are also linked together in a fan about the NULL
+ /// vertex.
+ ///
+ /// The bounding box also makes it easy to traverse the convex hull, as the
+ /// divide-and-conquer algorithm needs to do.
+ ///
+ class Dwyer
+ {
+ static Random rand = new Random(DateTime.Now.Millisecond);
+ bool useDwyer = true;
+
+ ///
+ /// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Uses quicksort. Randomized O(n log n) time. No, I did not make any of
+ /// the usual quicksort mistakes.
+ ///
+ static void VertexSort(Vertex[] sortarray, int left, int right)
+ {
+ int oleft = left;
+ int oright = right;
+ int arraysize = right - left + 1;
+ int pivot;
+ double pivotx, pivoty;
+ Vertex temp;
+
+ if (arraysize < 32)
+ {
+ // Insertion sort
+ for (int i = left + 1; i <= right; i++)
+ {
+ var a = sortarray[i];
+ int j = i - 1;
+ while (j >= left && (sortarray[j].pt.X > a.pt.X || (sortarray[j].pt.X == a.pt.X && sortarray[j].pt.Y > a.pt.Y)))
+ {
+ sortarray[j + 1] = sortarray[j];
+ j--;
+ }
+ sortarray[j + 1] = a;
+ }
+
+ return;
+ }
+
+ // Choose a random pivot to split the array.
+ pivot = rand.Next(left, right);
+ pivotx = sortarray[pivot].pt.X;
+ pivoty = sortarray[pivot].pt.Y;
+ // Split the array.
+ left--;
+ right++;
+ while (left < right)
+ {
+ // Search for a vertex whose x-coordinate is too large for the left.
+ do
+ {
+ left++;
+ }
+ while ((left <= right) && ((sortarray[left].pt.X < pivotx) ||
+ ((sortarray[left].pt.X == pivotx) &&
+ (sortarray[left].pt.Y < pivoty))));
+ // Search for a vertex whose x-coordinate is too small for the right.
+ do
+ {
+ right--;
+ }
+ while ((left <= right) && ((sortarray[right].pt.X > pivotx) ||
+ ((sortarray[right].pt.X == pivotx) &&
+ (sortarray[right].pt.Y > pivoty))));
+
+ if (left < right)
+ {
+ // Swap the left and right vertices.
+ temp = sortarray[left];
+ sortarray[left] = sortarray[right];
+ sortarray[right] = temp;
+ }
+ }
+ if (left > oleft)
+ {
+ // Recursively sort the left subset.
+ VertexSort(sortarray, oleft, left);
+ }
+ if (oright > right + 1)
+ {
+ // Recursively sort the right subset.
+ VertexSort(sortarray, right + 1, oright);
+ }
+ }
+
+ ///
+ /// An order statistic algorithm, almost. Shuffles an array of vertices so that
+ /// the first 'median' vertices occur lexicographically before the remaining vertices.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Uses the x-coordinate as the primary key if axis == 0; the y-coordinate
+ /// if axis == 1. Very similar to the vertexsort() procedure, but runs in
+ /// randomized linear time.
+ ///
+ void VertexMedian(Vertex[] sortarray, int left, int right, int median, int axis)
+ {
+ int arraysize = right - left + 1;
+ int oleft = left, oright = right;
+ int pivot;
+ double pivot1, pivot2;
+ Vertex temp;
+
+ if (arraysize == 2)
+ {
+ // Recursive base case.
+ if ((sortarray[left][axis] > sortarray[right][axis]) ||
+ ((sortarray[left][axis] == sortarray[right][axis]) &&
+ (sortarray[left][1 - axis] > sortarray[right][1 - axis])))
+ {
+ temp = sortarray[right];
+ sortarray[right] = sortarray[left];
+ sortarray[left] = temp;
+ }
+ return;
+ }
+ // Choose a random pivot to split the array.
+ pivot = rand.Next(left, right); //left + arraysize / 2;
+ pivot1 = sortarray[pivot][axis];
+ pivot2 = sortarray[pivot][1 - axis];
+
+ left--;
+ right++;
+ while (left < right)
+ {
+ // Search for a vertex whose x-coordinate is too large for the left.
+ do
+ {
+ left++;
+ }
+ while ((left <= right) && ((sortarray[left][axis] < pivot1) ||
+ ((sortarray[left][axis] == pivot1) &&
+ (sortarray[left][1 - axis] < pivot2))));
+ // Search for a vertex whose x-coordinate is too small for the right.
+ do
+ {
+ right--;
+ }
+ while ((left <= right) && ((sortarray[right][axis] > pivot1) ||
+ ((sortarray[right][axis] == pivot1) &&
+ (sortarray[right][1 - axis] > pivot2))));
+ if (left < right)
+ {
+ // Swap the left and right vertices.
+ temp = sortarray[left];
+ sortarray[left] = sortarray[right];
+ sortarray[right] = temp;
+ }
+ }
+
+ // Unlike in vertexsort(), at most one of the following conditionals is true.
+ if (left > median)
+ {
+ // Recursively shuffle the left subset.
+ VertexMedian(sortarray, oleft, left - 1, median, axis);
+ }
+ if (right < median - 1)
+ {
+ // Recursively shuffle the right subset.
+ VertexMedian(sortarray, right + 1, oright, median, axis);
+ }
+ }
+
+ ///
+ /// Sorts the vertices as appropriate for the divide-and-conquer algorithm with
+ /// alternating cuts.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Partitions by x-coordinate if axis == 0; by y-coordinate if axis == 1.
+ /// For the base case, subsets containing only two or three vertices are
+ /// always sorted by x-coordinate.
+ ///
+ void AlternateAxes(Vertex[] sortarray, int left, int right, int axis)
+ {
+ int arraysize = right - left + 1;
+ int divider;
+
+ divider = arraysize >> 1;
+ //divider += left; // TODO: check
+ if (arraysize <= 3)
+ {
+ // Recursive base case: subsets of two or three vertices will be
+ // handled specially, and should always be sorted by x-coordinate.
+ axis = 0;
+ }
+ // Partition with a horizontal or vertical cut.
+ VertexMedian(sortarray, left, right, left + divider, axis);
+ // Recursively partition the subsets with a cross cut.
+ if (arraysize - divider >= 2)
+ {
+ if (divider >= 2)
+ {
+ AlternateAxes(sortarray, left, left + divider - 1, 1 - axis);
+ }
+ AlternateAxes(sortarray, left + divider, right, 1 - axis);
+ }
+ }
+
+ ///
+ /// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation.
+ ///
+ /// Bounding triangles of the left triangulation.
+ /// Bounding triangles of the left triangulation.
+ /// Bounding triangles of the right triangulation.
+ /// Bounding triangles of the right triangulation.
+ ///
+ ///
+ /// This is similar to the algorithm given by Guibas and Stolfi, but uses
+ /// a triangle-based, rather than edge-based, data structure.
+ ///
+ /// The algorithm walks up the gap between the two triangulations, knitting
+ /// them together. As they are merged, some of their bounding triangles
+ /// are converted into real triangles of the triangulation. The procedure
+ /// pulls each hull's bounding triangles apart, then knits them together
+ /// like the teeth of two gears. The Delaunay property determines, at each
+ /// step, whether the next "tooth" is a bounding triangle of the left hull
+ /// or the right. When a bounding triangle becomes real, its apex is
+ /// changed from NULL to a real vertex.
+ ///
+ /// Only two new triangles need to be allocated. These become new bounding
+ /// triangles at the top and bottom of the seam. They are used to connect
+ /// the remaining bounding triangles (those that have not been converted
+ /// into real triangles) into a single fan.
+ ///
+ /// On entry, 'farleft' and 'innerleft' are bounding triangles of the left
+ /// triangulation. The origin of 'farleft' is the leftmost vertex, and
+ /// the destination of 'innerleft' is the rightmost vertex of the
+ /// triangulation. Similarly, 'innerright' and 'farright' are bounding
+ /// triangles of the right triangulation. The origin of 'innerright' and
+ /// destination of 'farright' are the leftmost and rightmost vertices.
+ ///
+ /// On completion, the origin of 'farleft' is the leftmost vertex of the
+ /// merged triangulation, and the destination of 'farright' is the rightmost
+ /// vertex.
+ ///
+ void MergeHulls(Mesh m, ref Otri farleft,
+ ref Otri innerleft, ref Otri innerright,
+ ref Otri farright, int axis)
+ {
+ Otri leftcand = default(Otri), rightcand = default(Otri);
+ Otri baseedge = default(Otri);
+ Otri nextedge = default(Otri);
+ Otri sidecasing = default(Otri), topcasing = default(Otri), outercasing = default(Otri);
+ Otri checkedge = default(Otri);
+ Vertex innerleftdest;
+ Vertex innerrightorg;
+ Vertex innerleftapex, innerrightapex;
+ Vertex farleftpt, farrightpt;
+ Vertex farleftapex, farrightapex;
+ Vertex lowerleft, lowerright;
+ Vertex upperleft, upperright;
+ Vertex nextapex;
+ Vertex checkvertex;
+ bool changemade;
+ bool badedge;
+ bool leftfinished, rightfinished;
+
+ innerleftdest = innerleft.Dest();
+ innerleftapex = innerleft.Apex();
+ innerrightorg = innerright.Org();
+ innerrightapex = innerright.Apex();
+ // Special treatment for horizontal cuts.
+ if (useDwyer && (axis == 1))
+ {
+ farleftpt = farleft.Org();
+ farleftapex = farleft.Apex();
+ farrightpt = farright.Dest();
+ farrightapex = farright.Apex();
+ // The pointers to the extremal vertices are shifted to point to the
+ // topmost and bottommost vertex of each hull, rather than the
+ // leftmost and rightmost vertices.
+ while (farleftapex.pt.Y < farleftpt.pt.Y)
+ {
+ farleft.LnextSelf();
+ farleft.SymSelf();
+ farleftpt = farleftapex;
+ farleftapex = farleft.Apex();
+ }
+ innerleft.Sym(ref checkedge);
+ checkvertex = checkedge.Apex();
+ while (checkvertex.pt.Y > innerleftdest.pt.Y)
+ {
+ checkedge.Lnext(ref innerleft);
+ innerleftapex = innerleftdest;
+ innerleftdest = checkvertex;
+ innerleft.Sym(ref checkedge);
+ checkvertex = checkedge.Apex();
+ }
+ while (innerrightapex.pt.Y < innerrightorg.pt.Y)
+ {
+ innerright.LnextSelf();
+ innerright.SymSelf();
+ innerrightorg = innerrightapex;
+ innerrightapex = innerright.Apex();
+ }
+ farright.Sym(ref checkedge);
+ checkvertex = checkedge.Apex();
+ while (checkvertex.pt.Y > farrightpt.pt.Y)
+ {
+ checkedge.Lnext(ref farright);
+ farrightapex = farrightpt;
+ farrightpt = checkvertex;
+ farright.Sym(ref checkedge);
+ checkvertex = checkedge.Apex();
+ }
+ }
+ // Find a line tangent to and below both hulls.
+ do
+ {
+ changemade = false;
+ // Make innerleftdest the "bottommost" vertex of the left hull.
+ if (Primitives.CounterClockwise(innerleftdest.pt, innerleftapex.pt, innerrightorg.pt) > 0.0)
+ {
+ innerleft.LprevSelf();
+ innerleft.SymSelf();
+ innerleftdest = innerleftapex;
+ innerleftapex = innerleft.Apex();
+ changemade = true;
+ }
+ // Make innerrightorg the "bottommost" vertex of the right hull.
+ if (Primitives.CounterClockwise(innerrightapex.pt, innerrightorg.pt, innerleftdest.pt) > 0.0)
+ {
+ innerright.LnextSelf();
+ innerright.SymSelf();
+ innerrightorg = innerrightapex;
+ innerrightapex = innerright.Apex();
+ changemade = true;
+ }
+ } while (changemade);
+ // Find the two candidates to be the next "gear tooth."
+ innerleft.Sym(ref leftcand);
+ innerright.Sym(ref rightcand);
+ // Create the bottom new bounding triangle.
+ m.MakeTriangle(ref baseedge);
+ // Connect it to the bounding boxes of the left and right triangulations.
+ baseedge.Bond(ref innerleft);
+ baseedge.LnextSelf();
+ baseedge.Bond(ref innerright);
+ baseedge.LnextSelf();
+ baseedge.SetOrg(innerrightorg);
+ baseedge.SetDest(innerleftdest);
+ // Apex is intentionally left NULL.
+
+ // Fix the extreme triangles if necessary.
+ farleftpt = farleft.Org();
+ if (innerleftdest == farleftpt)
+ {
+ baseedge.Lnext(ref farleft);
+ }
+ farrightpt = farright.Dest();
+ if (innerrightorg == farrightpt)
+ {
+ baseedge.Lprev(ref farright);
+ }
+ // The vertices of the current knitting edge.
+ lowerleft = innerleftdest;
+ lowerright = innerrightorg;
+ // The candidate vertices for knitting.
+ upperleft = leftcand.Apex();
+ upperright = rightcand.Apex();
+ // Walk up the gap between the two triangulations, knitting them together.
+ while (true)
+ {
+ // Have we reached the top? (This isn't quite the right question,
+ // because even though the left triangulation might seem finished now,
+ // moving up on the right triangulation might reveal a new vertex of
+ // the left triangulation. And vice-versa.)
+ leftfinished = Primitives.CounterClockwise(upperleft.pt, lowerleft.pt, lowerright.pt) <= 0.0;
+ rightfinished = Primitives.CounterClockwise(upperright.pt, lowerleft.pt, lowerright.pt) <= 0.0;
+ if (leftfinished && rightfinished)
+ {
+ // Create the top new bounding triangle.
+ m.MakeTriangle(ref nextedge);
+ nextedge.SetOrg(lowerleft);
+ nextedge.SetDest(lowerright);
+ // Apex is intentionally left NULL.
+ // Connect it to the bounding boxes of the two triangulations.
+ nextedge.Bond(ref baseedge);
+ nextedge.LnextSelf();
+ nextedge.Bond(ref rightcand);
+ nextedge.LnextSelf();
+ nextedge.Bond(ref leftcand);
+
+ // Special treatment for horizontal cuts.
+ if (useDwyer && (axis == 1))
+ {
+ farleftpt = farleft.Org();
+ farleftapex = farleft.Apex();
+ farrightpt = farright.Dest();
+ farrightapex = farright.Apex();
+ farleft.Sym(ref checkedge);
+ checkvertex = checkedge.Apex();
+ // The pointers to the extremal vertices are restored to the
+ // leftmost and rightmost vertices (rather than topmost and
+ // bottommost).
+ while (checkvertex.pt.X < farleftpt.pt.X)
+ {
+ checkedge.Lprev(ref farleft);
+ farleftapex = farleftpt;
+ farleftpt = checkvertex;
+ farleft.Sym(ref checkedge);
+ checkvertex = checkedge.Apex();
+ }
+ while (farrightapex.pt.X > farrightpt.pt.X)
+ {
+ farright.LprevSelf();
+ farright.SymSelf();
+ farrightpt = farrightapex;
+ farrightapex = farright.Apex();
+ }
+ }
+ return;
+ }
+ // Consider eliminating edges from the left triangulation.
+ if (!leftfinished)
+ {
+ // What vertex would be exposed if an edge were deleted?
+ leftcand.Lprev(ref nextedge);
+ nextedge.SymSelf();
+ nextapex = nextedge.Apex();
+ // If nextapex is NULL, then no vertex would be exposed; the
+ // triangulation would have been eaten right through.
+ if (nextapex != null)
+ {
+ // Check whether the edge is Delaunay.
+ badedge = Primitives.InCircle(lowerleft.pt, lowerright.pt, upperleft.pt, nextapex.pt) > 0.0;
+ while (badedge)
+ {
+ // Eliminate the edge with an edge flip. As a result, the
+ // left triangulation will have one more boundary triangle.
+ nextedge.LnextSelf();
+ nextedge.Sym(ref topcasing);
+ nextedge.LnextSelf();
+ nextedge.Sym(ref sidecasing);
+ nextedge.Bond(ref topcasing);
+ leftcand.Bond(ref sidecasing);
+ leftcand.LnextSelf();
+ leftcand.Sym(ref outercasing);
+ nextedge.LprevSelf();
+ nextedge.Bond(ref outercasing);
+ // Correct the vertices to reflect the edge flip.
+ leftcand.SetOrg(lowerleft);
+ leftcand.SetDest(null);
+ leftcand.SetApex(nextapex);
+ nextedge.SetOrg(null);
+ nextedge.SetDest(upperleft);
+ nextedge.SetApex(nextapex);
+ // Consider the newly exposed vertex.
+ upperleft = nextapex;
+ // What vertex would be exposed if another edge were deleted?
+ sidecasing.Copy(ref nextedge);
+ nextapex = nextedge.Apex();
+ if (nextapex != null)
+ {
+ // Check whether the edge is Delaunay.
+ badedge = Primitives.InCircle(lowerleft.pt, lowerright.pt, upperleft.pt, nextapex.pt) > 0.0;
+ }
+ else
+ {
+ // Avoid eating right through the triangulation.
+ badedge = false;
+ }
+ }
+ }
+ }
+ // Consider eliminating edges from the right triangulation.
+ if (!rightfinished)
+ {
+ // What vertex would be exposed if an edge were deleted?
+ rightcand.Lnext(ref nextedge);
+ nextedge.SymSelf();
+ nextapex = nextedge.Apex();
+ // If nextapex is NULL, then no vertex would be exposed; the
+ // triangulation would have been eaten right through.
+ if (nextapex != null)
+ {
+ // Check whether the edge is Delaunay.
+ badedge = Primitives.InCircle(lowerleft.pt, lowerright.pt, upperright.pt, nextapex.pt) > 0.0;
+ while (badedge)
+ {
+ // Eliminate the edge with an edge flip. As a result, the
+ // right triangulation will have one more boundary triangle.
+ nextedge.LprevSelf();
+ nextedge.Sym(ref topcasing);
+ nextedge.LprevSelf();
+ nextedge.Sym(ref sidecasing);
+ nextedge.Bond(ref topcasing);
+ rightcand.Bond(ref sidecasing);
+ rightcand.LprevSelf();
+ rightcand.Sym(ref outercasing);
+ nextedge.LnextSelf();
+ nextedge.Bond(ref outercasing);
+ // Correct the vertices to reflect the edge flip.
+ rightcand.SetOrg(null);
+ rightcand.SetDest(lowerright);
+ rightcand.SetApex(nextapex);
+ nextedge.SetOrg(upperright);
+ nextedge.SetDest(null);
+ nextedge.SetApex(nextapex);
+ // Consider the newly exposed vertex.
+ upperright = nextapex;
+ // What vertex would be exposed if another edge were deleted?
+ sidecasing.Copy(ref nextedge);
+ nextapex = nextedge.Apex();
+ if (nextapex != null)
+ {
+ // Check whether the edge is Delaunay.
+ badedge = Primitives.InCircle(lowerleft.pt, lowerright.pt, upperright.pt, nextapex.pt) > 0.0;
+ }
+ else
+ {
+ // Avoid eating right through the triangulation.
+ badedge = false;
+ }
+ }
+ }
+ }
+ if (leftfinished || (!rightfinished &&
+ (Primitives.InCircle(upperleft.pt, lowerleft.pt, lowerright.pt, upperright.pt) > 0.0)))
+ {
+ // Knit the triangulations, adding an edge from 'lowerleft'
+ // to 'upperright'.
+ baseedge.Bond(ref rightcand);
+ rightcand.Lprev(ref baseedge);
+ baseedge.SetDest(lowerleft);
+ lowerright = upperright;
+ baseedge.Sym(ref rightcand);
+ upperright = rightcand.Apex();
+ }
+ else
+ {
+ // Knit the triangulations, adding an edge from 'upperleft'
+ // to 'lowerright'.
+ baseedge.Bond(ref leftcand);
+ leftcand.Lnext(ref baseedge);
+ baseedge.SetOrg(lowerright);
+ lowerleft = upperleft;
+ baseedge.Sym(ref leftcand);
+ upperleft = leftcand.Apex();
+ }
+ }
+ }
+
+ ///
+ /// Recursively form a Delaunay triangulation by the divide-and-conquer method.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Recursively breaks down the problem into smaller pieces, which are
+ /// knitted together by mergehulls(). The base cases (problems of two or
+ /// three vertices) are handled specially here.
+ ///
+ /// On completion, 'farleft' and 'farright' are bounding triangles such that
+ /// the origin of 'farleft' is the leftmost vertex (breaking ties by
+ /// choosing the highest leftmost vertex), and the destination of
+ /// 'farright' is the rightmost vertex (breaking ties by choosing the
+ /// lowest rightmost vertex).
+ ///
+ void DivconqRecurse(Mesh m, Vertex[] sortarray, int left, int right, int axis,
+ ref Otri farleft, ref Otri farright)
+ {
+ Otri midtri = default(Otri);
+ Otri tri1 = default(Otri);
+ Otri tri2 = default(Otri);
+ Otri tri3 = default(Otri);
+ Otri innerleft = default(Otri), innerright = default(Otri);
+ double area;
+ int vertices = right - left + 1;
+ int divider;
+
+ if (vertices == 2)
+ {
+ // The triangulation of two vertices is an edge. An edge is
+ // represented by two bounding triangles.
+ m.MakeTriangle(ref farleft);
+ farleft.SetOrg(sortarray[left]);
+ farleft.SetDest(sortarray[left + 1]);
+ // The apex is intentionally left NULL.
+ m.MakeTriangle(ref farright);
+ farright.SetOrg(sortarray[left + 1]);
+ farright.SetDest(sortarray[left]);
+ // The apex is intentionally left NULL.
+ farleft.Bond(ref farright);
+ farleft.LprevSelf();
+ farright.LnextSelf();
+ farleft.Bond(ref farright);
+ farleft.LprevSelf();
+ farright.LnextSelf();
+ farleft.Bond(ref farright);
+
+ // Ensure that the origin of 'farleft' is sortarray[0].
+ farright.Lprev(ref farleft);
+ return;
+ }
+ else if (vertices == 3)
+ {
+ // The triangulation of three vertices is either a triangle (with
+ // three bounding triangles) or two edges (with four bounding
+ // triangles). In either case, four triangles are created.
+ m.MakeTriangle(ref midtri);
+ m.MakeTriangle(ref tri1);
+ m.MakeTriangle(ref tri2);
+ m.MakeTriangle(ref tri3);
+ area = Primitives.CounterClockwise(sortarray[left].pt, sortarray[left + 1].pt, sortarray[left + 2].pt);
+ if (area == 0.0)
+ {
+ // Three collinear vertices; the triangulation is two edges.
+ midtri.SetOrg(sortarray[left]);
+ midtri.SetDest(sortarray[left + 1]);
+ tri1.SetOrg(sortarray[left + 1]);
+ tri1.SetDest(sortarray[left]);
+ tri2.SetOrg(sortarray[left + 2]);
+ tri2.SetDest(sortarray[left + 1]);
+ tri3.SetOrg(sortarray[left + 1]);
+ tri3.SetDest(sortarray[left + 2]);
+ // All apices are intentionally left NULL.
+ midtri.Bond(ref tri1);
+ tri2.Bond(ref tri3);
+ midtri.LnextSelf();
+ tri1.LprevSelf();
+ tri2.LnextSelf();
+ tri3.LprevSelf();
+ midtri.Bond(ref tri3);
+ tri1.Bond(ref tri2);
+ midtri.LnextSelf();
+ tri1.LprevSelf();
+ tri2.LnextSelf();
+ tri3.LprevSelf();
+ midtri.Bond(ref tri1);
+ tri2.Bond(ref tri3);
+ // Ensure that the origin of 'farleft' is sortarray[0].
+ tri1.Copy(ref farleft);
+ // Ensure that the destination of 'farright' is sortarray[2].
+ tri2.Copy(ref farright);
+ }
+ else
+ {
+ // The three vertices are not collinear; the triangulation is one
+ // triangle, namely 'midtri'.
+ midtri.SetOrg(sortarray[left]);
+ tri1.SetDest(sortarray[left]);
+ tri3.SetOrg(sortarray[left]);
+ // Apices of tri1, tri2, and tri3 are left NULL.
+ if (area > 0.0)
+ {
+ // The vertices are in counterclockwise order.
+ midtri.SetDest(sortarray[left + 1]);
+ tri1.SetOrg(sortarray[left + 1]);
+ tri2.SetDest(sortarray[left + 1]);
+ midtri.SetApex(sortarray[left + 2]);
+ tri2.SetOrg(sortarray[left + 2]);
+ tri3.SetDest(sortarray[left + 2]);
+ }
+ else
+ {
+ // The vertices are in clockwise order.
+ midtri.SetDest(sortarray[left + 2]);
+ tri1.SetOrg(sortarray[left + 2]);
+ tri2.SetDest(sortarray[left + 2]);
+ midtri.SetApex(sortarray[left + 1]);
+ tri2.SetOrg(sortarray[left + 1]);
+ tri3.SetDest(sortarray[left + 1]);
+ }
+ // The topology does not depend on how the vertices are ordered.
+ midtri.Bond(ref tri1);
+ midtri.LnextSelf();
+ midtri.Bond(ref tri2);
+ midtri.LnextSelf();
+ midtri.Bond(ref tri3);
+ tri1.LprevSelf();
+ tri2.LnextSelf();
+ tri1.Bond(ref tri2);
+ tri1.LprevSelf();
+ tri3.LprevSelf();
+ tri1.Bond(ref tri3);
+ tri2.LnextSelf();
+ tri3.LprevSelf();
+ tri2.Bond(ref tri3);
+ // Ensure that the origin of 'farleft' is sortarray[0].
+ tri1.Copy(ref farleft);
+ // Ensure that the destination of 'farright' is sortarray[2].
+ if (area > 0.0)
+ {
+ tri2.Copy(ref farright);
+ }
+ else
+ {
+ farleft.Lnext(ref farright);
+ }
+ }
+
+ return;
+ }
+ else
+ {
+ // Split the vertices in half.
+ divider = vertices >> 1;
+ // Recursively triangulate each half.
+ DivconqRecurse(m, sortarray, left, left + divider - 1, 1 - axis, ref farleft, ref innerleft);
+ DivconqRecurse(m, sortarray, left + divider, right, 1 - axis, ref innerright, ref farright);
+
+ // Merge the two triangulations into one.
+ MergeHulls(m, ref farleft, ref innerleft, ref innerright, ref farright, axis);
+ }
+ }
+
+ ///
+ /// Removes ghost triangles.
+ ///
+ ///
+ /// Number of vertices on the hull.
+ int RemoveGhosts(Mesh m, ref Otri startghost)
+ {
+ Otri searchedge = default(Otri);
+ Otri dissolveedge = default(Otri);
+ Otri deadtriangle = default(Otri);
+ Vertex markorg;
+ int hullsize;
+
+ // Find an edge on the convex hull to start point location from.
+ startghost.Lprev(ref searchedge);
+ searchedge.SymSelf();
+ Mesh.dummytri.neighbors[0] = searchedge;
+ // Remove the bounding box and count the convex hull edges.
+ startghost.Copy(ref dissolveedge);
+ hullsize = 0;
+ do
+ {
+ hullsize++;
+ dissolveedge.Lnext(ref deadtriangle);
+ dissolveedge.LprevSelf();
+ dissolveedge.SymSelf();
+ // If no PSLG is involved, set the boundary markers of all the vertices
+ // on the convex hull. If a PSLG is used, this step is done later.
+ if (!Behavior.Poly)
+ {
+ // Watch out for the case where all the input vertices are collinear.
+ if (dissolveedge.triangle != Mesh.dummytri)
+ {
+ markorg = dissolveedge.Org();
+ if (markorg.mark == 0)
+ {
+ markorg.mark = 1;
+ }
+ }
+ }
+ // Remove a bounding triangle from a convex hull triangle.
+ dissolveedge.Dissolve();
+ // Find the next bounding triangle.
+ deadtriangle.Sym(ref dissolveedge);
+ // Delete the bounding triangle.
+ m.TriangleDealloc(deadtriangle.triangle);
+ } while (!dissolveedge.Equal(startghost));
+
+ return hullsize;
+ }
+
+ ///
+ /// Form a Delaunay triangulation by the divide-and-conquer method.
+ ///
+ ///
+ ///
+ /// Sorts the vertices, calls a recursive procedure to triangulate them, and
+ /// removes the bounding box, setting boundary markers as appropriate.
+ ///
+ public int Triangulate(Mesh m)
+ {
+ Vertex[] sortarray;
+ Otri hullleft = default(Otri), hullright = default(Otri);
+ int divider;
+ int i, j;
+
+ // Allocate an array of pointers to vertices for sorting.
+ sortarray = new Vertex[m.invertices];
+ i = 0;
+ foreach (var v in m.vertices.Values)
+ {
+ sortarray[i++] = v;
+ }
+ // Sort the vertices.
+ //Array.Sort(sortarray);
+ VertexSort(sortarray, 0, m.invertices - 1);
+ // Discard duplicate vertices, which can really mess up the algorithm.
+ i = 0;
+ for (j = 1; j < m.invertices; j++)
+ {
+ if ((sortarray[i].pt.X == sortarray[j].pt.X)
+ && (sortarray[i].pt.Y == sortarray[j].pt.Y))
+ {
+ if (Behavior.Verbose)
+ {
+ SimpleLogger.Instance.Warning("A duplicate vertex appeared and was ignored.", "DivConquer.DivconqDelaunay()");
+ }
+ sortarray[j].type = VertexType.UndeadVertex;
+ m.undeads++;
+ }
+ else
+ {
+ i++;
+ sortarray[i] = sortarray[j];
+ }
+ }
+ i++;
+ if (useDwyer)
+ {
+ // Re-sort the array of vertices to accommodate alternating cuts.
+ divider = i >> 1;
+ if (i - divider >= 2)
+ {
+ if (divider >= 2)
+ {
+ AlternateAxes(sortarray, 0, divider - 1, 1);
+ }
+ AlternateAxes(sortarray, divider, i - 1, 1);
+ }
+ }
+
+ // Form the Delaunay triangulation.
+ DivconqRecurse(m, sortarray, 0, i-1, 0, ref hullleft, ref hullright);
+ //trifree((VOID*)sortarray);
+
+ return RemoveGhosts(m, ref hullleft);
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Algorithm/ITriangulator.cs b/Triangle.NET/Triangle/Algorithm/ITriangulator.cs
new file mode 100644
index 0000000..0b34c43
--- /dev/null
+++ b/Triangle.NET/Triangle/Algorithm/ITriangulator.cs
@@ -0,0 +1,21 @@
+// -----------------------------------------------------------------------
+//
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Algorithm
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// TODO: Update summary.
+ ///
+ public interface ITriangulator
+ {
+ int Triangulate(Mesh mesh);
+ }
+}
diff --git a/Triangle.NET/Triangle/Algorithm/Incremental.cs b/Triangle.NET/Triangle/Algorithm/Incremental.cs
new file mode 100644
index 0000000..73bd4e5
--- /dev/null
+++ b/Triangle.NET/Triangle/Algorithm/Incremental.cs
@@ -0,0 +1,184 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Algorithm
+{
+ using TriangleNet.Data;
+ using TriangleNet.Log;
+
+ ///
+ /// Builds a delaunay triangulation using the incremental algorithm.
+ ///
+ class Incremental
+ {
+ Mesh mesh;
+
+ ///
+ /// Form an "infinite" bounding triangle to insert vertices into.
+ ///
+ ///
+ /// The vertices at "infinity" are assigned finite coordinates, which are
+ /// used by the point location routines, but (mostly) ignored by the
+ /// Delaunay edge flip routines.
+ ///
+ void BoundingBox()
+ {
+ Otri inftri = default(Otri); // Handle for the triangular bounding box.
+ double width;
+
+ // Find the width (or height, whichever is larger) of the triangulation.
+ width = mesh.xmax - mesh.xmin;
+ if (mesh.ymax - mesh.ymin > width)
+ {
+ width = mesh.ymax - mesh.ymin;
+ }
+ if (width == 0.0)
+ {
+ width = 1.0;
+ }
+ // Create the vertices of the bounding box.
+ mesh.infvertex1 = new Vertex();
+ mesh.infvertex2 = new Vertex();
+ mesh.infvertex3 = new Vertex();
+ mesh.infvertex1.pt.X = mesh.xmin - 50.0 * width;
+ mesh.infvertex1.pt.Y = mesh.ymin - 40.0 * width;
+ mesh.infvertex2.pt.X = mesh.xmax + 50.0 * width;
+ mesh.infvertex2.pt.Y = mesh.ymin - 40.0 * width;
+ mesh.infvertex3.pt.X = 0.5 * (mesh.xmin + mesh.xmax);
+ mesh.infvertex3.pt.Y = mesh.ymax + 60.0 * width;
+
+ // Create the bounding box.
+ mesh.MakeTriangle(ref inftri);
+ inftri.SetOrg(mesh.infvertex1);
+ inftri.SetDest(mesh.infvertex2);
+ inftri.SetApex(mesh.infvertex3);
+ // Link dummytri to the bounding box so we can always find an
+ // edge to begin searching (point location) from.
+ Mesh.dummytri.neighbors[0] = inftri;
+ }
+
+ ///
+ /// Remove the "infinite" bounding triangle, setting boundary markers as appropriate.
+ ///
+ /// Returns the number of edges on the convex hull of the triangulation.
+ ///
+ /// The triangular bounding box has three boundary triangles (one for each
+ /// side of the bounding box), and a bunch of triangles fanning out from
+ /// the three bounding box vertices (one triangle for each edge of the
+ /// convex hull of the inner mesh). This routine removes these triangles.
+ ///
+ int RemoveBox()
+ {
+ Otri deadtriangle = default(Otri);
+ Otri searchedge = default(Otri);
+ Otri checkedge = default(Otri);
+ Otri nextedge = default(Otri), finaledge = default(Otri), dissolveedge = default(Otri);
+ Vertex markorg;
+ int hullsize;
+
+ // Find a boundary triangle.
+ nextedge.triangle = Mesh.dummytri;
+ nextedge.orient = 0;
+ nextedge.SymSelf();
+ // Mark a place to stop.
+ nextedge.Lprev(ref finaledge);
+ nextedge.LnextSelf();
+ nextedge.SymSelf();
+ // Find a triangle (on the boundary of the vertex set) that isn't
+ // a bounding box triangle.
+ nextedge.Lprev(ref searchedge);
+ searchedge.SymSelf();
+ // Check whether nextedge is another boundary triangle
+ // adjacent to the first one.
+ nextedge.Lnext(ref checkedge);
+ checkedge.SymSelf();
+ if (checkedge.triangle == Mesh.dummytri)
+ {
+ // Go on to the next triangle. There are only three boundary
+ // triangles, and this next triangle cannot be the third one,
+ // so it's safe to stop here.
+ searchedge.LprevSelf();
+ searchedge.SymSelf();
+ }
+ // Find a new boundary edge to search from, as the current search
+ // edge lies on a bounding box triangle and will be deleted.
+ Mesh.dummytri.neighbors[0] = searchedge;
+ hullsize = -2;
+ while (!nextedge.Equal(finaledge))
+ {
+ hullsize++;
+ nextedge.Lprev(ref dissolveedge);
+ dissolveedge.SymSelf();
+ // If not using a PSLG, the vertices should be marked now.
+ // (If using a PSLG, markhull() will do the job.)
+ if (!Behavior.Poly)
+ {
+ // Be careful! One must check for the case where all the input
+ // vertices are collinear, and thus all the triangles are part of
+ // the bounding box. Otherwise, the setvertexmark() call below
+ // will cause a bad pointer reference.
+ if (dissolveedge.triangle != Mesh.dummytri)
+ {
+ markorg = dissolveedge.Org();
+ if (markorg.mark == 0)
+ {
+ markorg.mark = 1;
+ }
+ }
+ }
+ // Disconnect the bounding box triangle from the mesh triangle.
+ dissolveedge.Dissolve();
+ nextedge.Lnext(ref deadtriangle);
+ deadtriangle.Sym(ref nextedge);
+ // Get rid of the bounding box triangle.
+ mesh.TriangleDealloc(deadtriangle.triangle);
+ // Do we need to turn the corner?
+ if (nextedge.triangle == Mesh.dummytri)
+ {
+ // Turn the corner.
+ dissolveedge.Copy(ref nextedge);
+ }
+ }
+ mesh.TriangleDealloc(finaledge.triangle);
+
+ return hullsize;
+ }
+
+ ///
+ /// Form a Delaunay triangulation by incrementally inserting vertices.
+ ///
+ /// Returns the number of edges on the convex hull of the
+ /// triangulation.
+ public int Triangulate(Mesh mesh)
+ {
+ this.mesh = mesh;
+
+ Otri starttri = new Otri();
+
+ // Create a triangular bounding box.
+ BoundingBox();
+
+ foreach (var v in mesh.vertices.Values)
+ {
+ starttri.triangle = Mesh.dummytri;
+ Osub tmp = default(Osub);
+ if (mesh.InsertVertex(v, ref starttri, ref tmp, false, false) == InsertVertexResult.Duplicate)
+ {
+ if (Behavior.Verbose)
+ {
+ SimpleLogger.Instance.Warning("A duplicate vertex appeared and was ignored.",
+ "Incremental.IncrementalDelaunay()");
+ }
+ v.type = VertexType.UndeadVertex;
+ mesh.undeads++;
+ }
+ }
+ // Remove the bounding box.
+ return RemoveBox();
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Algorithm/SweepLine.cs b/Triangle.NET/Triangle/Algorithm/SweepLine.cs
new file mode 100644
index 0000000..f3085ea
--- /dev/null
+++ b/Triangle.NET/Triangle/Algorithm/SweepLine.cs
@@ -0,0 +1,752 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Algorithm
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using TriangleNet.Data;
+ using TriangleNet.Log;
+
+ ///
+ /// Builds a delaunay triangulation using the sweepline algorithm.
+ ///
+ class SweepLine
+ {
+ ///
+ /// Introducing a new class which aggregates a sweep event is the easiest way
+ /// to handle the pointer magic of the original code (casting a sweep event
+ /// to vertex etc.).
+ ///
+ class SweepEventVertex : Vertex
+ {
+ public SweepEvent evt;
+
+ public SweepEventVertex(SweepEvent e)
+ {
+ evt = e;
+ }
+ }
+
+ static int randomseed = 1;
+ static int SAMPLERATE = 10;
+
+ int randomnation(int choices)
+ {
+ randomseed = (randomseed * 1366 + 150889) % 714025;
+ return randomseed / (714025 / choices + 1);
+ }
+
+ Mesh mesh;
+ double xminextreme; // Nonexistent x value used as a flag in sweepline.
+ List splaynodes;
+
+ #region Heap
+
+ void HeapInsert(SweepEvent[] heap, int heapsize, SweepEvent newevent)
+ {
+ double eventx, eventy;
+ int eventnum;
+ int parent;
+ bool notdone;
+
+ eventx = newevent.xkey;
+ eventy = newevent.ykey;
+ eventnum = heapsize;
+ notdone = eventnum > 0;
+ while (notdone)
+ {
+ parent = (eventnum - 1) >> 1;
+ if ((heap[parent].ykey < eventy) ||
+ ((heap[parent].ykey == eventy)
+ && (heap[parent].xkey <= eventx)))
+ {
+ notdone = false;
+ }
+ else
+ {
+ heap[eventnum] = heap[parent];
+ heap[eventnum].heapposition = eventnum;
+
+ eventnum = parent;
+ notdone = eventnum > 0;
+ }
+ }
+ heap[eventnum] = newevent;
+ newevent.heapposition = eventnum;
+ }
+
+ void Heapify(SweepEvent[] heap, int heapsize, int eventnum)
+ {
+ SweepEvent thisevent;
+ double eventx, eventy;
+ int leftchild, rightchild;
+ int smallest;
+ bool notdone;
+
+ thisevent = heap[eventnum];
+ eventx = thisevent.xkey;
+ eventy = thisevent.ykey;
+ leftchild = 2 * eventnum + 1;
+ notdone = leftchild < heapsize;
+ while (notdone)
+ {
+ if ((heap[leftchild].ykey < eventy) ||
+ ((heap[leftchild].ykey == eventy)
+ && (heap[leftchild].xkey < eventx)))
+ {
+ smallest = leftchild;
+ }
+ else
+ {
+ smallest = eventnum;
+ }
+ rightchild = leftchild + 1;
+ if (rightchild < heapsize)
+ {
+ if ((heap[rightchild].ykey < heap[smallest].ykey) ||
+ ((heap[rightchild].ykey == heap[smallest].ykey)
+ && (heap[rightchild].xkey < heap[smallest].xkey)))
+ {
+ smallest = rightchild;
+ }
+ }
+ if (smallest == eventnum)
+ {
+ notdone = false;
+ }
+ else
+ {
+ heap[eventnum] = heap[smallest];
+ heap[eventnum].heapposition = eventnum;
+ heap[smallest] = thisevent;
+ thisevent.heapposition = smallest;
+
+ eventnum = smallest;
+ leftchild = 2 * eventnum + 1;
+ notdone = leftchild < heapsize;
+ }
+ }
+ }
+
+ void HeapDelete(SweepEvent[] heap, int heapsize, int eventnum)
+ {
+ SweepEvent moveevent;
+ double eventx, eventy;
+ int parent;
+ bool notdone;
+
+ moveevent = heap[heapsize - 1];
+ if (eventnum > 0)
+ {
+ eventx = moveevent.xkey;
+ eventy = moveevent.ykey;
+ do
+ {
+ parent = (eventnum - 1) >> 1;
+ if ((heap[parent].ykey < eventy) ||
+ ((heap[parent].ykey == eventy)
+ && (heap[parent].xkey <= eventx)))
+ {
+ notdone = false;
+ }
+ else
+ {
+ heap[eventnum] = heap[parent];
+ heap[eventnum].heapposition = eventnum;
+
+ eventnum = parent;
+ notdone = eventnum > 0;
+ }
+ } while (notdone);
+ }
+ heap[eventnum] = moveevent;
+ moveevent.heapposition = eventnum;
+ Heapify(heap, heapsize - 1, eventnum);
+ }
+
+ void CreateHeap(out SweepEvent[] eventheap)
+ {
+ Vertex thisvertex;
+ int maxevents;
+ int i;
+ SweepEvent evt;
+
+ maxevents = (3 * mesh.invertices) / 2;
+ eventheap = new SweepEvent[maxevents];
+
+ i = 0;
+ foreach (var v in mesh.vertices.Values)
+ {
+ thisvertex = v;
+ evt = new SweepEvent();
+ evt.vertexEvent = thisvertex;
+ evt.xkey = thisvertex.pt.X;
+ evt.ykey = thisvertex.pt.Y;
+ HeapInsert(eventheap, i++, evt);
+
+ }
+ }
+
+ #endregion
+
+ #region Splaytree
+
+ SplayNode Splay(SplayNode splaytree, Vertex searchpoint, ref Otri searchtri)
+ {
+ SplayNode child, grandchild;
+ SplayNode lefttree, righttree;
+ SplayNode leftright;
+ Vertex checkvertex;
+ bool rightofroot, rightofchild;
+
+ if (splaytree == null)
+ {
+ return null;
+ }
+ checkvertex = splaytree.keyedge.Dest();
+ if (checkvertex == splaytree.keydest)
+ {
+ rightofroot = RightOfHyperbola(ref splaytree.keyedge, searchpoint);
+ if (rightofroot)
+ {
+ splaytree.keyedge.Copy(ref searchtri);
+ child = splaytree.rchild;
+ }
+ else
+ {
+ child = splaytree.lchild;
+ }
+ if (child == null)
+ {
+ return splaytree;
+ }
+ checkvertex = child.keyedge.Dest();
+ if (checkvertex != child.keydest)
+ {
+ child = Splay(child, searchpoint, ref searchtri);
+ if (child == null)
+ {
+ if (rightofroot)
+ {
+ splaytree.rchild = null;
+ }
+ else
+ {
+ splaytree.lchild = null;
+ }
+ return splaytree;
+ }
+ }
+ rightofchild = RightOfHyperbola(ref child.keyedge, searchpoint);
+ if (rightofchild)
+ {
+ child.keyedge.Copy(ref searchtri);
+ grandchild = Splay(child.rchild, searchpoint, ref searchtri);
+ child.rchild = grandchild;
+ }
+ else
+ {
+ grandchild = Splay(child.lchild, searchpoint, ref searchtri);
+ child.lchild = grandchild;
+ }
+ if (grandchild == null)
+ {
+ if (rightofroot)
+ {
+ splaytree.rchild = child.lchild;
+ child.lchild = splaytree;
+ }
+ else
+ {
+ splaytree.lchild = child.rchild;
+ child.rchild = splaytree;
+ }
+ return child;
+ }
+ if (rightofchild)
+ {
+ if (rightofroot)
+ {
+ splaytree.rchild = child.lchild;
+ child.lchild = splaytree;
+ }
+ else
+ {
+ splaytree.lchild = grandchild.rchild;
+ grandchild.rchild = splaytree;
+ }
+ child.rchild = grandchild.lchild;
+ grandchild.lchild = child;
+ }
+ else
+ {
+ if (rightofroot)
+ {
+ splaytree.rchild = grandchild.lchild;
+ grandchild.lchild = splaytree;
+ }
+ else
+ {
+ splaytree.lchild = child.rchild;
+ child.rchild = splaytree;
+ }
+ child.lchild = grandchild.rchild;
+ grandchild.rchild = child;
+ }
+ return grandchild;
+ }
+ else
+ {
+ lefttree = Splay(splaytree.lchild, searchpoint, ref searchtri);
+ righttree = Splay(splaytree.rchild, searchpoint, ref searchtri);
+
+ splaynodes.Remove(splaytree);
+ if (lefttree == null)
+ {
+ return righttree;
+ }
+ else if (righttree == null)
+ {
+ return lefttree;
+ }
+ else if (lefttree.rchild == null)
+ {
+ lefttree.rchild = righttree.lchild;
+ righttree.lchild = lefttree;
+ return righttree;
+ }
+ else if (righttree.lchild == null)
+ {
+ righttree.lchild = lefttree.rchild;
+ lefttree.rchild = righttree;
+ return lefttree;
+ }
+ else
+ {
+ // printf("Holy Toledo!!!\n");
+ leftright = lefttree.rchild;
+ while (leftright.rchild != null)
+ {
+ leftright = leftright.rchild;
+ }
+ leftright.rchild = righttree;
+ return lefttree;
+ }
+ }
+ }
+
+ SplayNode SplayInsert(SplayNode splayroot, Otri newkey, Vertex searchpoint)
+ {
+ SplayNode newsplaynode;
+
+ newsplaynode = new SplayNode(); //poolalloc(m.splaynodes);
+ splaynodes.Add(newsplaynode);
+ newkey.Copy(ref newsplaynode.keyedge);
+ newsplaynode.keydest = newkey.Dest();
+ if (splayroot == null)
+ {
+ newsplaynode.lchild = null;
+ newsplaynode.rchild = null;
+ }
+ else if (RightOfHyperbola(ref splayroot.keyedge, searchpoint))
+ {
+ newsplaynode.lchild = splayroot;
+ newsplaynode.rchild = splayroot.rchild;
+ splayroot.rchild = null;
+ }
+ else
+ {
+ newsplaynode.lchild = splayroot.lchild;
+ newsplaynode.rchild = splayroot;
+ splayroot.lchild = null;
+ }
+ return newsplaynode;
+ }
+
+ #endregion
+
+ SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey,
+ Vertex pa, Vertex pb, Vertex pc, double topy)
+ {
+ double ccwabc;
+ double xac, yac, xbc, ybc;
+ double aclen2, bclen2;
+ Vertex searchpoint = new Vertex(mesh.nextras);
+ Otri dummytri = default(Otri);
+
+ ccwabc = Primitives.CounterClockwise(pa.pt, pb.pt, pc.pt);
+ xac = pa.pt.X - pc.pt.X;
+ yac = pa.pt.Y - pc.pt.Y;
+ xbc = pb.pt.X - pc.pt.X;
+ ybc = pb.pt.Y - pc.pt.Y;
+ aclen2 = xac * xac + yac * yac;
+ bclen2 = xbc * xbc + ybc * ybc;
+ searchpoint.pt.X = pc.pt.X - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc);
+ searchpoint.pt.Y = topy;
+ return SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint);
+ }
+
+ bool RightOfHyperbola(ref Otri fronttri, Vertex newsite)
+ {
+ Vertex leftvertex, rightvertex;
+ double dxa, dya, dxb, dyb;
+
+ Statistic.HyperbolaCount++;
+
+ leftvertex = fronttri.Dest();
+ rightvertex = fronttri.Apex();
+ if ((leftvertex.pt.Y < rightvertex.pt.Y) ||
+ ((leftvertex.pt.Y == rightvertex.pt.Y) &&
+ (leftvertex.pt.X < rightvertex.pt.X)))
+ {
+ if (newsite.pt.X >= rightvertex.pt.X)
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (newsite.pt.X <= leftvertex.pt.X)
+ {
+ return false;
+ }
+ }
+ dxa = leftvertex.pt.X - newsite.pt.X;
+ dya = leftvertex.pt.Y - newsite.pt.Y;
+ dxb = rightvertex.pt.X - newsite.pt.X;
+ dyb = rightvertex.pt.Y - newsite.pt.Y;
+ return dya * (dxb * dxb + dyb * dyb) > dyb * (dxa * dxa + dya * dya);
+ }
+
+ double CircleTop(Vertex pa, Vertex pb, Vertex pc, double ccwabc)
+ {
+ double xac, yac, xbc, ybc, xab, yab;
+ double aclen2, bclen2, ablen2;
+
+ Statistic.CircleTopCount++;
+
+ xac = pa.pt.X - pc.pt.X;
+ yac = pa.pt.Y - pc.pt.Y;
+ xbc = pb.pt.X - pc.pt.X;
+ ybc = pb.pt.Y - pc.pt.Y;
+ xab = pa.pt.X - pb.pt.X;
+ yab = pa.pt.Y - pb.pt.Y;
+ aclen2 = xac * xac + yac * yac;
+ bclen2 = xbc * xbc + ybc * ybc;
+ ablen2 = xab * xab + yab * yab;
+ return pc.pt.Y + (xac * bclen2 - xbc * aclen2 + Math.Sqrt(aclen2 * bclen2 * ablen2)) / (2.0 * ccwabc);
+ }
+
+ void Check4DeadEvent(ref Otri checktri, SweepEvent[] eventheap, ref int heapsize)
+ {
+ SweepEvent deadevent;
+ SweepEventVertex eventvertex;
+ int eventnum = -1;
+
+ eventvertex = checktri.Org() as SweepEventVertex;
+ if (eventvertex != null)
+ {
+ deadevent = eventvertex.evt;
+ eventnum = deadevent.heapposition;
+
+ HeapDelete(eventheap, heapsize, eventnum);
+ heapsize--;
+ checktri.SetOrg(null);
+ }
+ }
+
+ SplayNode FrontLocate(SplayNode splayroot, Otri bottommost, Vertex searchvertex,
+ ref Otri searchtri, ref bool farright)
+ {
+ bool farrightflag;
+
+ bottommost.Copy(ref searchtri);
+ splayroot = Splay(splayroot, searchvertex, ref searchtri);
+
+ farrightflag = false;
+ while (!farrightflag && RightOfHyperbola(ref searchtri, searchvertex))
+ {
+ searchtri.OnextSelf();
+ farrightflag = searchtri.Equal(bottommost);
+ }
+ farright = farrightflag;
+ return splayroot;
+ }
+
+ ///
+ /// Removes ghost triangles.
+ ///
+ ///
+ /// Number of vertices on the hull.
+ int RemoveGhosts(Mesh m, ref Otri startghost)
+ {
+ Otri searchedge = default(Otri);
+ Otri dissolveedge = default(Otri);
+ Otri deadtriangle = default(Otri);
+ Vertex markorg;
+ int hullsize;
+
+ // Find an edge on the convex hull to start point location from.
+ startghost.Lprev(ref searchedge);
+ searchedge.SymSelf();
+ Mesh.dummytri.neighbors[0] = searchedge;
+ // Remove the bounding box and count the convex hull edges.
+ startghost.Copy(ref dissolveedge);
+ hullsize = 0;
+ do
+ {
+ hullsize++;
+ dissolveedge.Lnext(ref deadtriangle);
+ dissolveedge.LprevSelf();
+ dissolveedge.SymSelf();
+ // If no PSLG is involved, set the boundary markers of all the vertices
+ // on the convex hull. If a PSLG is used, this step is done later.
+ if (!Behavior.Poly)
+ {
+ // Watch out for the case where all the input vertices are collinear.
+ if (dissolveedge.triangle != Mesh.dummytri)
+ {
+ markorg = dissolveedge.Org();
+ if (markorg.mark == 0)
+ {
+ markorg.mark = 1;
+ }
+ }
+ }
+ // Remove a bounding triangle from a convex hull triangle.
+ dissolveedge.Dissolve();
+ // Find the next bounding triangle.
+ deadtriangle.Sym(ref dissolveedge);
+ // Delete the bounding triangle.
+ m.TriangleDealloc(deadtriangle.triangle);
+ } while (!dissolveedge.Equal(startghost));
+
+ return hullsize;
+ }
+
+ public int Triangulate(Mesh mesh)
+ {
+ this.mesh = mesh;
+
+ // Nonexistent x value used as a flag to mark circle events in sweepline
+ // Delaunay algorithm.
+ xminextreme = 10 * mesh.xmin - 9 * mesh.xmax;
+
+ SweepEvent[] eventheap;
+
+ SweepEvent nextevent;
+ SweepEvent newevent;
+ SplayNode splayroot;
+ Otri bottommost = default(Otri);
+ Otri searchtri = default(Otri);
+ Otri fliptri;
+ Otri lefttri = default(Otri);
+ Otri righttri = default(Otri);
+ Otri farlefttri = default(Otri);
+ Otri farrighttri = default(Otri);
+ Otri inserttri = default(Otri);
+ Vertex firstvertex, secondvertex;
+ Vertex nextvertex, lastvertex;
+ Vertex connectvertex;
+ Vertex leftvertex, midvertex, rightvertex;
+ double lefttest, righttest;
+ int heapsize;
+ bool check4events, farrightflag = false;
+
+ splaynodes = new List();
+ splayroot = null;
+
+ CreateHeap(out eventheap);//, out events, out freeevents);
+ heapsize = mesh.invertices;
+
+ mesh.MakeTriangle(ref lefttri);
+ mesh.MakeTriangle(ref righttri);
+ lefttri.Bond(ref righttri);
+ lefttri.LnextSelf();
+ righttri.LprevSelf();
+ lefttri.Bond(ref righttri);
+ lefttri.LnextSelf();
+ righttri.LprevSelf();
+ lefttri.Bond(ref righttri);
+ firstvertex = eventheap[0].vertexEvent;
+
+ HeapDelete(eventheap, heapsize, 0);
+ heapsize--;
+ do
+ {
+ if (heapsize == 0)
+ {
+ SimpleLogger.Instance.Error("Input vertices are all identical.", "SweepLine.SweepLineDelaunay()");
+ throw new Exception("Input vertices are all identical.");
+ }
+ secondvertex = eventheap[0].vertexEvent;
+ HeapDelete(eventheap, heapsize, 0);
+ heapsize--;
+ if ((firstvertex.pt.X == secondvertex.pt.X) &&
+ (firstvertex.pt.Y == secondvertex.pt.Y))
+ {
+ if (Behavior.Verbose)
+ {
+ SimpleLogger.Instance.Warning("A duplicate vertex appeared and was ignored.",
+ "SweepLine.SweepLineDelaunay().1");
+ }
+ secondvertex.type = VertexType.UndeadVertex;
+ mesh.undeads++;
+ }
+ } while ((firstvertex.pt.X == secondvertex.pt.X) &&
+ (firstvertex.pt.Y == secondvertex.pt.Y));
+ lefttri.SetOrg(firstvertex);
+ lefttri.SetDest(secondvertex);
+ righttri.SetOrg(secondvertex);
+ righttri.SetDest(firstvertex);
+ lefttri.Lprev(ref bottommost);
+ lastvertex = secondvertex;
+
+ while (heapsize > 0)
+ {
+ nextevent = eventheap[0];
+ HeapDelete(eventheap, heapsize, 0);
+ heapsize--;
+ check4events = true;
+ if (nextevent.xkey < mesh.xmin)
+ {
+ fliptri = nextevent.otriEvent;
+ fliptri.Oprev(ref farlefttri);
+ Check4DeadEvent(ref farlefttri, eventheap, ref heapsize);
+ fliptri.Onext(ref farrighttri);
+ Check4DeadEvent(ref farrighttri, eventheap, ref heapsize);
+
+ if (farlefttri.Equal(bottommost))
+ {
+ fliptri.Lprev(ref bottommost);
+ }
+ mesh.Flip(ref fliptri);
+ fliptri.SetApex(null);
+ fliptri.Lprev(ref lefttri);
+ fliptri.Lnext(ref righttri);
+ lefttri.Sym(ref farlefttri);
+
+ if (randomnation(SAMPLERATE) == 0)
+ {
+ fliptri.SymSelf();
+ leftvertex = fliptri.Dest();
+ midvertex = fliptri.Apex();
+ rightvertex = fliptri.Org();
+ splayroot = CircleTopInsert(splayroot, lefttri, leftvertex, midvertex, rightvertex, nextevent.ykey);
+ }
+ }
+ else
+ {
+ nextvertex = nextevent.vertexEvent;
+ if ((nextvertex.pt.X == lastvertex.pt.X) &&
+ (nextvertex.pt.Y == lastvertex.pt.Y))
+ {
+ if (Behavior.Verbose)
+ {
+ SimpleLogger.Instance.Warning("A duplicate vertex appeared and was ignored.",
+ "SweepLine.SweepLineDelaunay().2");
+ }
+ nextvertex.type = VertexType.UndeadVertex;
+ mesh.undeads++;
+ check4events = false;
+ }
+ else
+ {
+ lastvertex = nextvertex;
+
+ splayroot = FrontLocate(splayroot, bottommost, nextvertex,
+ ref searchtri, ref farrightflag);
+ //
+ bottommost.Copy(ref searchtri);
+ farrightflag = false;
+ while (!farrightflag && RightOfHyperbola(ref searchtri, nextvertex))
+ {
+ searchtri.OnextSelf();
+ farrightflag = searchtri.Equal(bottommost);
+ }
+
+
+ Check4DeadEvent(ref searchtri, eventheap, ref heapsize);
+
+ searchtri.Copy(ref farrighttri);
+ searchtri.Sym(ref farlefttri);
+ mesh.MakeTriangle(ref lefttri);
+ mesh.MakeTriangle(ref righttri);
+ connectvertex = farrighttri.Dest();
+ lefttri.SetOrg(connectvertex);
+ lefttri.SetDest(nextvertex);
+ righttri.SetOrg(nextvertex);
+ righttri.SetDest(connectvertex);
+ lefttri.Bond(ref righttri);
+ lefttri.LnextSelf();
+ righttri.LprevSelf();
+ lefttri.Bond(ref righttri);
+ lefttri.LnextSelf();
+ righttri.LprevSelf();
+ lefttri.Bond(ref farlefttri);
+ righttri.Bond(ref farrighttri);
+ if (!farrightflag && farrighttri.Equal(bottommost))
+ {
+ lefttri.Copy(ref bottommost);
+ }
+
+ if (randomnation(SAMPLERATE) == 0)
+ {
+ splayroot = SplayInsert(splayroot, lefttri, nextvertex);
+ }
+ else if (randomnation(SAMPLERATE) == 0)
+ {
+ righttri.Lnext(ref inserttri);
+ splayroot = SplayInsert(splayroot, inserttri, nextvertex);
+ }
+ }
+ }
+
+ if (check4events)
+ {
+ leftvertex = farlefttri.Apex();
+ midvertex = lefttri.Dest();
+ rightvertex = lefttri.Apex();
+ lefttest = Primitives.CounterClockwise(leftvertex.pt, midvertex.pt, rightvertex.pt);
+ if (lefttest > 0.0)
+ {
+ newevent = new SweepEvent();
+
+ newevent.xkey = xminextreme;
+ newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, lefttest);
+ newevent.otriEvent = lefttri;
+ HeapInsert(eventheap, heapsize, newevent);
+ heapsize++;
+ lefttri.SetOrg(new SweepEventVertex(newevent));
+ }
+ leftvertex = righttri.Apex();
+ midvertex = righttri.Org();
+ rightvertex = farrighttri.Apex();
+ righttest = Primitives.CounterClockwise(leftvertex.pt, midvertex.pt, rightvertex.pt);
+ if (righttest > 0.0)
+ {
+ newevent = new SweepEvent();
+
+ newevent.xkey = xminextreme;
+ newevent.ykey = CircleTop(leftvertex, midvertex, rightvertex, righttest);
+ newevent.otriEvent = farrighttri;
+ HeapInsert(eventheap, heapsize, newevent);
+ heapsize++;
+ farrighttri.SetOrg(new SweepEventVertex(newevent));
+ }
+ }
+ }
+
+ splaynodes.Clear();
+ bottommost.LprevSelf();
+ return RemoveGhosts(mesh, ref bottommost);
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/BadTriQueue.cs b/Triangle.NET/Triangle/BadTriQueue.cs
new file mode 100644
index 0000000..db47b18
--- /dev/null
+++ b/Triangle.NET/Triangle/BadTriQueue.cs
@@ -0,0 +1,202 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet
+{
+ using System.Collections.Generic;
+ using TriangleNet.Data;
+
+ ///
+ /// TODO: Update summary.
+ ///
+ class BadTriQueue
+ {
+ static readonly double SQRT2 = 1.4142135623730950488016887242096980785696718753769480732;
+
+ public List badtriangles;
+
+ // Variables that maintain the bad triangle queues. The queues are
+ // ordered from 4095 (highest priority) to 0 (lowest priority).
+
+ BadTriangle[] queuefront;//[4096];
+ BadTriangle[] queuetail;//[4096];
+ int[] nextnonemptyq;//[4096];
+ int firstnonemptyq;
+
+ public BadTriQueue()
+ {
+ badtriangles = new List();
+
+ queuefront = new BadTriangle[4096];
+ queuetail = new BadTriangle[4096];
+ nextnonemptyq = new int[4096];
+
+ firstnonemptyq = -1;
+ }
+
+ #region Queue
+
+ ///
+ /// Add a bad triangle data structure to the end of a queue.
+ ///
+ ///
+ ///
+ // The queue is actually a set of 4096 queues. I use multiple queues to
+ // give priority to smaller angles. I originally implemented a heap, but
+ // the queues are faster by a larger margin than I'd suspected.
+ ///
+ public void Enqueue(BadTriangle badtri)
+ {
+ double length, multiplier;
+ int exponent, expincrement;
+ int queuenumber;
+ int posexponent;
+ int i;
+
+ // Determine the appropriate queue to put the bad triangle into.
+ // Recall that the key is the square of its shortest edge length.
+ if (badtri.key >= 1.0)
+ {
+ length = badtri.key;
+ posexponent = 1;
+ }
+ else
+ {
+ // 'badtri.key' is 2.0 to a negative exponent, so we'll record that
+ // fact and use the reciprocal of 'badtri.key', which is > 1.0.
+ length = 1.0 / badtri.key;
+ posexponent = 0;
+ }
+ // 'length' is approximately 2.0 to what exponent? The following code
+ // determines the answer in time logarithmic in the exponent.
+ exponent = 0;
+ while (length > 2.0)
+ {
+ // Find an approximation by repeated squaring of two.
+ expincrement = 1;
+ multiplier = 0.5;
+ while (length * multiplier * multiplier > 1.0)
+ {
+ expincrement *= 2;
+ multiplier *= multiplier;
+ }
+ // Reduce the value of 'length', then iterate if necessary.
+ exponent += expincrement;
+ length *= multiplier;
+ }
+ // 'length' is approximately squareroot(2.0) to what exponent?
+ exponent = 2 * exponent + (length > SQRT2 ? 1 : 0);
+ // 'exponent' is now in the range 0...2047 for IEEE double precision.
+ // Choose a queue in the range 0...4095. The shortest edges have the
+ // highest priority (queue 4095).
+ if (posexponent > 0)
+ {
+ queuenumber = 2047 - exponent;
+ }
+ else
+ {
+ queuenumber = 2048 + exponent;
+ }
+
+ // Are we inserting into an empty queue?
+ if (queuefront[queuenumber] == null)
+ {
+ // Yes, we are inserting into an empty queue.
+ // Will this become the highest-priority queue?
+ if (queuenumber > firstnonemptyq)
+ {
+ // Yes, this is the highest-priority queue.
+ nextnonemptyq[queuenumber] = firstnonemptyq;
+ firstnonemptyq = queuenumber;
+ }
+ else
+ {
+ // No, this is not the highest-priority queue.
+ // Find the queue with next higher priority.
+ i = queuenumber + 1;
+ while (queuefront[i] == null)
+ {
+ i++;
+ }
+ // Mark the newly nonempty queue as following a higher-priority queue.
+ nextnonemptyq[queuenumber] = nextnonemptyq[i];
+ nextnonemptyq[i] = queuenumber;
+ }
+ // Put the bad triangle at the beginning of the (empty) queue.
+ queuefront[queuenumber] = badtri;
+ }
+ else
+ {
+ // Add the bad triangle to the end of an already nonempty queue.
+ queuetail[queuenumber].nexttriang = badtri;
+ }
+ // Maintain a pointer to the last triangle of the queue.
+ queuetail[queuenumber] = badtri;
+ // Newly enqueued bad triangle has no successor in the queue.
+ badtri.nexttriang = null;
+ }
+
+ ///
+ /// Add a bad triangle to the end of a queue.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Allocates a badtriang data structure for the triangle, then passes it to enqueuebadtriang().
+ ///
+ public void Enqueue(ref Otri enqtri, double minedge, Vertex enqapex, Vertex enqorg, Vertex enqdest)
+ {
+ // Allocate space for the bad triangle.
+ BadTriangle newbad = new BadTriangle();
+
+ newbad.poortri = enqtri;
+ newbad.key = minedge;
+ newbad.triangapex = enqapex;
+ newbad.triangorg = enqorg;
+ newbad.triangdest = enqdest;
+
+ Vertex org = enqtri.Org();
+ Vertex dest = enqtri.Dest();
+ Vertex apex = enqtri.Apex();
+
+ badtriangles.Add(newbad);
+
+ Enqueue(newbad);
+ }
+
+ ///
+ /// Remove a triangle from the front of the queue.
+ ///
+ ///
+ public BadTriangle Dequeue()
+ {
+ BadTriangle result;
+
+ // If no queues are nonempty, return NULL.
+ if (firstnonemptyq < 0)
+ {
+ return null;
+ }
+ // Find the first triangle of the highest-priority queue.
+ result = queuefront[firstnonemptyq];
+ // Remove the triangle from the queue.
+ queuefront[firstnonemptyq] = result.nexttriang;
+ // If this queue is now empty, note the new highest-priority
+ // nonempty queue.
+ if (result == queuetail[firstnonemptyq])
+ {
+ firstnonemptyq = nextnonemptyq[firstnonemptyq];
+ }
+ return result;
+ }
+
+ #endregion
+ }
+}
diff --git a/Triangle.NET/Triangle/Behavior.cs b/Triangle.NET/Triangle/Behavior.cs
new file mode 100644
index 0000000..fab900d
--- /dev/null
+++ b/Triangle.NET/Triangle/Behavior.cs
@@ -0,0 +1,73 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+using System;
+namespace TriangleNet
+{
+ // TODO: Make Behavior non-static and an instance member of mesh class.
+
+ ///
+ /// Controls the behavior of the meshing software.
+ ///
+ static class Behavior
+ {
+ public static bool Poly { get; set; }
+ public static bool Quality { get; set; }
+ public static bool VarArea { get; set; }
+ public static bool FixedArea { get; set; }
+ public static bool Usertest { get; set; }
+ public static bool RegionAttrib { get; set; }
+ public static bool Convex { get; set; }
+ public static bool Jettison { get; set; }
+ public static bool UseBoundaryMarkers { get; set; }
+ public static bool NoHoles { get; set; }
+ public static bool NoExact { get; set; }
+ public static bool ConformDel { get; set; }
+ public static TriangulationAlgorithm Algorithm { get; set; }
+ public static bool Verbose { get; set; }
+ public static bool UseSegments { get; set; }
+
+ public static int NoBisect { get; set; } // <- int
+ public static int Steiner { get; set; }
+
+ public static double MinAngle { get; set; }
+ public static double GoodAngle { get; set; }
+ public static double Offconstant { get; set; }
+ public static double MaxArea { get; set; }
+ public static double MaxAngle { get; set; }
+ public static double MaxGoodAngle { get; set; }
+
+ public static void Init()
+ {
+ Poly = false;
+ Quality = false;
+ VarArea = false;
+ FixedArea = false;
+ Usertest = false;
+ RegionAttrib = false;
+ Convex = false;
+ Jettison = false;
+ UseBoundaryMarkers = false;
+ NoHoles = false;
+ NoExact = false;
+ ConformDel = false;
+ Algorithm = TriangulationAlgorithm.Dwyer;
+ Verbose = false;
+ UseSegments = true;
+
+ NoBisect = 0;
+ Steiner = -1;
+
+ MinAngle = 0.0;
+ GoodAngle = 0.0;
+ MaxAngle = 0.0;
+ MaxGoodAngle = 0.0;
+ MaxArea = -1.0;
+ Offconstant = 0.0;
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Carver.cs b/Triangle.NET/Triangle/Carver.cs
new file mode 100644
index 0000000..47fc91f
--- /dev/null
+++ b/Triangle.NET/Triangle/Carver.cs
@@ -0,0 +1,528 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet
+{
+ using TriangleNet.Data;
+ using System;
+
+ ///
+ /// Carves holes into the triangulation.
+ ///
+ class Carver
+ {
+ Mesh mesh;
+
+ public Carver(Mesh mesh)
+ {
+ this.mesh = mesh;
+ }
+
+ ///
+ /// Virally infect all of the triangles of the convex hull that are not
+ /// protected by subsegments. Where there are subsegments, set boundary
+ /// markers as appropriate.
+ ///
+ void InfectHull()
+ {
+ Otri hulltri = default(Otri);
+ Otri nexttri = default(Otri);
+ Otri starttri = default(Otri);
+ Osub hullsubseg = default(Osub);
+ Vertex horg, hdest;
+
+ // Find a triangle handle on the hull.
+ hulltri.triangle = Mesh.dummytri;
+ hulltri.orient = 0;
+ hulltri.SymSelf();
+ // Remember where we started so we know when to stop.
+ hulltri.Copy(ref starttri);
+ // Go once counterclockwise around the convex hull.
+ do
+ {
+ // Ignore triangles that are already infected.
+ if (!hulltri.IsInfected())
+ {
+ // Is the triangle protected by a subsegment?
+ hulltri.SegPivot(ref hullsubseg);
+ if (hullsubseg.ss == Mesh.dummysub)
+ {
+ // The triangle is not protected; infect it.
+ if (!hulltri.IsInfected())
+ {
+ hulltri.Infect();
+ mesh.viri.Add(hulltri.triangle);
+ }
+ }
+ else
+ {
+ // The triangle is protected; set boundary markers if appropriate.
+ if (hullsubseg.ss.boundary == 0)
+ {
+ hullsubseg.ss.boundary = 1;
+ horg = hulltri.Org();
+ hdest = hulltri.Dest();
+ if (horg.mark == 0)
+ {
+ horg.mark = 1;
+ }
+ if (hdest.mark == 0)
+ {
+ hdest.mark = 1;
+ }
+ }
+ }
+ }
+ // To find the next hull edge, go clockwise around the next vertex.
+ hulltri.LnextSelf();
+ hulltri.Oprev(ref nexttri);
+ while (nexttri.triangle != Mesh.dummytri)
+ {
+ nexttri.Copy(ref hulltri);
+ hulltri.Oprev(ref nexttri);
+ }
+ } while (!hulltri.Equal(starttri));
+ }
+
+ ///
+ /// Spread the virus from all infected triangles to any neighbors not
+ /// protected by subsegments. Delete all infected triangles.
+ ///
+ ///
+ /// This is the procedure that actually creates holes and concavities.
+ ///
+ /// This procedure operates in two phases. The first phase identifies all
+ /// the triangles that will die, and marks them as infected. They are
+ /// marked to ensure that each triangle is added to the virus pool only
+ /// once, so the procedure will terminate.
+ ///
+ /// The second phase actually eliminates the infected triangles. It also
+ /// eliminates orphaned vertices.
+ ///
+ void Plague()
+ {
+ Otri testtri = default(Otri);
+ Otri neighbor = default(Otri);
+ Triangle virusloop;
+ Osub neighborsubseg = default(Osub);
+ Vertex testvertex;
+ Vertex norg, ndest;
+ //Vertex deadorg, deaddest, deadapex;
+ bool killorg;
+
+ // Loop through all the infected triangles, spreading the virus to
+ // their neighbors, then to their neighbors' neighbors.
+ for (int i = 0; i < mesh.viri.Count; i++)
+ {
+ virusloop = mesh.viri[i];
+ testtri.triangle = virusloop;
+ // A triangle is marked as infected by messing with one of its pointers
+ // to subsegments, setting it to an illegal value. Hence, we have to
+ // temporarily uninfect this triangle so that we can examine its
+ // adjacent subsegments.
+ // TODO: Not true in the C# version (so we could skip this).
+ testtri.Uninfect();
+
+ // Check each of the triangle's three neighbors.
+ for (testtri.orient = 0; testtri.orient < 3; testtri.orient++)
+ {
+ // Find the neighbor.
+ testtri.Sym(ref neighbor);
+ // Check for a subsegment between the triangle and its neighbor.
+ testtri.SegPivot(ref neighborsubseg);
+ // Check if the neighbor is nonexistent or already infected.
+ if ((neighbor.triangle == Mesh.dummytri) || neighbor.IsInfected())
+ {
+ if (neighborsubseg.ss != Mesh.dummysub)
+ {
+ // There is a subsegment separating the triangle from its
+ // neighbor, but both triangles are dying, so the subsegment
+ // dies too.
+ mesh.SubsegDealloc(neighborsubseg.ss);
+ if (neighbor.triangle != Mesh.dummytri)
+ {
+ // Make sure the subsegment doesn't get deallocated again
+ // later when the infected neighbor is visited.
+ neighbor.Uninfect();
+ neighbor.SegDissolve();
+ neighbor.Infect();
+ }
+ }
+ }
+ else
+ { // The neighbor exists and is not infected.
+ if (neighborsubseg.ss == Mesh.dummysub)
+ {
+ // There is no subsegment protecting the neighbor, so
+ // the neighbor becomes infected.
+ neighbor.Infect();
+ // Ensure that the neighbor's neighbors will be infected.
+ mesh.viri.Add(neighbor.triangle);
+ }
+ else
+ { // The neighbor is protected by a subsegment.
+ // Remove this triangle from the subsegment.
+ neighborsubseg.TriDissolve();
+ // The subsegment becomes a boundary. Set markers accordingly.
+ if (neighborsubseg.ss.boundary == 0)
+ {
+ neighborsubseg.ss.boundary = 1;
+ }
+ norg = neighbor.Org();
+ ndest = neighbor.Dest();
+ if (norg.mark == 0)
+ {
+ norg.mark = 1;
+ }
+ if (ndest.mark == 0)
+ {
+ ndest.mark = 1;
+ }
+ }
+ }
+ }
+ // Remark the triangle as infected, so it doesn't get added to the
+ // virus pool again.
+ testtri.Infect();
+ }
+
+ for (int i = 0; i < mesh.viri.Count; i++)
+ {
+ virusloop = mesh.viri[i];
+ testtri.triangle = virusloop;
+
+ // Check each of the three corners of the triangle for elimination.
+ // This is done by walking around each vertex, checking if it is
+ // still connected to at least one live triangle.
+ for (testtri.orient = 0; testtri.orient < 3; testtri.orient++)
+ {
+ testvertex=testtri.Org();
+ // Check if the vertex has already been tested.
+ if (testvertex != null)
+ {
+ killorg = true;
+ // Mark the corner of the triangle as having been tested.
+ testtri.SetOrg(null);
+ // Walk counterclockwise about the vertex.
+ testtri.Onext(ref neighbor);
+ // Stop upon reaching a boundary or the starting triangle.
+ while ((neighbor.triangle != Mesh.dummytri) &&
+ (!neighbor.Equal(testtri)))
+ {
+ if (neighbor.IsInfected())
+ {
+ // Mark the corner of this triangle as having been tested.
+ neighbor.SetOrg(null);
+ }
+ else
+ {
+ // A live triangle. The vertex survives.
+ killorg = false;
+ }
+ // Walk counterclockwise about the vertex.
+ neighbor.OnextSelf();
+ }
+ // If we reached a boundary, we must walk clockwise as well.
+ if (neighbor.triangle == Mesh.dummytri)
+ {
+ // Walk clockwise about the vertex.
+ testtri.Oprev(ref neighbor);
+ // Stop upon reaching a boundary.
+ while (neighbor.triangle != Mesh.dummytri)
+ {
+ if (neighbor.IsInfected())
+ {
+ // Mark the corner of this triangle as having been tested.
+ neighbor.SetOrg(null);
+ }
+ else
+ {
+ // A live triangle. The vertex survives.
+ killorg = false;
+ }
+ // Walk clockwise about the vertex.
+ neighbor.OprevSelf();
+ }
+ }
+ if (killorg)
+ {
+ // Deleting vertex
+ testvertex.type = VertexType.UndeadVertex;
+ mesh.undeads++;
+ }
+ }
+ }
+
+ // Record changes in the number of boundary edges, and disconnect
+ // dead triangles from their neighbors.
+ for (testtri.orient = 0; testtri.orient < 3; testtri.orient++)
+ {
+ testtri.Sym(ref neighbor);
+ if (neighbor.triangle == Mesh.dummytri)
+ {
+ // There is no neighboring triangle on this edge, so this edge
+ // is a boundary edge. This triangle is being deleted, so this
+ // boundary edge is deleted.
+ mesh.hullsize--;
+ }
+ else
+ {
+ // Disconnect the triangle from its neighbor.
+ neighbor.Dissolve();
+ // There is a neighboring triangle on this edge, so this edge
+ // becomes a boundary edge when this triangle is deleted.
+ mesh.hullsize++;
+ }
+ }
+ // Return the dead triangle to the pool of triangles.
+ mesh.TriangleDealloc(testtri.triangle);
+ }
+ // Empty the virus pool.
+ mesh.viri.Clear();
+ }
+
+ ///
+ /// Spread regional attributes and/or area constraints (from a .poly file)
+ /// throughout the mesh.
+ ///
+ ///
+ ///
+ ///
+ /// This procedure operates in two phases. The first phase spreads an
+ /// attribute and/or an area constraint through a (segment-bounded) region.
+ /// The triangles are marked to ensure that each triangle is added to the
+ /// virus pool only once, so the procedure will terminate.
+ ///
+ /// The second phase uninfects all infected triangles, returning them to
+ /// normal.
+ ///
+ void RegionPlague(double attribute, double area)
+ {
+ Otri testtri = default(Otri);
+ Otri neighbor = default(Otri);
+ Osub neighborsubseg = default(Osub);
+
+ // Loop through all the infected triangles, spreading the attribute
+ // and/or area constraint to their neighbors, then to their neighbors'
+ // neighbors.
+ for (int i = 0; i < mesh.viri.Count; i++)
+ {
+ testtri.triangle = mesh.viri[i];
+ // A triangle is marked as infected by messing with one of its pointers
+ // to subsegments, setting it to an illegal value. Hence, we have to
+ // temporarily uninfect this triangle so that we can examine its
+ // adjacent subsegments.
+ // TODO: Not true in the C# version (so we could skip this).
+ testtri.Uninfect();
+ if (Behavior.RegionAttrib)
+ {
+ // Set an attribute (Note: the attributes array was resized before).
+ testtri.triangle.attributes[mesh.eextras] = attribute;
+ }
+ if (Behavior.VarArea)
+ {
+ // Set an area constraint.
+ testtri.triangle.area = area;
+ }
+
+ // Check each of the triangle's three neighbors.
+ for (testtri.orient = 0; testtri.orient < 3; testtri.orient++)
+ {
+ // Find the neighbor.
+ testtri.Sym(ref neighbor);
+ // Check for a subsegment between the triangle and its neighbor.
+ testtri.SegPivot(ref neighborsubseg);
+ // Make sure the neighbor exists, is not already infected, and
+ // isn't protected by a subsegment.
+ if ((neighbor.triangle != Mesh.dummytri) && !neighbor.IsInfected()
+ && (neighborsubseg.ss == Mesh.dummysub))
+ {
+ // Infect the neighbor.
+ neighbor.Infect();
+ // Ensure that the neighbor's neighbors will be infected.
+ mesh.viri.Add(neighbor.triangle);
+ }
+ }
+ // Remark the triangle as infected, so it doesn't get added to the
+ // virus pool again.
+ testtri.Infect();
+ }
+
+ // Uninfect all triangles.
+ foreach (var virus in mesh.viri)
+ {
+ testtri.triangle = virus;
+ testtri.Uninfect();
+ }
+
+ // Empty the virus pool.
+ mesh.viri.Clear();
+ }
+ ///
+ /// Find the holes and infect them. Find the area constraints and infect
+ /// them. Infect the convex hull. Spread the infection and kill triangles.
+ /// Spread the area constraints.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void CarveHoles()
+ {
+ Otri searchtri = default(Otri);
+ Otri tri = default(Otri);
+ Vertex searchorg, searchdest;
+ LocateResult intersect;
+
+ int numRegions = mesh.regions.Count;
+ Otri[] regiontris = (numRegions > 0) ? new Otri[numRegions] : null;
+
+ if (!Behavior.Convex)
+ {
+ // Mark as infected any unprotected triangles on the boundary.
+ // This is one way by which concavities are created.
+ InfectHull();
+ }
+
+ if (!Behavior.NoHoles)
+ {
+ // Infect each triangle in which a hole lies.
+ foreach (var hole in mesh.holes)
+ {
+ // Ignore holes that aren't within the bounds of the mesh.
+ if ((hole.X >= mesh.xmin) && (hole.X <= mesh.xmax)
+ && (hole.Y >= mesh.ymin) && (hole.Y <= mesh.ymax))
+ {
+ // Start searching from some triangle on the outer boundary.
+ searchtri.triangle = Mesh.dummytri;
+ searchtri.orient = 0;
+ searchtri.SymSelf();
+ // Ensure that the hole is to the left of this boundary edge;
+ // otherwise, locate() will falsely report that the hole
+ // falls within the starting triangle.
+ searchorg = searchtri.Org();
+ searchdest = searchtri.Dest();
+ if (Primitives.CounterClockwise(searchorg.pt, searchdest.pt, hole) > 0.0)
+ {
+ // Find a triangle that contains the hole.
+ intersect = mesh.Locate(hole, ref searchtri);
+ if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected()))
+ {
+ // Infect the triangle. This is done by marking the triangle
+ // as infected and including the triangle in the virus pool.
+ searchtri.Infect();
+ mesh.viri.Add(searchtri.triangle);
+ }
+ }
+ }
+ }
+ }
+
+ // Now, we have to find all the regions BEFORE we carve the holes, because locate() won't
+ // work when the triangulation is no longer convex. (Incidentally, this is the reason why
+ // regional attributes and area constraints can't be used when refining a preexisting mesh,
+ // which might not be convex; they can only be used with a freshly triangulated PSLG.)
+ if (numRegions > 0)
+ {
+ int i = 0;
+
+ // Find the starting triangle for each region.
+ foreach (var region in mesh.regions)
+ {
+ regiontris[i].triangle = Mesh.dummytri;
+ // Ignore region points that aren't within the bounds of the mesh.
+ if ((region.pt.X >= mesh.xmin) && (region.pt.X <= mesh.xmax) &&
+ (region.pt.Y >= mesh.ymin) && (region.pt.Y <= mesh.ymax))
+ {
+ // Start searching from some triangle on the outer boundary.
+ searchtri.triangle = Mesh.dummytri;
+ searchtri.orient = 0;
+ searchtri.SymSelf();
+ // Ensure that the region point is to the left of this boundary
+ // edge; otherwise, locate() will falsely report that the
+ // region point falls within the starting triangle.
+ searchorg = searchtri.Org();
+ searchdest = searchtri.Dest();
+ if (Primitives.CounterClockwise(searchorg.pt, searchdest.pt, region.pt) > 0.0)
+ {
+ // Find a triangle that contains the region point.
+ intersect = mesh.Locate(region.pt, ref searchtri);
+ if ((intersect != LocateResult.Outside) && (!searchtri.IsInfected()))
+ {
+ // Record the triangle for processing after the
+ // holes have been carved.
+ searchtri.Copy(ref regiontris[i]);
+ }
+ }
+ }
+
+ i++;
+ }
+ }
+
+ if (mesh.viri.Count > 0)
+ {
+ // Carve the holes and concavities.
+ Plague();
+ }
+ // The virus pool should be empty now.
+
+ if (numRegions > 0)
+ {
+ if (Behavior.RegionAttrib)
+ {
+ // Make the triangle's attributes larger.
+ double[] attributes = new double[mesh.eextras + 1];
+
+ // Assign every triangle a regional attribute of zero.
+ tri.orient = 0;
+ foreach (var t in mesh.triangles.Values)
+ {
+ Array.Copy(tri.triangle.attributes, attributes, mesh.eextras);
+ tri.triangle = t;
+ tri.triangle.attributes = attributes;
+ }
+ }
+
+ for (int i = 0; i < numRegions; i++)
+ {
+ if (regiontris[i].triangle != Mesh.dummytri)
+ {
+ // Make sure the triangle under consideration still exists.
+ // It may have been eaten by the virus.
+ if (!Otri.IsDead(regiontris[i].triangle))
+ {
+ // Put one triangle in the virus pool.
+ regiontris[i].Infect();
+ mesh.viri.Add(regiontris[i].triangle);
+ // Apply one region's attribute and/or area constraint.
+ RegionPlague(mesh.regions[i].attribute, mesh.regions[i].area);
+ // The virus pool should be empty now.
+ }
+ }
+ }
+
+ if (Behavior.RegionAttrib)
+ {
+ // Note the fact that each triangle has an additional attribute.
+ mesh.eextras++;
+ }
+ }
+
+ // Free up memory.
+ if (((mesh.holes.Count > 0) && !Behavior.NoHoles) || !Behavior.Convex || (numRegions > 0))
+ {
+ mesh.viri.Clear();
+ }
+
+ if (numRegions > 0)
+ {
+ regiontris = null;
+ }
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/BadSubseg.cs b/Triangle.NET/Triangle/Data/BadSubseg.cs
new file mode 100644
index 0000000..03a9347
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/BadSubseg.cs
@@ -0,0 +1,45 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// A queue used to store encroached subsegments.
+ ///
+ ///
+ /// Each subsegment's vertices are stored so that we can check whether a
+ /// subsegment is still the same.
+ ///
+ class BadSubseg
+ {
+ private static int hashSeed = 0;
+ internal int Hash;
+
+ public Osub encsubseg; // An encroached subsegment.
+ public Vertex subsegorg, subsegdest; // Its two vertices.
+
+ public BadSubseg()
+ {
+ this.Hash = hashSeed++;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.Hash;
+ }
+
+ public override string ToString()
+ {
+ return String.Format("B-SID {0}", encsubseg.ss.Hash);
+ }
+ };
+}
diff --git a/Triangle.NET/Triangle/Data/BadTriangle.cs b/Triangle.NET/Triangle/Data/BadTriangle.cs
new file mode 100644
index 0000000..d98bf34
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/BadTriangle.cs
@@ -0,0 +1,42 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// A queue used to store bad triangles.
+ ///
+ ///
+ /// The key is the square of the cosine of the smallest angle of the triangle.
+ /// Each triangle's vertices are stored so that one can check whether a
+ /// triangle is still the same.
+ ///
+ class BadTriangle
+ {
+ public static int OTID = 0;
+ public int ID = 0;
+
+ public Otri poortri; // A skinny or too-large triangle.
+ public double key; // cos^2 of smallest (apical) angle.
+ public Vertex triangorg, triangdest, triangapex; // Its three vertices.
+ public BadTriangle nexttriang; // Pointer to next bad triangle.
+
+ public BadTriangle()
+ {
+ ID = OTID++;
+ }
+ public override string ToString()
+ {
+ return String.Format("B-TID {0}", poortri.triangle.Hash);
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/FlipStacker.cs b/Triangle.NET/Triangle/Data/FlipStacker.cs
new file mode 100644
index 0000000..517ab5c
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/FlipStacker.cs
@@ -0,0 +1,27 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// A stack of triangles flipped during the most recent vertex insertion.
+ ///
+ ///
+ /// The stack is used to undo the vertex insertion if the vertex encroaches
+ /// upon a subsegment.
+ ///
+ class FlipStacker
+ {
+ public Otri flippedtri; // A recently flipped triangle.
+ public FlipStacker prevflip; // Previous flip in the stack.
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/Osub.cs b/Triangle.NET/Triangle/Data/Osub.cs
new file mode 100644
index 0000000..ca2be29
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/Osub.cs
@@ -0,0 +1,255 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/index.html
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// An oriented subsegment.
+ ///
+ ///
+ /// Iincludes a pointer to a subsegment and an orientation. The orientation
+ /// denotes a side of the edge. Hence, there are two possible orientations.
+ /// By convention, the edge is always directed so that the "side" denoted
+ /// is the right side of the edge.
+ ///
+ struct Osub
+ {
+ public Subseg ss;
+ public int ssorient; // Ranges from 0 to 1.
+
+ public override string ToString()
+ {
+ if (ss == null)
+ {
+ return "O-TID [null]";
+ }
+ return String.Format("O-SID {0}", ss.Hash);
+ }
+
+ #region Osub primitives
+
+ ///
+ /// Reverse the orientation of a subsegment. [sym(ab) -> ba]
+ ///
+ /// ssym() toggles the orientation of a subsegment.
+ ///
+ public void Sym(ref Osub o2)
+ {
+ o2.ss = ss;
+ o2.ssorient = 1 - ssorient;
+ }
+
+ ///
+ /// Reverse the orientation of a subsegment. [sym(ab) -> ba]
+ ///
+ public void SymSelf()
+ {
+ ssorient = 1 - ssorient;
+ }
+
+ ///
+ /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*]
+ ///
+ /// spivot() finds the other subsegment (from the same segment)
+ /// that shares the same origin.
+ ///
+ public void Pivot(ref Osub o2)
+ {
+ o2 = ss.subsegs[ssorient];
+ //sdecode(sptr, o2);
+ }
+
+ ///
+ /// Find adjoining subsegment with the same origin. [pivot(ab) -> a*]
+ ///
+ public void PivotSelf()
+ {
+ this = ss.subsegs[ssorient];
+ //sdecode(sptr, osub);
+ }
+
+ ///
+ /// Find next subsegment in sequence. [next(ab) -> b*]
+ ///
+ /// snext() finds the next subsegment (from the same segment) in
+ /// sequence; one whose origin is the input subsegment's destination.
+ ///
+ public void Next(ref Osub o2)
+ {
+ o2 = ss.subsegs[1 - ssorient];
+ //sdecode(sptr, o2);
+ }
+
+ ///
+ /// Find next subsegment in sequence. [next(ab) -> b*]
+ ///
+ public void NextSelf()
+ {
+ this = ss.subsegs[1 - ssorient];
+ //sdecode(sptr, osub);
+ }
+
+ ///
+ /// Get the origin of a subsegment
+ ///
+ public Vertex Org()
+ {
+ return ss.vertices[ssorient];
+ }
+
+ ///
+ /// Get the destination of a subsegment
+ ///
+ public Vertex Dest()
+ {
+ return ss.vertices[1 - ssorient];
+ }
+
+ ///
+ /// Set the origin or destination of a subsegment.
+ ///
+ public void SetOrg(Vertex ptr)
+ {
+ ss.vertices[ssorient] = ptr;
+ }
+
+ ///
+ /// Set destination of a subsegment.
+ ///
+ public void SetDest(Vertex ptr)
+ {
+ ss.vertices[1 - ssorient] = ptr;
+ }
+
+ ///
+ /// Get the origin of the segment that includes the subsegment.
+ ///
+ public Vertex SegOrg()
+ {
+ return ss.vertices[2 + ssorient];
+ }
+
+ ///
+ /// Get the destination of the segment that includes the subsegment.
+ ///
+ public Vertex SegDest()
+ {
+ return ss.vertices[3 - ssorient];
+ }
+
+ ///
+ /// Set the origin of the segment that includes the subsegment.
+ ///
+ public void SetSegOrg(Vertex ptr)
+ {
+ ss.vertices[2 + ssorient] = ptr;
+ }
+
+ ///
+ /// Set the destination of the segment that includes the subsegment.
+ ///
+ public void SetSegDest(Vertex ptr)
+ {
+ ss.vertices[3 - ssorient] = ptr;
+ }
+
+ ///
+ /// Read a boundary marker.
+ ///
+ /// Boundary markers are used to hold user-defined tags for
+ /// setting boundary conditions in finite element solvers.
+ public int Mark()
+ {
+ return ss.boundary;
+ }
+
+ ///
+ /// Set a boundary marker.
+ ///
+ public void SetMark(int value)
+ {
+ ss.boundary = value;
+ }
+
+ ///
+ /// Bond two subsegments together. [bond(abc, ba)]
+ ///
+ public void Bond(ref Osub o2)
+ {
+ ss.subsegs[ssorient] = o2;
+ o2.ss.subsegs[o2.ssorient] = this;
+ }
+
+ ///
+ /// Dissolve a subsegment bond (from one side).
+ ///
+ /// Note that the other subsegment will still think it's
+ /// connected to this subsegment.
+ public void Dissolve()
+ {
+ ss.subsegs[ssorient].ss = Mesh.dummysub;
+ }
+
+ ///
+ /// Copy a subsegment.
+ ///
+ public void Copy(ref Osub o2)
+ {
+ o2.ss = ss;
+ o2.ssorient = ssorient;
+ }
+
+ ///
+ /// Test for equality of subsegments.
+ ///
+ public bool Equal(Osub o2)
+ {
+ return ((ss == o2.ss) && (ssorient == o2.ssorient));
+ }
+
+ ///
+ /// Check a subsegment's deallocation.
+ ///
+ public static bool IsDead(Subseg sub)
+ {
+ return sub.subsegs[0].ss == null;
+ }
+
+ ///
+ /// Set a subsegment's deallocation.
+ ///
+ public static void Kill(Subseg sub)
+ {
+ sub.subsegs[0].ss = null;
+ sub.subsegs[1].ss = null;
+ }
+
+ ///
+ /// Finds a triangle abutting a subsegment.
+ ///
+ public void TriPivot(ref Otri ot)
+ {
+ ot = ss.triangles[ssorient];
+ //decode(ptr, otri)
+ }
+
+ ///
+ /// Dissolve a bond (from the subsegment side).
+ ///
+ public void TriDissolve()
+ {
+ ss.triangles[ssorient].triangle = Mesh.dummytri;
+ }
+
+ #endregion
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/Otri.cs b/Triangle.NET/Triangle/Data/Otri.cs
new file mode 100644
index 0000000..9ebfa18
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/Otri.cs
@@ -0,0 +1,477 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/index.html
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// An oriented triangle.
+ ///
+ ///
+ /// Iincludes a pointer to a triangle and orientation.
+ /// The orientation denotes an edge of the triangle. Hence, there are
+ /// three possible orientations. By convention, each edge always points
+ /// counterclockwise about the corresponding triangle.
+ ///
+ struct Otri
+ {
+ public Triangle triangle;
+ public int orient; // Ranges from 0 to 2.
+
+ public override string ToString()
+ {
+ if (triangle == null)
+ {
+ return "O-TID [null]";
+ }
+ return String.Format("O-TID {0}", triangle.Hash);
+ }
+
+ #region Otri primitives
+
+ // For fast access
+ static readonly int[] plus1Mod3 = { 1, 2, 0 };
+ static readonly int[] minus1Mod3 = { 2, 0, 1 };
+
+ // The following handle manipulation primitives are all described by Guibas
+ // and Stolfi. However, Guibas and Stolfi use an edge-based data structure,
+ // whereas I use a triangle-based data structure.
+
+ ///
+ /// Find the abutting triangle; same edge. [sym(abc) -> ba*]
+ ///
+ ///
+ /// Note that the edge direction is necessarily reversed, because the handle specified
+ /// by an oriented triangle is directed counterclockwise around the triangle.
+ ///
+ public void Sym(ref Otri o2)
+ {
+ //o2 = tri.triangles[orient];
+ // decode(ptr, otri2);
+
+ o2.triangle = triangle.neighbors[orient].triangle;
+ o2.orient = triangle.neighbors[orient].orient;
+ }
+
+ ///
+ /// Find the abutting triangle; same edge. [sym(abc) -> ba*]
+ ///
+ public void SymSelf()
+ {
+ //this = tri.triangles[orient];
+ // decode(ptr, otri);
+
+ int tmp = orient;
+ orient = triangle.neighbors[tmp].orient;
+ triangle = triangle.neighbors[tmp].triangle;
+ }
+ // lnext() finds the next edge (counterclockwise) of a triangle.
+
+ ///
+ /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca]
+ ///
+ public void Lnext(ref Otri o2)
+ {
+ o2.triangle = triangle;
+ o2.orient = plus1Mod3[orient];
+ }
+
+ ///
+ /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca]
+ ///
+ public void LnextSelf()
+ {
+ orient = plus1Mod3[orient];
+ }
+
+ ///
+ /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab]
+ ///
+ public void Lprev(ref Otri o2)
+ {
+ o2.triangle = triangle;
+ o2.orient = minus1Mod3[orient];
+ }
+
+ ///
+ /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab]
+ ///
+ public void LprevSelf()
+ {
+ orient = minus1Mod3[orient];
+ }
+
+ ///
+ /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*]
+ ///
+ /// onext() spins counterclockwise around a vertex; that is, it finds
+ /// the next edge with the same origin in the counterclockwise direction. This
+ /// edge is part of a different triangle.
+ ///
+ public void Onext(ref Otri o2)
+ {
+ //Lprev(ref o2);
+ o2.triangle = triangle;
+ o2.orient = minus1Mod3[orient];
+
+ //o2.SymSelf();
+ int tmp = o2.orient;
+ o2.orient = o2.triangle.neighbors[tmp].orient;
+ o2.triangle = o2.triangle.neighbors[tmp].triangle;
+ }
+
+ ///
+ /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*]
+ ///
+ public void OnextSelf()
+ {
+ //LprevSelf();
+ orient = minus1Mod3[orient];
+
+ //SymSelf();
+ int tmp = orient;
+ orient = triangle.neighbors[tmp].orient;
+ triangle = triangle.neighbors[tmp].triangle;
+ }
+
+ ///
+ /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b]
+ ///
+ /// oprev() spins clockwise around a vertex; that is, it finds the
+ /// next edge with the same origin in the clockwise direction. This edge is
+ /// part of a different triangle.
+ ///
+ public void Oprev(ref Otri o2)
+ {
+ //Sym(ref o2);
+ o2.triangle = triangle.neighbors[orient].triangle;
+ o2.orient = triangle.neighbors[orient].orient;
+
+ //o2.LnextSelf();
+ o2.orient = plus1Mod3[o2.orient];
+ }
+
+ ///
+ /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b]
+ ///
+ public void OprevSelf()
+ {
+ //SymSelf();
+ int tmp = orient;
+ orient = triangle.neighbors[tmp].orient;
+ triangle = triangle.neighbors[tmp].triangle;
+
+ //LnextSelf();
+ orient = plus1Mod3[orient];
+ }
+
+ ///
+ /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba]
+ ///
+ /// dnext() spins counterclockwise around a vertex; that is, it finds
+ /// the next edge with the same destination in the counterclockwise direction.
+ /// This edge is part of a different triangle.
+ ///
+ public void Dnext(ref Otri o2)
+ {
+ //Sym(ref o2);
+ o2.triangle = triangle.neighbors[orient].triangle;
+ o2.orient = triangle.neighbors[orient].orient;
+
+ //o2.LprevSelf();
+ o2.orient = minus1Mod3[o2.orient];
+ }
+
+ ///
+ /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba]
+ ///
+ public void DnextSelf()
+ {
+ //SymSelf();
+ int tmp = orient;
+ orient = triangle.neighbors[tmp].orient;
+ triangle = triangle.neighbors[tmp].triangle;
+
+ //LprevSelf();
+ orient = minus1Mod3[orient];
+ }
+
+ ///
+ /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*]
+ ///
+ /// dprev() spins clockwise around a vertex; that is, it finds the
+ /// next edge with the same destination in the clockwise direction. This edge
+ /// is part of a different triangle.
+ ///
+ public void Dprev(ref Otri o2)
+ {
+ //Lnext(ref o2);
+ o2.triangle = triangle;
+ o2.orient = plus1Mod3[orient];
+
+ //o2.SymSelf();
+ int tmp = o2.orient;
+ o2.orient = o2.triangle.neighbors[tmp].orient;
+ o2.triangle = o2.triangle.neighbors[tmp].triangle;
+ }
+
+ ///
+ /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*]
+ ///
+ public void DprevSelf()
+ {
+ //LnextSelf();
+ orient = plus1Mod3[orient];
+
+ //SymSelf();
+ int tmp = orient;
+ orient = triangle.neighbors[tmp].orient;
+ triangle = triangle.neighbors[tmp].triangle;
+ }
+
+ ///
+ /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*]
+ ///
+ /// rnext() moves one edge counterclockwise about the adjacent
+ /// triangle. (It's best understood by reading Guibas and Stolfi. It
+ /// involves changing triangles twice.)
+ ///
+ public void Rnext(ref Otri o2)
+ {
+ //Sym(ref o2);
+ o2.triangle = triangle.neighbors[orient].triangle;
+ o2.orient = triangle.neighbors[orient].orient;
+
+ //o2.LnextSelf();
+ o2.orient = plus1Mod3[o2.orient];
+
+ //o2.SymSelf();
+ int tmp = o2.orient;
+ o2.orient = o2.triangle.neighbors[tmp].orient;
+ o2.triangle = o2.triangle.neighbors[tmp].triangle;
+ }
+
+ ///
+ /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*]
+ ///
+ public void RnextSelf()
+ {
+ //SymSelf();
+ int tmp = orient;
+ orient = triangle.neighbors[tmp].orient;
+ triangle = triangle.neighbors[tmp].triangle;
+
+ //LnextSelf();
+ orient = plus1Mod3[orient];
+
+ //SymSelf();
+ tmp = orient;
+ orient = triangle.neighbors[tmp].orient;
+ triangle = triangle.neighbors[tmp].triangle;
+ }
+
+ ///
+ /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**]
+ ///
+ /// rprev() moves one edge clockwise about the adjacent triangle.
+ /// (It's best understood by reading Guibas and Stolfi. It involves
+ /// changing triangles twice.)
+ ///
+ public void Rprev(ref Otri o2)
+ {
+ //Sym(ref o2);
+ o2.triangle = triangle.neighbors[orient].triangle;
+ o2.orient = triangle.neighbors[orient].orient;
+
+ //o2.LprevSelf();
+ o2.orient = minus1Mod3[o2.orient];
+
+ //o2.SymSelf();
+ int tmp = o2.orient;
+ o2.orient = o2.triangle.neighbors[tmp].orient;
+ o2.triangle = o2.triangle.neighbors[tmp].triangle;
+ }
+
+ ///
+ /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**]
+ ///
+ public void RprevSelf()
+ {
+ //SymSelf();
+ int tmp = orient;
+ orient = triangle.neighbors[tmp].orient;
+ triangle = triangle.neighbors[tmp].triangle;
+
+ //LprevSelf();
+ orient = minus1Mod3[orient];
+
+ //SymSelf();
+ tmp = orient;
+ orient = triangle.neighbors[tmp].orient;
+ triangle = triangle.neighbors[tmp].triangle;
+ }
+
+ ///
+ /// Origin [org(abc) -> a]
+ ///
+ public Vertex Org()
+ {
+ return triangle.vertices[plus1Mod3[orient]];
+ }
+
+ ///
+ /// Destination [dest(abc) -> b]
+ ///
+ public Vertex Dest()
+ {
+ return triangle.vertices[minus1Mod3[orient]];
+ }
+
+ ///
+ /// Apex [apex(abc) -> c]
+ ///
+ public Vertex Apex()
+ {
+ return triangle.vertices[orient];
+ }
+
+ ///
+ /// Set Origin
+ ///
+ public void SetOrg(Vertex ptr)
+ {
+ triangle.vertices[plus1Mod3[orient]] = ptr;
+ }
+
+ ///
+ /// Set Destination
+ ///
+ public void SetDest(Vertex ptr)
+ {
+ triangle.vertices[minus1Mod3[orient]] = ptr;
+ }
+
+ ///
+ /// Set Apex
+ ///
+ public void SetApex(Vertex ptr)
+ {
+ triangle.vertices[orient] = ptr;
+ }
+
+ ///
+ /// Bond two triangles together at the resepective handles. [bond(abc, bad)]
+ ///
+ public void Bond(ref Otri o2)
+ {
+ triangle.neighbors[orient] = o2;
+ o2.triangle.neighbors[o2.orient] = this;
+ }
+
+ ///
+ /// Dissolve a bond (from one side).
+ ///
+ /// Note that the other triangle will still think it's connected to
+ /// this triangle. Usually, however, the other triangle is being deleted
+ /// entirely, or bonded to another triangle, so it doesn't matter.
+ ///
+ public void Dissolve()
+ {
+ triangle.neighbors[orient].triangle = Mesh.dummytri;
+ }
+
+ ///
+ /// Copy an oriented triangle.
+ ///
+ public void Copy(ref Otri o2)
+ {
+ o2.triangle = triangle;
+ o2.orient = orient;
+ }
+
+ ///
+ /// Test for equality of oriented triangles.
+ ///
+ public bool Equal(Otri o2)
+ {
+ return ((triangle == o2.triangle) && (orient == o2.orient));
+ }
+
+ ///
+ /// Infect a triangle with the virus.
+ ///
+ public void Infect()
+ {
+ triangle.infected = true;
+ }
+
+ ///
+ /// Cure a triangle from the virus.
+ ///
+ public void Uninfect()
+ {
+ triangle.infected = false;
+ }
+
+ ///
+ /// Test a triangle for viral infection.
+ ///
+ public bool IsInfected()
+ {
+ return triangle.infected;
+ }
+
+ ///
+ /// Check a triangle's deallocation.
+ ///
+ public static bool IsDead(Triangle tria)
+ {
+ return tria.neighbors[0].triangle == null;
+ }
+
+ ///
+ /// Set a triangle's deallocation.
+ ///
+ public static void Kill(Triangle tria)
+ {
+ tria.neighbors[0].triangle = null;
+ tria.neighbors[2].triangle = null;
+ }
+
+ ///
+ /// Finds a subsegment abutting a triangle.
+ ///
+ public void SegPivot(ref Osub os)
+ {
+ os = triangle.subsegs[orient];
+ //sdecode(sptr, osub)
+ }
+
+ ///
+ /// Bond a triangle to a subsegment.
+ ///
+ public void SegBond(ref Osub os)
+ {
+ triangle.subsegs[orient] = os;
+ os.ss.triangles[os.ssorient] = this;
+ }
+
+ ///
+ /// Dissolve a bond (from the triangle side).
+ ///
+ public void SegDissolve()
+ {
+ triangle.subsegs[orient].ss = Mesh.dummysub;
+ }
+
+ #endregion
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/Point2.cs b/Triangle.NET/Triangle/Data/Point2.cs
new file mode 100644
index 0000000..afc6735
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/Point2.cs
@@ -0,0 +1,35 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// Represents a 2D point.
+ ///
+ public struct Point2
+ {
+ public double X;
+ public double Y;
+
+ public Point2(double x, double y)
+ {
+ this.X = x;
+ this.Y = y;
+ }
+
+ public bool Equals(Point2 p)
+ {
+ // Return true if the fields match:
+ return (X == p.X) && (Y == p.Y);
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/Region.cs b/Triangle.NET/Triangle/Data/Region.cs
new file mode 100644
index 0000000..053833b
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/Region.cs
@@ -0,0 +1,31 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// TODO: Update summary.
+ ///
+ struct Region
+ {
+ internal Point2 pt;
+ internal double attribute;
+ internal double area;
+
+ public Region(double[] region)
+ {
+ pt = new Point2(region[0], region[1]);
+ attribute = region[2];
+ area = region[3];
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/SplayNode.cs b/Triangle.NET/Triangle/Data/SplayNode.cs
new file mode 100644
index 0000000..b04080f
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/SplayNode.cs
@@ -0,0 +1,37 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// A node in the splay tree.
+ ///
+ ///
+ /// Only used in the sweepline algorithm.
+ ///
+ /// Each node holds an oriented ghost triangle that represents a boundary edge
+ /// of the growing triangulation. When a circle event covers two boundary edges
+ /// with a triangle, so that they are no longer boundary edges, those edges are
+ /// not immediately deleted from the tree; rather, they are lazily deleted when
+ /// they are next encountered. (Since only a random sample of boundary edges are
+ /// kept in the tree, lazy deletion is faster.) 'keydest' is used to verify that
+ /// a triangle is still the same as when it entered the splay tree; if it has
+ /// been rotated (due to a circle event), it no longer represents a boundary
+ /// edge and should be deleted.
+ ///
+ class SplayNode
+ {
+ public Otri keyedge; // Lprev of an edge on the front.
+ public Vertex keydest; // Used to verify that splay node is still live.
+ public SplayNode lchild, rchild; // Children in splay tree.
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/Subseg.cs b/Triangle.NET/Triangle/Data/Subseg.cs
new file mode 100644
index 0000000..93a151d
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/Subseg.cs
@@ -0,0 +1,85 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// The subsegment data structure.
+ ///
+ ///
+ /// Each subsegment contains two pointers to adjoining subsegments, plus
+ /// four pointers to vertices, plus two pointers to adjoining triangles,
+ /// plus one boundary marker, plus one segment number.
+ ///
+ class Subseg
+ {
+ // Start at -1, so dummysub has that ID
+ private static int hashSeed = -1;
+ internal int Hash;
+
+ // The ID is only used for mesh output.
+ public int ID;
+
+ public Osub[] subsegs;
+ public Vertex[] vertices;
+ public Otri[] triangles;
+ public int boundary;
+ public int segment;
+
+ public Subseg()
+ {
+ Hash = hashSeed++;
+
+ // Initialize the two adjoining subsegments to be the omnipresent
+ // subsegment.
+ subsegs = new Osub[2];
+ subsegs[0].ss = Mesh.dummysub;
+ subsegs[1].ss = Mesh.dummysub;
+
+ // Four NULL vertices.
+ vertices = new Vertex[4];
+
+ // Initialize the two adjoining triangles to be "outer space."
+ triangles = new Otri[2];
+ triangles[0].triangle = Mesh.dummytri;
+ triangles[1].triangle = Mesh.dummytri;
+
+ // Set the boundary marker to zero.
+ boundary = 0;
+ }
+
+ ///
+ /// Reset the hash seed.
+ ///
+ /// The new has seed value.
+ /// Reset value will usally 0, if a new triangulation starts,
+ /// or the number of subsegments, if refinement is done.
+ internal static void ResetHashSeed(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentException("A hash seed must be non negative.");
+ }
+ hashSeed = value;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.Hash;
+ }
+
+ public override string ToString()
+ {
+ return String.Format("SID {0}", Hash);
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/SweepEvent.cs b/Triangle.NET/Triangle/Data/SweepEvent.cs
new file mode 100644
index 0000000..17f37ef
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/SweepEvent.cs
@@ -0,0 +1,33 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// A node in a heap used to store events for the sweepline Delaunay algorithm.
+ ///
+ ///
+ /// Only used in the sweepline algorithm.
+ ///
+ /// Nodes do not point directly to their parents or children in the heap. Instead, each
+ /// node knows its position in the heap, and can look up its parent and children in a
+ /// separate array. To distinguish site events from circle events, all circle events are
+ /// given an invalid (smaller than 'xmin') x-coordinate 'xkey'.
+ ///
+ class SweepEvent
+ {
+ public double xkey, ykey; // Coordinates of the event.
+ public Vertex vertexEvent; // Vertex event.
+ public Otri otriEvent; // Circle event.
+ public int heapposition; // Marks this event's position in the heap.
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/Triangle.cs b/Triangle.NET/Triangle/Data/Triangle.cs
new file mode 100644
index 0000000..6d18a05
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/Triangle.cs
@@ -0,0 +1,104 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// The triangle data structure.
+ ///
+ ///
+ /// Each triangle contains three pointers to
+ /// adjoining triangles, plus three pointers to vertices, plus three
+ /// pointers to subsegments (declared below; these pointers are usually
+ /// 'dummysub'). It may or may not also contain user-defined attributes
+ /// and/or a floating-point "area constraint." It may also contain extra
+ /// pointers for nodes, when the user asks for high-order elements.
+ /// Because the size and structure of a 'triangle' is not decided until
+ /// runtime, I haven't simply declared the type 'triangle' as a struct.
+ ///
+ class Triangle
+ {
+ // Start at -1, so dummytri has that ID
+ private static int hashSeed = -1;
+ internal int Hash;
+
+ // The ID is only used for mesh output.
+ internal int ID;
+
+ internal Otri[] neighbors;
+ internal Vertex[] vertices;
+ internal Osub[] subsegs;
+ internal double[] attributes;
+ internal double area;
+ internal bool infected;
+
+ public Triangle(int numAttributes)
+ {
+ this.Hash = hashSeed++;
+ this.ID = this.Hash;
+
+ // Initialize the three adjoining triangles to be "outer space".
+ neighbors = new Otri[3];
+ neighbors[0].triangle = Mesh.dummytri;
+ neighbors[1].triangle = Mesh.dummytri;
+ neighbors[2].triangle = Mesh.dummytri;
+
+ // Three NULL vertices.
+ vertices = new Vertex[3];
+
+ if (Behavior.UseSegments)
+ {
+ // Initialize the three adjoining subsegments to be the
+ // omnipresent subsegment.
+ subsegs = new Osub[3];
+ subsegs[0].ss = Mesh.dummysub;
+ subsegs[1].ss = Mesh.dummysub;
+ subsegs[2].ss = Mesh.dummysub;
+ }
+
+ if (numAttributes > 0)
+ {
+ attributes = new double[numAttributes];
+ }
+
+ if (Behavior.VarArea)
+ {
+ area = -1.0;
+ }
+ }
+
+ ///
+ /// Reset the hash seed.
+ ///
+ /// The new has seed value.
+ /// Reset value will usally 0, if a new triangulation starts,
+ /// or the number of triangles, if refinement is done.
+ internal static void ResetHashSeed(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentException("A hash seed must be non negative.");
+ }
+ hashSeed = value;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.Hash;
+ }
+
+ public override string ToString()
+ {
+ return String.Format("TID {0}", Hash);
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Data/Vertex.cs b/Triangle.NET/Triangle/Data/Vertex.cs
new file mode 100644
index 0000000..c005be8
--- /dev/null
+++ b/Triangle.NET/Triangle/Data/Vertex.cs
@@ -0,0 +1,164 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Data
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+
+ ///
+ /// The vertex data structure.
+ ///
+ ///
+ /// Each vertex is actually an array of doubles. An integer boundary marker,
+ /// and sometimes a to a triangle, is appended after the doubles.
+ ///
+ class Vertex : IComparable, IEquatable
+ {
+ private static int hashSeed = 0;
+ internal int Hash;
+
+ // The ID is only used for mesh output.
+ internal int ID;
+
+ internal Point2 pt;
+ internal int mark;
+ internal VertexType type;
+ internal Otri tri;
+ internal double[] attributes;
+
+ public Vertex()
+ : this(0)
+ { }
+
+ public Vertex(int numAttributes)
+ {
+ this.Hash = hashSeed++;
+
+ pt = default(Point2);
+
+ if (numAttributes > 0)
+ {
+ attributes = new double[numAttributes];
+ }
+ }
+
+ public static bool operator ==(Vertex a, Vertex b)
+ {
+ // If vertex is null return false.
+ if ((object)a == null)
+ {
+ return false;
+ }
+
+ return a.Equals(b);
+ }
+
+ public static bool operator !=(Vertex a, Vertex b)
+ {
+ // If vertex is null return false.
+ if ((object)a == null)
+ {
+ return false;
+ }
+
+ return !a.Equals(b);
+ }
+
+ ///
+ /// Gets the specified coordinate of the vertex.
+ ///
+ /// Coordinate index.
+ /// X coordinate, if index is 0, Y coordinate, if index is 1.
+ public double this[int i]
+ {
+ get
+ {
+ if (i == 0)
+ {
+ return pt.X;
+ }
+
+ if (i == 1)
+ {
+ return pt.Y;
+ }
+
+ throw new ArgumentOutOfRangeException("Index must be 0 or 1.");
+ }
+ }
+
+ ///
+ /// Reset the hash seed.
+ ///
+ /// The new has seed value.
+ /// Reset value will usally 0, if a new triangulation starts,
+ /// or the number of points, if refinement is done.
+ internal static void ResetHashSeed(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentException("A hash seed must be non negative.");
+ }
+ hashSeed = value;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.Hash;
+ }
+
+ public override bool Equals(object obj)
+ {
+ Vertex v = obj as Vertex;
+
+ if (v == null)
+ {
+ return false;
+ }
+
+ return this.Equals(v);
+ }
+
+ public bool Equals(Vertex v)
+ {
+ // If both are null, or both are same instance, return true.
+ if (object.ReferenceEquals(this, v))
+ {
+ return true;
+ }
+
+ // If vertex is null return false.
+ if ((object)v == null)
+ {
+ return false;
+ }
+
+ // Return true if the fields match:
+ return this.pt.Equals(v.pt);
+ }
+
+
+ public override string ToString()
+ {
+ return String.Format("[{0},{1}]", pt.X, pt.Y);
+ }
+
+ public int CompareTo(Vertex other)
+ {
+ if (pt.X == other.pt.X && pt.Y == other.pt.Y)
+ {
+ return 0;
+ }
+
+ return (pt.X < other.pt.X || (pt.X == other.pt.X && pt.Y < other.pt.Y)) ? -1 : 1;
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Enums.cs b/Triangle.NET/Triangle/Enums.cs
new file mode 100644
index 0000000..cef6836
--- /dev/null
+++ b/Triangle.NET/Triangle/Enums.cs
@@ -0,0 +1,94 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet
+{
+ ///
+ /// Mesh generation options.
+ ///
+ public enum Options
+ {
+ ///
+ /// Minimum angle constraint (numeric).
+ ///
+ MinAngle,
+ ///
+ /// Maximum angle constraint (numeric).
+ ///
+ MaxAngle,
+ ///
+ /// Global maximum area constraint (numeric).
+ ///
+ MaxArea,
+ ///
+ /// Maximum number of Steiner points (interger).
+ ///
+ SteinerPoints,
+ ///
+ /// No new vertices on the boundary (interger).
+ ///
+ NoBisect,
+ ///
+ /// Generate conforming Delaunay triangulations (boolean).
+ ///
+ ConformingDelaunay,
+ ///
+ /// Use boundary markers (boolean).
+ ///
+ BoundaryMarkers,
+ ///
+ /// Set default values for quality mesh generation (boolean).
+ ///
+ Quality,
+ ///
+ /// Create segments on the convex hull (boolean).
+ ///
+ Convex
+ };
+
+ ///
+ /// Implemented triangulation algorithms.
+ ///
+ public enum TriangulationAlgorithm
+ {
+ Dwyer,
+ Incremental,
+ SweepLine
+ };
+
+ ///
+ /// Labels that signify the result of point location.
+ ///
+ /// The result of a search indicates that the point falls in the
+ /// interior of a triangle, on an edge, on a vertex, or outside the mesh.
+ ///
+ enum LocateResult { InTriangle, OnEdge, OnVertex, Outside };
+
+ ///
+ /// Labels that signify the result of vertex insertion.
+ ///
+ /// The result indicates that the vertex was inserted with complete
+ /// success, was inserted but encroaches upon a subsegment, was not inserted
+ /// because it lies on a segment, or was not inserted because another vertex
+ /// occupies the same location.
+ ///
+ enum InsertVertexResult { Successful, Encroaching, Violating, Duplicate };
+
+ ///
+ /// Labels that signify the result of direction finding.
+ ///
+ /// The result indicates that a segment connecting the two query
+ /// points falls within the direction triangle, along the left edge of the
+ /// direction triangle, or along the right edge of the direction triangle.
+ ///
+ enum FindDirectionResult { Within, Leftcollinear, Rightcollinear };
+
+ ///
+ /// The type of the mesh vertex.
+ ///
+ enum VertexType { InputVertex, SegmentVertex, FreeVertex, DeadVertex, UndeadVertex };
+}
diff --git a/Triangle.NET/Triangle/IO/DataReader.cs b/Triangle.NET/Triangle/IO/DataReader.cs
new file mode 100644
index 0000000..438603e
--- /dev/null
+++ b/Triangle.NET/Triangle/IO/DataReader.cs
@@ -0,0 +1,359 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.IO
+{
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Linq;
+ using System.Globalization;
+ using TriangleNet.Data;
+ using TriangleNet.Log;
+
+ ///
+ /// TODO: Update summary.
+ ///
+ public static class DataReader
+ {
+ class StackTri
+ {
+ public Otri tri = default(Otri);
+ public StackTri next;
+ }
+
+ #region Library
+
+ ///
+ /// Reconstruct a triangulation from its raw data representation.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Reads an .ele file and reconstructs the original mesh. If the -p switch
+ /// is used, this procedure will also read a .poly file and reconstruct the
+ /// subsegments of the original mesh. If the -a switch is used, this
+ /// procedure will also read an .area file and set a maximum area constraint
+ /// on each triangle.
+ ///
+ /// Vertices that are not corners of triangles, such as nodes on edges of
+ /// subparametric elements, are discarded.
+ ///
+ /// This routine finds the adjacencies between triangles (and subsegments)
+ /// by forming one stack of triangles for each vertex. Each triangle is on
+ /// three different stacks simultaneously. Each triangle's subsegment
+ /// pointers are used to link the items in each stack. This memory-saving
+ /// feature makes the code harder to read. The most important thing to keep
+ /// in mind is that each triangle is removed from a stack precisely when
+ /// the corresponding pointer is adjusted to refer to a subsegment rather
+ /// than the next triangle of the stack.
+ ///
+ public static int Reconstruct(Mesh mesh, MeshData input)
+ {
+ long hullsize = 0;
+
+ Otri tri = default(Otri);
+ Otri triangleleft = default(Otri);
+ Otri checktri = default(Otri);
+ Otri checkleft = default(Otri);
+ Otri checkneighbor = default(Otri);
+ Osub subseg = default(Osub);
+ List[] vertexarray; // Triangle
+ Otri prevlink; // Triangle
+ Otri nexttri; // Triangle
+ Vertex tdest, tapex;
+ Vertex checkdest, checkapex;
+ Vertex shorg;
+ Vertex segmentorg, segmentdest;
+ int[] corner = new int[3];
+ int[] end = new int[2];
+ bool segmentmarkers = false;
+ int boundmarker;
+ int aroundvertex;
+ bool notfound;
+ int i = 0;
+
+ int elements = input.Triangles == null ? 0 : input.Triangles.Length;
+ int attribs = input.TriangleAttributes == null ? 0 : input.TriangleAttributes.Length;
+ int numberofsegments = input.Segments == null ? 0 : input.Segments.Length;
+
+ mesh.inelements = elements;
+ mesh.eextras = attribs;
+
+ // Create the triangles.
+ for (i = 0; i < mesh.inelements; i++)
+ {
+ mesh.MakeTriangle(ref tri);
+ // Mark the triangle as living.
+ //tri.triangle.neighbors[0].triangle = tri.triangle;
+ }
+
+ if (Behavior.Poly)
+ {
+ mesh.insegments = numberofsegments;
+ segmentmarkers = input.SegmentMarkers != null;
+
+ // Create the subsegments.
+ for (i = 0; i < mesh.insegments; i++)
+ {
+ mesh.MakeSubseg(ref subseg);
+ // Mark the subsegment as living.
+ //subseg.ss.subsegs[0].ss = subseg.ss;
+ }
+ }
+
+ // Allocate a temporary array that maps each vertex to some adjacent
+ // triangle. I took care to allocate all the permanent memory for
+ // triangles and subsegments first.
+ vertexarray = new List[mesh.vertices.Count];
+ // Each vertex is initially unrepresented.
+ for (i = 0; i < mesh.vertices.Count; i++)
+ {
+ Otri tmp = default(Otri);
+ tmp.triangle = Mesh.dummytri;
+ vertexarray[i] = new List(3);
+ vertexarray[i].Add(tmp);
+ }
+
+ i = 0;
+ string debug = "";
+ // Read the triangles from the .ele file, and link
+ // together those that share an edge.
+ foreach (var item in mesh.triangles.Values)
+ {
+ tri.triangle = item;
+
+ // Copy the triangle's three corners.
+ for (int j = 0; j < 3; j++)
+ {
+ corner[j] = input.Triangles[i][j];
+ if ((corner[j] < 0) || (corner[j] >= mesh.invertices))
+ {
+ SimpleLogger.Instance.Error("Triangle has an invalid vertex index.", "MeshReader.Reconstruct()");
+ throw new Exception("Triangle has an invalid vertex index.");
+ }
+ }
+
+ // Read the triangle's attributes.
+ for (int j = 0; j < mesh.eextras; j++)
+ {
+ tri.triangle.attributes[j] = input.TriangleAttributes[i][j];
+ }
+
+ // TODO
+ if (Behavior.VarArea)
+ {
+ tri.triangle.area = input.TriangleAreas[i];
+ }
+
+ // Set the triangle's vertices.
+ tri.orient = 0;
+ tri.SetOrg(mesh.vertices[corner[0]]);
+ tri.SetDest(mesh.vertices[corner[1]]);
+ tri.SetApex(mesh.vertices[corner[2]]);
+
+ debug += String.Format("Checking element {0} [{1}, {2}, {3}]\n", i, corner[0], corner[1], corner[2]);
+
+ // Try linking the triangle to others that share these vertices.
+ for (tri.orient = 0; tri.orient < 3; tri.orient++)
+ {
+ // Take the number for the origin of triangleloop.
+ aroundvertex = corner[tri.orient];
+ int index = vertexarray[aroundvertex].Count - 1;
+ // Look for other triangles having this vertex.
+ nexttri = vertexarray[aroundvertex][index];
+ // Link the current triangle to the next one in the stack.
+ //tri.triangle.neighbors[tri.orient] = nexttri;
+ // Push the current triangle onto the stack.
+ vertexarray[aroundvertex].Add(tri);
+
+ checktri = nexttri;
+
+ debug += String.Format(" {0}: aroundvertex = {1}\n", tri.orient, aroundvertex);
+ if (checktri.triangle != Mesh.dummytri)
+ {
+ tdest = tri.Dest();
+ tapex = tri.Apex();
+
+ debug += String.Format(" No dummy: tdest ({0}, {1}), tapex ({2}, {3})\n",
+ tdest[0], tdest[1], tapex[0], tapex[1]);
+ // Look for other triangles that share an edge.
+ do
+ {
+ checkdest = checktri.Dest();
+ checkapex = checktri.Apex();
+
+ debug += String.Format(" checktri.orient {0}\n", checktri.orient);
+
+ debug += String.Format(" checkdest ({0}, {1}), checkapex ({2}, {3})\n",
+ checkdest[0], checkdest[1], checkapex[0], checkapex[1]);
+
+ if (tapex == checkdest)
+ {
+ debug += String.Format(" > tapex == checkdest\n");
+ // The two triangles share an edge; bond them together.
+ tri.Lprev(ref triangleleft);
+ triangleleft.Bond(ref checktri);
+ }
+ if (tdest == checkapex)
+ {
+ debug += String.Format(" > tdest == checkapex\n");
+ // The two triangles share an edge; bond them together.
+ checktri.Lprev(ref checkleft);
+ tri.Bond(ref checkleft);
+ }
+ // Find the next triangle in the stack.
+ index--;
+ nexttri = vertexarray[aroundvertex][index];
+
+ checktri = nexttri;
+ } while (checktri.triangle != Mesh.dummytri);
+ }
+ }
+
+ i++;
+ }
+
+ // Prepare to count the boundary edges.
+ hullsize = 0;
+ if (Behavior.Poly)
+ {
+ // Read the segments from the .poly file, and link them
+ // to their neighboring triangles.
+ boundmarker = 0;
+ i = 0;
+ foreach (var item in mesh.subsegs.Values)
+ {
+ subseg.ss = item;
+
+ end[0] = input.Segments[i][0];
+ end[1] = input.Segments[i][1];
+ if (segmentmarkers)
+ {
+ boundmarker = input.SegmentMarkers[i];
+ }
+
+ for (int j = 0; j < 2; j++)
+ {
+ if ((end[j] < 0) || (end[j] >= mesh.invertices))
+ {
+ SimpleLogger.Instance.Error("Segment has an invalid vertex index.", "MeshReader.Reconstruct()");
+ throw new Exception("Segment has an invalid vertex index.");
+ }
+ }
+
+ debug += String.Format("Checking segment {0} [{1}, {2}]\n", i, end[0], end[1]);
+ // set the subsegment's vertices.
+ subseg.ssorient = 0;
+ segmentorg = mesh.vertices[end[0]];
+ segmentdest = mesh.vertices[end[1]];
+ subseg.SetOrg(segmentorg);
+ subseg.SetDest(segmentdest);
+ subseg.SetSegOrg(segmentorg);
+ subseg.SetSegDest(segmentdest);
+ subseg.ss.boundary = boundmarker;
+ // Try linking the subsegment to triangles that share these vertices.
+ for (subseg.ssorient = 0; subseg.ssorient < 2; subseg.ssorient++)
+ {
+ // Take the number for the destination of subsegloop.
+ aroundvertex = end[1 - subseg.ssorient];
+ debug += String.Format(" {0}: aroundvertex = {1}\n", subseg.ssorient, aroundvertex);
+ int index = vertexarray[aroundvertex].Count - 1;
+ // Look for triangles having this vertex.
+ prevlink = vertexarray[aroundvertex][index];
+ nexttri = vertexarray[aroundvertex][index];
+
+ checktri = nexttri;
+ shorg = subseg.Org();
+ notfound = true;
+ // Look for triangles having this edge. Note that I'm only
+ // comparing each triangle's destination with the subsegment;
+ // each triangle's apex is handled through a different vertex.
+ // Because each triangle appears on three vertices' lists, each
+ // occurrence of a triangle on a list can (and does) represent
+ // an edge. In this way, most edges are represented twice, and
+ // every triangle-subsegment bond is represented once.
+ while (notfound && (checktri.triangle != Mesh.dummytri))
+ {
+ checkdest = checktri.Dest();
+ debug += String.Format(" No dummy: shorg ({0}, {1}), checkdest ({2}, {3})\n",
+ shorg[0], shorg[1], checkdest[0], checkdest[1]);
+
+ if (shorg == checkdest)
+ {
+ debug +=" shorg == checkdest\n";
+ // We have a match. Remove this triangle from the list.
+ //prevlink = vertexarray[aroundvertex][index];
+ vertexarray[aroundvertex].Remove(prevlink);
+ // Bond the subsegment to the triangle.
+ checktri.SegBond(ref subseg);
+ // Check if this is a boundary edge.
+ checktri.Sym(ref checkneighbor);
+ if (checkneighbor.triangle == Mesh.dummytri)
+ {
+ debug +=" checkneighbor.tri == m->dummytri\n";
+ // The next line doesn't insert a subsegment (because there's
+ // already one there), but it sets the boundary markers of
+ // the existing subsegment and its vertices.
+ mesh.InsertSubseg(ref checktri, 1);
+ hullsize++;
+ }
+ notfound = false;
+ }
+ index--;
+ // Find the next triangle in the stack.
+ prevlink = vertexarray[aroundvertex][index];
+ nexttri = vertexarray[aroundvertex][index];
+
+ checktri = nexttri;
+ }
+ }
+
+ i++;
+ }
+ }
+
+ debug += "\nMark the remaining edges\n\n";
+ // Mark the remaining edges as not being attached to any subsegment.
+ // Also, count the (yet uncounted) boundary edges.
+ for (i = 0; i < mesh.vertices.Count; i++)
+ {
+ // Search the stack of triangles adjacent to a vertex.
+ int index = vertexarray[i].Count - 1;
+ nexttri = vertexarray[i][index];
+ checktri = nexttri;
+
+ while (checktri.triangle != Mesh.dummytri)
+ {
+ debug += " checktri.triangle != Mesh.dummytri\n";
+ // Find the next triangle in the stack before this
+ // information gets overwritten.
+ index--;
+ nexttri = vertexarray[i][index];
+ // No adjacent subsegment. (This overwrites the stack info.)
+ checktri.SegDissolve();
+ checktri.Sym(ref checkneighbor);
+ if (checkneighbor.triangle == Mesh.dummytri)
+ {
+ mesh.InsertSubseg(ref checktri, 1);
+ hullsize++;
+ debug += "checkneighbor.triangle == Mesh.dummytri (hullsize = " + hullsize + ")\n";
+ }
+
+ checktri = nexttri;
+ }
+ }
+
+ debug += "\nmesh.subsegs.Count = " + mesh.subsegs.Count;
+
+ return (int)hullsize;
+ }
+
+ #endregion
+ }
+}
diff --git a/Triangle.NET/Triangle/IO/DataWriter.cs b/Triangle.NET/Triangle/IO/DataWriter.cs
new file mode 100644
index 0000000..07add23
--- /dev/null
+++ b/Triangle.NET/Triangle/IO/DataWriter.cs
@@ -0,0 +1,382 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.IO
+{
+ using System;
+ using System.IO;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using TriangleNet.Data;
+
+ ///
+ /// Generates a mesh representaion using arrays.
+ ///
+ public static class DataWriter
+ {
+ static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
+
+ static int verticesCount;
+ static int elementsCount;
+
+ #region Library
+
+ ///
+ /// Number the vertices and write them to raw output data.
+ ///
+ ///
+ ///
+ public static void WriteNodes(Mesh mesh, MeshData data)
+ {
+ Vertex vertex;
+ int outvertices = mesh.vertices.Count;
+
+ if (Behavior.Jettison)
+ {
+ outvertices = mesh.vertices.Count - mesh.undeads;
+ }
+
+ verticesCount = outvertices;
+
+ // Allocate memory for output vertices if necessary.
+ data.Points = new double[outvertices][];
+
+ // Allocate memory for output vertex attributes if necessary.
+ if (mesh.nextras > 0)
+ {
+ data.PointAttributes = new double[outvertices][];
+ }
+ // Allocate memory for output vertex markers if necessary.
+ if (Behavior.UseBoundaryMarkers)
+ {
+ data.PointMarkers = new int[outvertices];
+ }
+
+ int i = 0;
+ foreach (var item in mesh.vertices.Values)
+ {
+ vertex = item;
+
+ if (!Behavior.Jettison || vertex.type != VertexType.UndeadVertex)
+ {
+ // X and y coordinates.
+ data.Points[i] = new double[] { vertex.pt.X, vertex.pt.Y };
+
+ // Vertex attributes.
+ if (data.PointAttributes != null)
+ {
+ data.PointAttributes[i] = vertex.attributes;
+ }
+
+ if (Behavior.UseBoundaryMarkers)
+ {
+ // Save the boundary marker.
+ data.PointMarkers[i] = vertex.mark;
+ }
+
+ // Assign array index to vertex ID for later use.
+ vertex.ID = i++;
+ }
+ }
+ }
+
+ ///
+ /// Write the triangles to raw output data.
+ ///
+ ///
+ ///
+ public static void WriteElements(Mesh mesh, MeshData data)
+ {
+ Otri tri = default(Otri);
+ Vertex p1, p2, p3;
+
+ elementsCount = mesh.triangles.Count;
+
+ // Allocate memory for output triangles if necessary.
+ data.Triangles = new int[elementsCount][];
+
+ // Allocate memory for output triangle attributes if necessary.
+ if (mesh.eextras > 0)
+ {
+ data.TriangleAttributes = new double[mesh.triangles.Count][];
+ }
+
+ tri.orient = 0;
+
+ int i = 0;
+ foreach (var item in mesh.triangles.Values)
+ {
+ tri.triangle = item;
+
+ p1 = tri.Org();
+ p2 = tri.Dest();
+ p3 = tri.Apex();
+
+ // Triangle order is always 1 (no higher order elements supported)
+ data.Triangles[i] = new int[] { p1.ID, p2.ID, p3.ID };
+
+ if (data.TriangleAttributes != null)
+ {
+ data.TriangleAttributes[i] = tri.triangle.attributes;
+ }
+
+ // Update ID for later use
+ item.ID = i++;
+ }
+ }
+
+ ///
+ /// Write the segments and holes to raw output data.
+ ///
+ ///
+ ///
+ public static void WritePoly(Mesh mesh, MeshData data)
+ {
+ Osub subseg = default(Osub);
+ Vertex pt1, pt2;
+ int n = mesh.subsegs.Count;
+
+ // Allocate memory for output segments if necessary.
+ data.Segments = new int[n][];
+
+ // Allocate memory for output segment markers if necessary.
+ if (Behavior.UseBoundaryMarkers)
+ {
+ data.SegmentMarkers = new int[n];
+ }
+
+ subseg.ssorient = 0;
+
+ int i = 0;
+ foreach (var item in mesh.subsegs.Values)
+ {
+ subseg.ss = item;
+
+ pt1 = subseg.Org();
+ pt2 = subseg.Dest();
+
+ // Copy indices of the segment's two endpoints.
+ data.Segments[i] = new int[] { pt1.ID, pt2.ID };
+
+ // Copy the boundary marker.
+ if (Behavior.UseBoundaryMarkers)
+ {
+ data.SegmentMarkers[i] = subseg.ss.boundary;
+ }
+
+ i++;
+ }
+ }
+
+ ///
+ /// Write the edges to raw output data.
+ ///
+ ///
+ ///
+ public static void WriteEdges(Mesh mesh, MeshData data)
+ {
+ Otri tri = default(Otri), trisym = default(Otri);
+ Osub checkmark = default(Osub);
+ Vertex p1, p2;
+
+ // Allocate memory for edges if necessary.
+ data.Edges = new int[mesh.edges][];
+
+ // Allocate memory for edge markers if necessary.
+ if (Behavior.UseBoundaryMarkers)
+ {
+ data.EdgeMarkers = new int[mesh.edges];
+ }
+
+ int index = 0;
+ // To loop over the set of edges, loop over all triangles, and look at
+ // the three edges of each triangle. If there isn't another triangle
+ // adjacent to the edge, operate on the edge. If there is another
+ // adjacent triangle, operate on the edge only if the current triangle
+ // has a smaller pointer than its neighbor. This way, each edge is
+ // considered only once.
+ foreach (var item in mesh.triangles.Values)
+ {
+ tri.triangle = item;
+
+ for (tri.orient = 0; tri.orient < 3; tri.orient++)
+ {
+ tri.Sym(ref trisym);
+ if ((tri.triangle.ID < trisym.triangle.ID) || (trisym.triangle == Mesh.dummytri))
+ {
+ p1 = tri.Org();
+ p2 = tri.Dest();
+
+ data.Edges[index] = new int[] { p1.ID, p2.ID };
+ if (Behavior.UseBoundaryMarkers)
+ {
+ // Edge number, indices of two endpoints, and a boundary marker.
+ // If there's no subsegment, the boundary marker is zero.
+ if (Behavior.UseSegments)
+ {
+ tri.SegPivot(ref checkmark);
+ if (checkmark.ss == Mesh.dummysub)
+ {
+ data.EdgeMarkers[index] = 0;
+ }
+ else
+ {
+ data.EdgeMarkers[index] = checkmark.ss.boundary;
+ }
+ }
+ else
+ {
+ data.EdgeMarkers[index] = (trisym.triangle == Mesh.dummytri ? 1 : 0);
+ }
+ }
+ index++;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Write the triangle neighbors to raw output data.
+ ///
+ ///
+ ///
+ /// WARNING: Be sure WriteElements has been called before,
+ /// so the elements are numbered right!
+ public static void WriteNeighbors(Mesh mesh, MeshData data)
+ {
+ Otri tri = default(Otri), trisym = default(Otri);
+
+ // Allocate memory for neighbors if necessary.
+ data.Neighbors = new int[mesh.triangles.Count][];
+
+ Mesh.dummytri.ID = -1;
+
+ int i = 0;
+ foreach (var item in mesh.triangles.Values)
+ {
+ data.Neighbors[i] = new int[3];
+
+ tri.triangle = item;
+
+ tri.orient = 1;
+ tri.Sym(ref trisym);
+ data.Neighbors[i][0] = trisym.triangle.ID;
+
+ tri.orient = 2;
+ tri.Sym(ref trisym);
+ data.Neighbors[i][1] = trisym.triangle.ID;
+
+ tri.orient = 0;
+ tri.Sym(ref trisym);
+ data.Neighbors[i][2] = trisym.triangle.ID;
+
+ i++;
+ }
+ }
+
+ ///
+ /// Gets the Voronoi diagram as raw output data.
+ ///
+ ///
+ ///
+ ///
+ /// The Voronoi diagram is the geometric dual of the Delaunay triangulation.
+ /// Hence, the Voronoi vertices are listed by traversing the Delaunay
+ /// triangles, and the Voronoi edges are listed by traversing the Delaunay
+ /// edges.
+ ///
+ /// WARNING: In order to assign numbers to the Voronoi vertices, this
+ /// procedure messes up the subsegments or the extra nodes of every
+ /// element. Hence, you should call this procedure last.
+ public static VoronoiData WriteVoronoi(Mesh m)
+ {
+ VoronoiData data = new VoronoiData();
+
+ Otri tri = default(Otri), trisym = default(Otri);
+ Vertex torg, tdest, tapex;
+ Point2 circumcenter;
+ double xi = 0, eta = 0;
+ int p1, p2;
+
+ // Allocate memory for Voronoi vertices.
+ data.PointList = new double[m.triangles.Count][];
+
+ int index = 0;
+
+ tri.orient = 0;
+
+ int i = 0;
+ foreach (var item in m.triangles.Values)
+ {
+ tri.triangle = item;
+ torg = tri.Org();
+ tdest = tri.Dest();
+ tapex = tri.Apex();
+ circumcenter = Primitives.FindCircumcenter(torg.pt, tdest.pt, tapex.pt, ref xi, ref eta, false);
+
+ // X and y coordinates.
+ data.PointList[i] = new double[] { circumcenter.X, circumcenter.Y };
+
+ // Update element id
+ tri.triangle.ID = i++;
+ }
+
+ // Allocate memory for output Voronoi edges.
+ data.EdgeList = new int[m.edges][];
+
+ // Allocate memory for output Voronoi norms.
+ data.NormList = new double[m.edges][];
+
+ index = 0;
+ // To loop over the set of edges, loop over all triangles, and look at
+ // the three edges of each triangle. If there isn't another triangle
+ // adjacent to the edge, operate on the edge. If there is another
+ // adjacent triangle, operate on the edge only if the current triangle
+ // has a smaller pointer than its neighbor. This way, each edge is
+ // considered only once.
+ foreach (var item in m.triangles.Values)
+ {
+ tri.triangle = item;
+
+ for (tri.orient = 0; tri.orient < 3; tri.orient++)
+ {
+ tri.Sym(ref trisym);
+ if ((tri.triangle.ID < trisym.triangle.ID) || (trisym.triangle == Mesh.dummytri))
+ {
+ // Find the number of this triangle (and Voronoi vertex).
+ p1 = tri.triangle.ID;
+
+ if (trisym.triangle == Mesh.dummytri)
+ {
+ torg = tri.Org();
+ tdest = tri.Dest();
+
+ // Copy an infinite ray. Index of one endpoint, and -1.
+ data.EdgeList[index] = new int[] { p1, -1};
+ data.NormList[index] = new double[] { tdest[1] - torg[1], torg[0] - tdest[0] };
+ }
+ else
+ {
+ // Find the number of the adjacent triangle (and Voronoi vertex).
+ p2 = trisym.triangle.ID;
+ // Finite edge. Write indices of two endpoints.
+
+ data.EdgeList[index] = new int[] { p1, p2 };
+ data.NormList[index] = new double[] { 0, 0 };
+ }
+
+ index++;
+ }
+ }
+ }
+
+ return data;
+ }
+
+ #endregion
+ }
+}
diff --git a/Triangle.NET/Triangle/IO/FileReader.cs b/Triangle.NET/Triangle/IO/FileReader.cs
new file mode 100644
index 0000000..676846a
--- /dev/null
+++ b/Triangle.NET/Triangle/IO/FileReader.cs
@@ -0,0 +1,618 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.IO
+{
+ using System;
+ using System.IO;
+ using System.Globalization;
+ using TriangleNet.Data;
+ using TriangleNet.Log;
+
+ ///
+ /// Helper for reading Triangle files.
+ ///
+ public static class FileReader
+ {
+ static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
+ static int startIndex = 0;
+
+ ///
+ /// Read the input data from a file, which may be a .node or .poly file.
+ ///
+ /// The file to read.
+ /// Will NOT read associated files by default.
+ public static MeshData ReadFile(string filename)
+ {
+ return ReadFile(filename, false);
+ }
+
+ ///
+ /// Read the input data from a file, which may be a .node or .poly file.
+ ///
+ /// The file to read.
+ /// Read associated files (ele, area, neigh).
+ public static MeshData ReadFile(string filename, bool readsupp)
+ {
+ string ext = Path.GetExtension(filename);
+
+ if (ext == ".node")
+ {
+ return ReadNodeFile(filename, readsupp);
+ }
+ else if (ext == ".poly")
+ {
+ return ReadPolyFile(filename, readsupp, readsupp);
+ }
+
+ throw new NotSupportedException("File format '" + ext + "' not supported.");
+ }
+
+ static bool TryReadLine(StreamReader reader, out string[] token)
+ {
+ token = null;
+
+ if (reader.EndOfStream)
+ {
+ return false;
+ }
+
+ string line = reader.ReadLine().Trim();
+
+ while (String.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
+ {
+ if (reader.EndOfStream)
+ {
+ return false;
+ }
+
+ line = reader.ReadLine().Trim();
+ }
+
+ token = line.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
+
+ return true;
+ }
+
+ static void ReadVertex(MeshData data, int index, string[] line)
+ {
+ int n = data.PointAttributes == null ? 0 : data.PointAttributes.Length;
+
+ data.Points[index] = new double[] {
+ double.Parse(line[1], nfi),
+ double.Parse(line[2], nfi) };
+
+ // Read the vertex attributes.
+ for (int j = 0; j < n; j++)
+ {
+ data.PointAttributes[index] = new double[n];
+
+ if (line.Length > 3 + j)
+ {
+ data.PointAttributes[index][j] = double.Parse(line[3 + j]);
+ }
+ }
+
+ if (data.PointMarkers != null)
+ {
+ // Read a vertex marker.
+ if (line.Length > 3 + n)
+ {
+ data.PointMarkers[index] = int.Parse(line[3 + n]);
+ }
+ }
+ }
+
+ ///
+ /// Read the vertices from a file, which may be a .node or .poly file.
+ ///
+ ///
+ /// Will NOT read associated .ele by default.
+ public static MeshData ReadNodeFile(string nodefilename)
+ {
+ return ReadNodeFile(nodefilename, false);
+ }
+
+ ///
+ /// Read the vertices from a file, which may be a .node or .poly file.
+ ///
+ ///
+ ///
+ public static MeshData ReadNodeFile(string nodefilename, bool readElements)
+ {
+ MeshData data = new MeshData();
+
+ startIndex = 0;
+
+ string[] line;
+ int invertices = 0, attributes = 0, nodemarkers = 0;
+
+ using (StreamReader reader = new StreamReader(nodefilename))
+ {
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file.");
+ }
+
+ // Read number of vertices, number of dimensions, number of vertex
+ // attributes, and number of boundary markers.
+ invertices = int.Parse(line[0]);
+
+ if (invertices < 3)
+ {
+ throw new Exception("Input must have at least three input vertices.");
+ }
+
+ if (line.Length > 1)
+ {
+ if (int.Parse(line[1]) != 2)
+ {
+ throw new Exception("Triangle only works with two-dimensional meshes.");
+ }
+ }
+
+ if (line.Length > 2)
+ {
+ attributes = int.Parse(line[2]);
+ }
+
+ if (line.Length > 3)
+ {
+ nodemarkers = int.Parse(line[3]);
+ }
+
+ // Read the vertices.
+ if (invertices > 0)
+ {
+ data.Points = new double[invertices][];
+
+ if (attributes > 0)
+ {
+ data.PointAttributes = new double[invertices][];
+ }
+
+ if (nodemarkers > 0)
+ {
+ data.PointMarkers = new int[invertices];
+ }
+
+ for (int i = 0; i < invertices; i++)
+ {
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (vertices).");
+ }
+
+ if (line.Length < 3)
+ {
+ throw new Exception("Invalid vertex.");
+ }
+
+ if (i == 0)
+ {
+ startIndex = int.Parse(line[0], nfi);
+ }
+
+ ReadVertex(data, i, line);
+ }
+ }
+ }
+
+ if (readElements)
+ {
+ // Read area file
+ string elefile = Path.ChangeExtension(nodefilename, ".ele");
+ if (File.Exists(elefile))
+ {
+ ReadEleFile(elefile, data, true);
+ }
+ }
+
+ return data;
+ }
+
+ ///
+ /// Read the vertices and segments from a .poly file.
+ ///
+ ///
+ /// Will NOT read associated .ele by default.
+ public static MeshData ReadPolyFile(string polyfilename)
+ {
+ return ReadPolyFile(polyfilename, false, false);
+ }
+
+ ///
+ /// Read the vertices and segments from a .poly file.
+ ///
+ ///
+ /// If true, look for an associated .ele file.
+ /// Will NOT read associated .area by default.
+ public static MeshData ReadPolyFile(string polyfilename, bool readElements)
+ {
+ return ReadPolyFile(polyfilename, readElements, false);
+ }
+
+ ///
+ /// Read the vertices and segments from a .poly file.
+ ///
+ ///
+ /// If true, look for an associated .ele file.
+ /// If true, look for an associated .area file.
+ public static MeshData ReadPolyFile(string polyfilename, bool readElements, bool readArea)
+ {
+ // Read poly file
+ MeshData data;
+
+ startIndex = 0;
+
+ string[] line;
+ int invertices = 0, attributes = 0, nodemarkers = 0;
+
+ using (StreamReader reader = new StreamReader(polyfilename))
+ {
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file.");
+ }
+
+ // Read number of vertices, number of dimensions, number of vertex
+ // attributes, and number of boundary markers.
+ invertices = int.Parse(line[0]);
+
+ if (line.Length > 1)
+ {
+ if (int.Parse(line[1]) != 2)
+ {
+ throw new Exception("Triangle only works with two-dimensional meshes.");
+ }
+ }
+
+ if (line.Length > 2)
+ {
+ attributes = int.Parse(line[2]);
+ }
+
+ if (line.Length > 3)
+ {
+ nodemarkers = int.Parse(line[3]);
+ }
+
+ // Read the vertices.
+ if (invertices > 0)
+ {
+ data = new MeshData();
+
+ data.Points = new double[invertices][];
+
+ if (attributes > 0)
+ {
+ data.PointAttributes = new double[invertices][];
+ }
+
+ if (nodemarkers > 0)
+ {
+ data.PointMarkers = new int[invertices];
+ }
+
+ for (int i = 0; i < invertices; i++)
+ {
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (vertices).");
+ }
+
+ if (line.Length < 3)
+ {
+ throw new Exception("Invalid vertex.");
+ }
+
+ if (i == 0)
+ {
+ // Set the start index!
+ startIndex = int.Parse(line[0], nfi);
+ }
+
+ ReadVertex(data, i, line);
+ }
+ }
+ else
+ {
+ // If the .poly file claims there are zero vertices, that means that
+ // the vertices should be read from a separate .node file.
+ string nodefile = Path.ChangeExtension(polyfilename, ".node");
+ data = ReadNodeFile(nodefile);
+ invertices = data.Points.Length;
+ }
+
+ if (data.Points == null)
+ {
+ throw new Exception("No nodes available.");
+ }
+
+ // Read the segments from a .poly file.
+
+ // Read number of segments and number of boundary markers.
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (segments).");
+ }
+
+ int insegments = int.Parse(line[0]);
+
+ int segmentmarkers = 0;
+ if (line.Length > 1)
+ {
+ segmentmarkers = int.Parse(line[1]);
+ }
+
+ if (insegments > 0)
+ {
+ data.Segments = new int[insegments][];
+ }
+
+ if (segmentmarkers > 0)
+ {
+ data.SegmentMarkers = new int[insegments];
+ }
+
+ int end1, end2;
+ // Read and insert the segments.
+ for (int i = 0; i < insegments; i++)
+ {
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (segments).");
+ }
+
+ if (line.Length < 3)
+ {
+ throw new Exception("Segment has no endpoints.");
+ }
+
+ // TODO: startIndex ok?
+ end1 = int.Parse(line[1]) - startIndex;
+ end2 = int.Parse(line[2]) - startIndex;
+
+ if (segmentmarkers > 0)
+ {
+ if (line.Length > 3)
+ {
+ data.SegmentMarkers[i] = int.Parse(line[3]);
+ }
+ else
+ {
+ data.SegmentMarkers[i] = 0;
+ }
+ }
+
+ if ((end1 < 0) || (end1 >= invertices))
+ {
+ if (Behavior.Verbose)
+ {
+ SimpleLogger.Instance.Warning("Invalid first endpoint of segment.",
+ "MeshReader.ReadPolyfile()");
+ }
+ }
+ else if ((end2 < 0) || (end2 >= invertices))
+ {
+ if (Behavior.Verbose)
+ {
+ SimpleLogger.Instance.Warning("Invalid second endpoint of segment.",
+ "MeshReader.ReadPolyfile()");
+ }
+ }
+ else
+ {
+ data.Segments[i] = new int[] { end1, end2 };
+ }
+ }
+
+ // Read holes from a .poly file.
+
+ // Read the holes.
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (holes).");
+ }
+
+ int holes = int.Parse(line[0]);
+ if (holes > 0)
+ {
+ data.Holes = new double[holes][];
+
+ for (int i = 0; i < holes; i++)
+ {
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (holes).");
+ }
+
+ if (line.Length < 3)
+ {
+ throw new Exception("Invalid hole.");
+ }
+
+ data.Holes[i] = new double[] {
+ double.Parse(line[1], nfi),
+ double.Parse(line[2], nfi) };
+ }
+ }
+
+ // Read area constraints (optional).
+ if (TryReadLine(reader, out line))
+ {
+ int regions = int.Parse(line[0]);
+
+ if (regions > 0)
+ {
+ data.Regions = new double[regions][];
+
+ for (int i = 0; i < regions; i++)
+ {
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (region).");
+ }
+
+ if (line.Length < 5)
+ {
+ throw new Exception("Invalid region.");
+ }
+
+ data.Regions[i] = new double[] {
+ // Region x and y
+ double.Parse(line[1]),
+ double.Parse(line[2]),
+ // Region attribute
+ double.Parse(line[3]),
+ // Region area constraint
+ double.Parse(line[4]) };
+ }
+ }
+ }
+ }
+
+ // Read ele file
+ if (readElements)
+ {
+ string elefile = Path.ChangeExtension(polyfilename, ".ele");
+ if (File.Exists(elefile))
+ {
+ ReadEleFile(elefile, data, readArea);
+ }
+ }
+
+ return data;
+ }
+
+ ///
+ /// Read the elements from an .ele file.
+ ///
+ ///
+ ///
+ ///
+ private static void ReadEleFile(string elefilename, MeshData data, bool readArea)
+ {
+ int intriangles = 0, attributes = 0;
+
+ using (StreamReader reader = new StreamReader(elefilename))
+ {
+ // Read number of elements and number of attributes.
+ string[] line;
+
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (elements).");
+ }
+
+ intriangles = int.Parse(line[0]);
+
+ // We irgnore index 1 (number of nodes per triangle)
+ attributes = 0;
+ if (line.Length > 2)
+ {
+ attributes = int.Parse(line[2]);
+ }
+
+ data.Triangles = new int[intriangles][];
+
+ if (attributes > 0)
+ {
+ data.TriangleAttributes = new double[intriangles][];
+ }
+
+ // Read triangles.
+ for (int i = 0; i < intriangles; i++)
+ {
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (elements).");
+ }
+
+ if (line.Length < 4)
+ {
+ throw new Exception("Triangle has no nodes.");
+ }
+
+ // TODO: startIndex ok?
+ data.Triangles[i] = new int[] {
+ int.Parse(line[1]) - startIndex,
+ int.Parse(line[2]) - startIndex,
+ int.Parse(line[3]) - startIndex };
+
+ // Read triangle attributes
+ if (attributes > 0)
+ {
+ for (int j = 0; j < attributes; j++)
+ {
+ data.TriangleAttributes[i] = new double[attributes];
+
+ if (line.Length > 4 + j)
+ {
+ data.TriangleAttributes[i][j] = double.Parse(line[4 + j]);
+ }
+ }
+ }
+ }
+ }
+
+ // Read area file
+ if (readArea)
+ {
+ string areafile = Path.ChangeExtension(elefilename, ".area");
+ if (File.Exists(areafile))
+ {
+ ReadAreaFile(areafile, intriangles, data);
+ }
+ }
+ }
+
+ ///
+ /// Read the area constraints from an .area file.
+ ///
+ ///
+ ///
+ ///
+ private static void ReadAreaFile(string areafilename, int intriangles, MeshData data)
+ {
+ using (StreamReader reader = new StreamReader(areafilename))
+ {
+ string[] line;
+
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (area).");
+ }
+
+ if (int.Parse(line[0]) != intriangles)
+ {
+ SimpleLogger.Instance.Warning("Number of area constraints doesn't match number of triangles.",
+ "ReadAreaFile()");
+ return;
+ }
+
+ data.TriangleAreas = new double[intriangles];
+
+ // Read area constraints.
+ for (int i = 0; i < intriangles; i++)
+ {
+ if (!TryReadLine(reader, out line))
+ {
+ throw new Exception("Can't read input file (area).");
+ }
+
+ if (line.Length != 2)
+ {
+ throw new Exception("Triangle has no nodes.");
+ }
+
+ data.TriangleAreas[i] = double.Parse(line[1], nfi);
+ }
+ }
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/IO/FileWriter.cs b/Triangle.NET/Triangle/IO/FileWriter.cs
new file mode 100644
index 0000000..2d0a527
--- /dev/null
+++ b/Triangle.NET/Triangle/IO/FileWriter.cs
@@ -0,0 +1,505 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.IO
+{
+ using System;
+ using System.IO;
+ using System.Globalization;
+ using TriangleNet.Data;
+
+ ///
+ /// TODO: Update summary.
+ ///
+ public static class FileWriter
+ {
+ static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
+
+ // TODO: Intelligent file name guessing
+
+ #region File IO
+
+ ///
+ /// Number the vertices and write them to a .node file.
+ ///
+ ///
+ ///
+ public static void WriteNodes(Mesh mesh, string filename)
+ {
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ FileWriter.WriteNodes(mesh, writer);
+ }
+ }
+
+ ///
+ /// Number the vertices and write them to a .node file.
+ ///
+ ///
+ ///
+ private static void WriteNodes(Mesh mesh, StreamWriter writer)
+ {
+ Vertex vertex;
+ long outvertices = mesh.vertices.Count;
+
+ if (Behavior.Jettison)
+ {
+ outvertices = mesh.vertices.Count - mesh.undeads;
+ }
+
+ int index = 0;
+
+ if (writer != null)
+ {
+ // Number of vertices, number of dimensions, number of vertex attributes,
+ // and number of boundary markers (zero or one).
+ writer.WriteLine("{0} {1} {2} {3}", outvertices, mesh.mesh_dim, mesh.nextras,
+ Behavior.UseBoundaryMarkers ? "1" : "0");
+
+ foreach (var item in mesh.vertices.Values)
+ {
+ vertex = item;
+
+ if (!Behavior.Jettison || vertex.type != VertexType.UndeadVertex)
+ {
+ // Vertex number, x and y coordinates.
+ writer.Write("{0} {1} {2}", index, vertex.pt.X.ToString(nfi), vertex.pt.Y.ToString(nfi));
+
+ // Write attributes.
+ for (int j = 0; j < mesh.nextras; j++)
+ {
+ writer.Write(" {0}", vertex.attributes[j].ToString(nfi));
+ }
+
+ if (Behavior.UseBoundaryMarkers)
+ {
+ // Write the boundary marker.
+ writer.Write(" {0}", vertex.mark);
+ }
+
+ writer.WriteLine();
+
+ // Assign array index to vertex ID for later use.
+ vertex.ID = index++;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Write the triangles to an .ele file.
+ ///
+ ///
+ ///
+ public static void WriteElements(Mesh mesh, string filename)
+ {
+ Otri tri = default(Otri);
+ Vertex p1, p2, p3;
+
+ int j = 0;
+
+ tri.orient = 0;
+
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ // Number of triangles, vertices per triangle, attributes per triangle.
+ writer.WriteLine("{0} 3 {1}", mesh.triangles.Count, mesh.eextras);
+
+ foreach (var item in mesh.triangles.Values)
+ {
+ tri.triangle = item;
+
+ p1 = tri.Org();
+ p2 = tri.Dest();
+ p3 = tri.Apex();
+
+ // Triangle number, indices for three vertices.
+ writer.Write("{0} {1} {2} {3}", j, p1.ID, p2.ID, p3.ID);
+
+ for (int i = 0; i < mesh.eextras; i++)
+ {
+ writer.Write(" {0}", tri.triangle.attributes[i].ToString(nfi));
+ }
+
+ writer.WriteLine();
+
+ // Number elements
+ item.ID = j++;
+ }
+ }
+ }
+
+ ///
+ /// Write the segments and holes to a .poly file.
+ ///
+ ///
+ ///
+ public static void WritePoly(Mesh mesh, string filename)
+ {
+ FileWriter.WritePoly(mesh, filename, true);
+ }
+
+ ///
+ /// Write the segments and holes to a .poly file.
+ ///
+ /// Data source.
+ /// File name.
+ /// Write nodes into this file.
+ /// If the nodes should not be written into this file,
+ /// make sure a .node file was written before, so that the nodes
+ /// are numbered right.
+ public static void WritePoly(Mesh mesh, string filename, bool writeNodes)
+ {
+ Osub subseg = default(Osub);
+ Vertex pt1, pt2;
+
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ if (writeNodes)
+ {
+ // Write nodes to this file.
+ FileWriter.WriteNodes(mesh, writer);
+ }
+ else
+ {
+ // The zero indicates that the vertices are in a separate .node file.
+ // Followed by number of dimensions, number of vertex attributes,
+ // and number of boundary markers (zero or one).
+ writer.WriteLine("0 {0} {1} {2}", mesh.mesh_dim, mesh.nextras,
+ Behavior.UseBoundaryMarkers ? "1" : "0");
+ }
+
+ // Number of segments, number of boundary markers (zero or one).
+ writer.WriteLine("{0} {1}", mesh.subsegs.Count,
+ Behavior.UseBoundaryMarkers ? "1" : "0");
+
+ subseg.ssorient = 0;
+
+ int j = 0;
+ foreach (var item in mesh.subsegs.Values)
+ {
+ subseg.ss = item;
+
+ pt1 = subseg.Org();
+ pt2 = subseg.Dest();
+
+ // Segment number, indices of its two endpoints, and possibly a marker.
+ if (Behavior.UseBoundaryMarkers)
+ {
+ writer.WriteLine("{0} {1} {2} {3}", j, pt1.ID, pt2.ID, subseg.ss.boundary);
+ }
+ else
+ {
+ writer.WriteLine("{0} {1} {2}", j, pt1.ID, pt2.ID);
+ }
+
+ j++;
+ }
+
+ // Holes
+ writer.WriteLine("{0}", mesh.holes.Count);
+ foreach (var hole in mesh.holes)
+ {
+ writer.WriteLine("{0} {1}", hole.X.ToString(nfi), hole.Y.ToString(nfi));
+ }
+
+ // Regions
+ if (mesh.regions.Count > 0)
+ {
+ j = 0;
+ writer.WriteLine("{0}", mesh.regions.Count);
+ foreach (var region in mesh.regions)
+ {
+ writer.WriteLine("{0} {1} {2} {3} {4}", j, region.pt.X.ToString(nfi),
+ region.pt.Y.ToString(nfi), region.attribute.ToString(nfi),
+ region.area.ToString(nfi));
+
+ j++;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Write the edges to an .edge file.
+ ///
+ ///
+ ///
+ public static void WriteEdges(Mesh mesh, string filename)
+ {
+ Otri tri = default(Otri), trisym = default(Otri);
+ Osub checkmark = default(Osub);
+ Vertex p1, p2;
+
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ // Number of edges, number of boundary markers (zero or one).
+ writer.WriteLine("{0} {1}", mesh.edges, Behavior.UseBoundaryMarkers ? "1" : "0");
+
+ long index = 0;
+ // To loop over the set of edges, loop over all triangles, and look at
+ // the three edges of each triangle. If there isn't another triangle
+ // adjacent to the edge, operate on the edge. If there is another
+ // adjacent triangle, operate on the edge only if the current triangle
+ // has a smaller pointer than its neighbor. This way, each edge is
+ // considered only once.
+ foreach (var item in mesh.triangles.Values)
+ {
+ tri.triangle = item;
+
+ for (tri.orient = 0; tri.orient < 3; tri.orient++)
+ {
+ tri.Sym(ref trisym);
+ if ((tri.triangle.ID < trisym.triangle.ID) || (trisym.triangle == Mesh.dummytri))
+ {
+ p1 = tri.Org();
+ p2 = tri.Dest();
+
+ if (Behavior.UseBoundaryMarkers)
+ {
+ // Edge number, indices of two endpoints, and a boundary marker.
+ // If there's no subsegment, the boundary marker is zero.
+ if (Behavior.UseSegments)
+ {
+ tri.SegPivot(ref checkmark);
+
+ if (checkmark.ss == Mesh.dummysub)
+ {
+ writer.WriteLine("{0} {1} {2} {3}", index, p1.ID, p2.ID, 0);
+ }
+ else
+ {
+ writer.WriteLine("{0} {1} {2} {3}", index, p1.ID, p2.ID,
+ checkmark.ss.boundary);
+ }
+ }
+ else
+ {
+ writer.WriteLine("{0} {1} {2} {3}", index, p1.ID, p2.ID,
+ trisym.triangle == Mesh.dummytri ? "1" : "0");
+ }
+ }
+ else
+ {
+ // Edge number, indices of two endpoints.
+ writer.WriteLine("{0} {1} {2}", index, p1.ID, p2.ID);
+ }
+
+ index++;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Write the triangle neighbors to a .neigh file.
+ ///
+ ///
+ ///
+ /// WARNING: Be sure WriteElements has been called before,
+ /// so the elements are numbered right!
+ public static void WriteNeighbors(Mesh mesh, string filename)
+ {
+ Otri tri = default(Otri), trisym = default(Otri);
+ int n1, n2, n3;
+ int i = 0;
+
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ // Number of triangles, three neighbors per triangle.
+ writer.WriteLine("{0} 3", mesh.triangles.Count);
+
+ Mesh.dummytri.ID = -1;
+
+ foreach (var item in mesh.triangles.Values)
+ {
+ tri.triangle = item;
+
+ tri.orient = 1;
+ tri.Sym(ref trisym);
+ n1 = trisym.triangle.ID;
+
+ tri.orient = 2;
+ tri.Sym(ref trisym);
+ n2 = trisym.triangle.ID;
+
+ tri.orient = 0;
+ tri.Sym(ref trisym);
+ n3 = trisym.triangle.ID;
+
+ // Triangle number, neighboring triangle numbers.
+ writer.WriteLine("{0} {1} {2} {3}", i++, n1, n2, n3);
+ }
+ }
+ }
+
+ ///
+ /// Write the Voronoi diagram to a .voro file.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The Voronoi diagram is the geometric dual of the Delaunay triangulation.
+ /// Hence, the Voronoi vertices are listed by traversing the Delaunay
+ /// triangles, and the Voronoi edges are listed by traversing the Delaunay
+ /// edges.
+ ///
+ /// WARNING: In order to assign numbers to the Voronoi vertices, this
+ /// procedure messes up the subsegments or the extra nodes of every
+ /// element. Hence, you should call this procedure last.
+ public static void WriteVoronoi(Mesh mesh, string filename)
+ {
+ Otri tri = default(Otri), trisym = default(Otri);
+ Vertex torg, tdest, tapex;
+ Point2 circumcenter;
+ double xi = 0, eta = 0;
+
+ int p1, p2, index = 0;
+ tri.orient = 0;
+
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ // Number of triangles, two dimensions, number of vertex attributes, no markers.
+ writer.WriteLine("{0} 2 {1} 0", mesh.triangles.Count, mesh.nextras);
+
+ foreach (var item in mesh.triangles.Values)
+ {
+ tri.triangle = item;
+ torg = tri.Org();
+ tdest = tri.Dest();
+ tapex = tri.Apex();
+ circumcenter = Primitives.FindCircumcenter(torg.pt, tdest.pt, tapex.pt, ref xi, ref eta, false);
+
+ // X and y coordinates.
+ writer.Write("{0} {1} {2}", index, circumcenter.X.ToString(nfi),
+ circumcenter.Y.ToString(nfi));
+
+ for (int i = 0; i < mesh.nextras; i++)
+ {
+ writer.Write(" 0");
+ // TODO
+ // Interpolate the vertex attributes at the circumcenter.
+ //writer.Write(" {0}", torg.attribs[i] + xi * (tdes.attribst[i] - torg.attribs[i]) +
+ // eta * (tapex.attribs[i] - torg.attribs[i]));
+ }
+ writer.WriteLine();
+
+ tri.triangle.ID = index++;
+ }
+
+
+ // Number of edges, zero boundary markers.
+ writer.WriteLine("{0} 0", mesh.edges);
+
+ index = 0;
+ // To loop over the set of edges, loop over all triangles, and look at
+ // the three edges of each triangle. If there isn't another triangle
+ // adjacent to the edge, operate on the edge. If there is another
+ // adjacent triangle, operate on the edge only if the current triangle
+ // has a smaller pointer than its neighbor. This way, each edge is
+ // considered only once.
+ foreach (var item in mesh.triangles.Values)
+ {
+ tri.triangle = item;
+
+ for (tri.orient = 0; tri.orient < 3; tri.orient++)
+ {
+ tri.Sym(ref trisym);
+ if ((tri.triangle.ID < trisym.triangle.ID) || (trisym.triangle == Mesh.dummytri))
+ {
+ // Find the number of this triangle (and Voronoi vertex).
+ p1 = tri.triangle.ID;
+
+ if (trisym.triangle == Mesh.dummytri)
+ {
+ torg = tri.Org();
+ tdest = tri.Dest();
+
+ // Write an infinite ray. Edge number, index of one endpoint,
+ // -1, and x and y coordinates of a vector representing the
+ // direction of the ray.
+ writer.WriteLine("{0} {1} -1 {2} {3}", index, p1,
+ (tdest[1] - torg[1]).ToString(nfi),
+ (torg[0] - tdest[0]).ToString(nfi));
+ }
+ else
+ {
+ // Find the number of the adjacent triangle (and Voronoi vertex).
+ p2 = trisym.triangle.ID;
+ // Finite edge. Write indices of two endpoints.
+ writer.WriteLine("{0} {1} {2}", index, p1, p2);
+ }
+
+ index++;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Write the triangulation to an .off file.
+ ///
+ ///
+ ///
+ ///
+ /// OFF stands for the Object File Format, a format used by the Geometry
+ /// Center's Geomview package.
+ ///
+ public static void WriteOffFile(Mesh mesh, string filename)
+ {
+ Otri tri;
+ Vertex p1, p2, p3;
+
+ long outvertices = mesh.vertices.Count;
+
+ if (Behavior.Jettison)
+ {
+ outvertices = mesh.vertices.Count - mesh.undeads;
+ }
+
+ int index = 0;
+
+ using (StreamWriter writer = new StreamWriter(filename))
+ {
+ writer.WriteLine("OFF");
+ writer.WriteLine("{0} {1} {2}", outvertices, mesh.triangles.Count, mesh.edges);
+
+ foreach (var item in mesh.vertices.Values)
+ {
+ p1 = item;
+
+ if (!Behavior.Jettison || p1.type != VertexType.UndeadVertex)
+ {
+ // The "0.0" is here because the OFF format uses 3D coordinates.
+ writer.WriteLine(" {0} {1} 0.0", p1[0].ToString(nfi), p1[1].ToString(nfi));
+
+ p1.ID = index++;
+ }
+ }
+
+ // Write the triangles.
+ tri.orient = 0;
+ foreach (var item in mesh.triangles.Values)
+ {
+ tri.triangle = item;
+
+ p1 = tri.Org();
+ p2 = tri.Dest();
+ p3 = tri.Apex();
+
+ // The "3" means a three-vertex polygon.
+ writer.WriteLine(" 3 {0} {1} {2}", p1.ID, p2.ID, p3.ID);
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Triangle.NET/Triangle/IO/MeshData.cs b/Triangle.NET/Triangle/IO/MeshData.cs
new file mode 100644
index 0000000..7508a79
--- /dev/null
+++ b/Triangle.NET/Triangle/IO/MeshData.cs
@@ -0,0 +1,33 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/index.html
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.IO
+{
+ ///
+ /// Stores the mesh data in- and output.
+ ///
+ public class MeshData
+ {
+ public double[][] Points; // In / out
+ public double[][] PointAttributes; // In / out
+ public int[] PointMarkers; // In / out
+
+ public int[][] Triangles; // In / out
+ public double[][] TriangleAttributes; // In / out
+ public double[] TriangleAreas; // In only
+ public int[][] Neighbors; // Out only
+
+ public int[][] Segments; // In / out
+ public int[] SegmentMarkers; // In / out
+
+ public double[][] Holes; // In / pointer to array copied out
+ public double[][] Regions; // In / pointer to array copied out
+
+ public int[][] Edges; // Out only
+ public int[] EdgeMarkers; // Out only
+ }
+}
diff --git a/Triangle.NET/Triangle/IO/VoronoiData.cs b/Triangle.NET/Triangle/IO/VoronoiData.cs
new file mode 100644
index 0000000..5094411
--- /dev/null
+++ b/Triangle.NET/Triangle/IO/VoronoiData.cs
@@ -0,0 +1,24 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/index.html
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.IO
+{
+ ///
+ /// Stores the mesh data in- and output.
+ ///
+ public class VoronoiData
+ {
+ public double[][] PointList; // In / out
+ //public int NumberOfPoints; // In / out
+
+ public int[][] EdgeList; // Out only
+ //public int NumberOfEdges; // Out only
+
+ // Stores the direction for infinite voronoi edges
+ public double[][] NormList;
+ }
+}
diff --git a/Triangle.NET/Triangle/Log/ILog.cs b/Triangle.NET/Triangle/Log/ILog.cs
new file mode 100644
index 0000000..2dd6ce9
--- /dev/null
+++ b/Triangle.NET/Triangle/Log/ILog.cs
@@ -0,0 +1,27 @@
+// -----------------------------------------------------------------------
+//
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Log
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// TODO: Update summary.
+ ///
+ public interface ILog
+ {
+ void Add(T item);
+ void Info(string message);
+ void Trace(string message, string location);
+ void Error(string message, string location);
+ void Warning(string message, string location);
+
+ IList Data { get; }
+ }
+}
diff --git a/Triangle.NET/Triangle/Log/SimpleLogger.cs b/Triangle.NET/Triangle/Log/SimpleLogger.cs
new file mode 100644
index 0000000..5011654
--- /dev/null
+++ b/Triangle.NET/Triangle/Log/SimpleLogger.cs
@@ -0,0 +1,74 @@
+// -----------------------------------------------------------------------
+//
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet.Log
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// A simple logger, which logs messages to a List.
+ ///
+ /// Using singleton pattern as proposed by Jon Skeet.
+ /// http://csharpindepth.com/Articles/General/Singleton.aspx
+ ///
+ public sealed class SimpleLogger : ILog
+ {
+ private List log = new List();
+
+ #region Singleton pattern
+
+ private static readonly SimpleLogger instance = new SimpleLogger();
+
+ // Explicit static constructor to tell C# compiler
+ // not to mark type as beforefieldinit
+ static SimpleLogger() { }
+
+ private SimpleLogger() { }
+
+ public static ILog Instance
+ {
+ get
+ {
+ return instance;
+ }
+ }
+
+ #endregion
+
+ public void Add(string item)
+ {
+ log.Add(item);
+ }
+
+ public void Info(string message)
+ {
+ log.Add(message);
+ }
+
+ public void Trace(string message, string location)
+ {
+ log.Add(message);
+ }
+
+ public void Warning(string message, string location)
+ {
+ log.Add(message);
+ }
+
+ public void Error(string message, string location)
+ {
+ log.Add(message);
+ }
+
+ public IList Data
+ {
+ get { return log; }
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Mesh.cs b/Triangle.NET/Triangle/Mesh.cs
new file mode 100644
index 0000000..ece6457
--- /dev/null
+++ b/Triangle.NET/Triangle/Mesh.cs
@@ -0,0 +1,3239 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using TriangleNet.Data;
+ using TriangleNet.Log;
+ using TriangleNet.IO;
+ using TriangleNet.Algorithm;
+
+ ///
+ /// Mesh data structure.
+ ///
+ public class Mesh
+ {
+ #region Variables
+
+ ILog logger;
+
+ Quality quality;
+ Sampler sampler;
+
+ // Variable that maintains the stack of recently flipped triangles.
+ List flipstackers;
+ FlipStacker lastflip;
+
+ // Using hashsets for memory management should quite fast.
+ internal Dictionary triangles;
+ internal Dictionary subsegs;
+ internal Dictionary vertices;
+
+ // TODO: Check if custom hashmap implementation could be faster.
+
+ internal List holes;
+ internal List regions;
+
+ internal List viri;
+
+ // Other variables.
+ internal double xmin, xmax, ymin, ymax; // x and y bounds.
+ internal int invertices; // Number of input vertices.
+ internal int inelements; // Number of input triangles.
+ internal int insegments; // Number of input segments.
+ internal int undeads; // Number of input vertices that don't appear in the mesh.
+ internal long edges; // Number of output edges.
+ internal int mesh_dim; // Dimension (ought to be 2).
+ internal int nextras; // Number of attributes per vertex.
+ internal int eextras; // Number of attributes per triangle.
+ internal long hullsize; // Number of edges in convex hull.
+ internal int steinerleft; // Number of Steiner points not yet used.
+ internal bool checksegments; // Are there segments in the triangulation yet?
+ internal bool checkquality; // Has quality triangulation begun yet?
+
+ // Triangular bounding box vertices.
+ internal Vertex infvertex1, infvertex2, infvertex3;
+
+ // The 'triangle' that occupies all of "outer space."
+ internal static Triangle dummytri;
+
+ // The omnipresent subsegment. Referenced by any triangle or
+ // subsegment that isn't really connected to a subsegment at
+ // that location.
+ internal static Subseg dummysub;
+
+ // Pointer to a recently visited triangle. Improves point location if
+ // proximate vertices are inserted sequentially.
+ internal Otri recenttri;
+
+ static FlipStacker dummyflip;
+
+ #endregion
+
+ ///
+ /// Gets the number of input vertices.
+ ///
+ public int NumberOfInputPoints { get { return invertices; } }
+
+ public Mesh()
+ {
+ logger = SimpleLogger.Instance;
+
+ Behavior.Init();
+
+ vertices = new Dictionary();
+ triangles = new Dictionary();
+ subsegs = new Dictionary();
+
+ viri = new List();
+ flipstackers = new List();
+
+ holes = new List();
+ regions = new List();
+
+ quality = new Quality(this);
+
+ sampler = new Sampler();
+
+ Primitives.ExactInit();
+
+ if (dummytri == null)
+ {
+ DummyInit();
+ }
+ }
+
+ ///
+ /// Load a mesh from file (.node/poly and .ele).
+ ///
+ public void Load(string inputfile)
+ {
+ MeshData input = FileReader.ReadFile(inputfile, true);
+
+ this.Load(input);
+ }
+
+ ///
+ /// Reconstructs a mesh from raw input data.
+ ///
+ public void Load(MeshData input)
+ {
+ if (input.Triangles == null)
+ {
+ throw new ArgumentException("The input data contains no triangles.");
+ }
+
+ // Clear all data structures / reset hash seeds
+ this.ResetData();
+
+ if (input.Segments != null)
+ {
+ Behavior.Poly = true;
+ }
+
+ if (input.EdgeMarkers != null)
+ {
+ Behavior.UseBoundaryMarkers = true;
+ }
+
+ if (input.TriangleAreas != null)
+ {
+ Behavior.VarArea = true;
+ }
+
+ if (!Behavior.Poly)
+ {
+ // Be careful not to allocate space for element area constraints that
+ // will never be assigned any value (other than the default -1.0).
+ Behavior.VarArea = false;
+
+ // Be careful not to add an extra attribute to each element unless the
+ // input supports it (PSLG in, but not refining a preexisting mesh).
+ Behavior.RegionAttrib = false;
+ }
+
+ TransferNodes(input);
+
+ // Read and reconstruct a mesh.
+ hullsize = DataReader.Reconstruct(this, input);
+ }
+
+ ///
+ /// Triangulate given input file (.node or .poly).
+ ///
+ ///
+ public void Triangulate(string inputFile)
+ {
+ MeshData input = FileReader.ReadFile(inputFile);
+
+ this.Triangulate(input);
+ }
+
+ ///
+ /// Triangulate given input data.
+ ///
+ ///
+ public void Triangulate(MeshData input)
+ {
+ ResetData();
+
+ if (input.Segments != null)
+ {
+ Behavior.Poly = true;
+ }
+
+ if (input.EdgeMarkers != null)
+ {
+ Behavior.UseBoundaryMarkers = true;
+ }
+
+ if (!Behavior.Poly)
+ {
+ // Be careful not to allocate space for element area constraints that
+ // will never be assigned any value (other than the default -1.0).
+ Behavior.VarArea = false;
+
+ // Be careful not to add an extra attribute to each element unless the
+ // input supports it (PSLG in, but not refining a preexisting mesh).
+ Behavior.RegionAttrib = false;
+ }
+
+ steinerleft = Behavior.Steiner;
+
+ TransferNodes(input);
+
+ hullsize = Delaunay(); // Triangulate the vertices.
+
+ // Ensure that no vertex can be mistaken for a triangular bounding
+ // box vertex in insertvertex().
+ infvertex1 = null;
+ infvertex2 = null;
+ infvertex3 = null;
+
+ if (Behavior.UseSegments)
+ {
+ // Segments will be introduced next.
+ checksegments = true;
+
+ // Insert PSLG segments and/or convex hull segments.
+ FormSkeleton(input);
+ }
+
+ if (Behavior.Poly && (triangles.Count > 0))
+ {
+ if (input.Holes != null)
+ {
+ // Copy holes
+ for (int i = 0; i < input.Holes.Length; i++)
+ {
+ holes.Add(new Point2(input.Holes[i][0], input.Holes[i][1]));
+ }
+ }
+
+ if (input.Regions != null)
+ {
+ // Copy regions
+ for (int i = 0; i < input.Regions.Length; i++)
+ {
+ regions.Add(new Region(input.Regions[i]));
+ }
+ }
+
+ // Carve out holes and concavities.
+ Carver c = new Carver(this);
+ c.CarveHoles();
+ }
+ else
+ {
+ // Without a PSLG, there can be no holes or regional attributes
+ // or area constraints. The following are set to zero to avoid
+ // an accidental free() later.
+ //
+ // TODO: -
+ holes.Clear();
+ regions.Clear();
+ }
+
+ if (Behavior.Quality && (triangles.Count > 0))
+ {
+ quality.EnforceQuality(); // Enforce angle and area constraints.
+ }
+
+ quality.CheckMesh();
+
+ // Calculate the number of edges.
+ edges = (3 * triangles.Count + hullsize) / 2;
+ }
+
+ ///
+ /// Refines the current mesh by finding the maximum triangle area and setting
+ /// the a global area constraint to half its size.
+ ///
+ public void Refine(bool halfArea)
+ {
+ if (halfArea)
+ {
+ double tmp, maxArea = 0;
+
+ foreach (var t in this.triangles.Values)
+ {
+ tmp = (t.vertices[2].pt.X - t.vertices[0].pt.X) * (t.vertices[1].pt.Y - t.vertices[0].pt.Y) -
+ (t.vertices[1].pt.X - t.vertices[0].pt.X) * (t.vertices[2].pt.Y - t.vertices[0].pt.Y);
+
+ tmp = Math.Abs(tmp) / 2.0;
+
+ if (tmp > maxArea)
+ {
+ maxArea = tmp;
+ }
+ }
+
+ this.Refine(maxArea / 2);
+ }
+ else
+ {
+ Refine();
+ }
+ }
+
+ ///
+ /// Refines the current mesh by setting a global area constraint.
+ ///
+ /// Global area constraint.
+ public void Refine(double areaConstraint)
+ {
+ Behavior.FixedArea = true;
+ Behavior.MaxArea = areaConstraint;
+
+ this.Refine();
+
+ // Reset option for sanity
+ Behavior.FixedArea = false;
+ Behavior.MaxArea = -1.0;
+ }
+
+ ///
+ /// Refines the current mesh.
+ ///
+ public void Refine()
+ {
+ inelements = triangles.Count;
+ invertices = vertices.Count;
+
+ // TODO: Set all vertex types to input (i.e. NOT free)?
+
+ if (Behavior.Poly)
+ {
+ if (Behavior.UseSegments)
+ {
+ insegments = subsegs.Count;
+ }
+ else
+ {
+ insegments = (int)hullsize;
+ }
+ }
+
+ Reset();
+
+ steinerleft = Behavior.Steiner;
+
+ // Ensure that no vertex can be mistaken for a triangular bounding
+ // box vertex in insertvertex().
+ infvertex1 = null;
+ infvertex2 = null;
+ infvertex3 = null;
+
+ if (Behavior.UseSegments)
+ {
+ checksegments = true;
+ }
+
+ // TODO
+ holes.Clear();
+ regions.Clear();
+
+ if (triangles.Count > 0)
+ {
+ // Enforce angle and area constraints.
+ quality.EnforceQuality();
+ }
+
+ // Calculate the number of edges.
+ edges = (3 * triangles.Count + hullsize) / 2;
+ }
+
+ ///
+ /// Check mesh consistency and (constrained) Delaunay property.
+ ///
+ public void Check()
+ {
+ quality.CheckMesh();
+ quality.CheckDelaunay();
+ }
+
+ ///
+ /// Returns the raw mesh data.
+ ///
+ /// Mesh data.
+ public MeshData GetMeshData()
+ {
+ return GetMeshData(true, true, true);
+ }
+
+ ///
+ /// Returns the raw mesh data.
+ ///
+ /// Write elements to output.
+ /// Write edges to output.
+ /// Write neighbour information to output.
+ /// Mesh data.
+ public MeshData GetMeshData(bool writeElements, bool writeEdges, bool writeNeighbors)
+ {
+ MeshData output = new MeshData();
+
+ if (Behavior.UseSegments)
+ {
+ //output.NumberOfSegments = subsegs.Count;
+ }
+ else
+ {
+ //output.NumberOfSegments = (int)hullsize;
+ }
+
+ // Numbers the vertices too.
+ DataWriter.WriteNodes(this, output);
+
+ if (writeElements)
+ {
+ DataWriter.WriteElements(this, output);
+ }
+
+ // The -c switch (convex switch) causes a PSLG to be written
+ // even if none was read.
+ if (Behavior.Poly || Behavior.Convex)
+ {
+ DataWriter.WritePoly(this, output);
+ }
+
+ if (writeEdges)
+ {
+ DataWriter.WriteEdges(this, output);
+ }
+
+ if (writeElements && writeNeighbors)
+ {
+ DataWriter.WriteNeighbors(this, output);
+ }
+
+ return output;
+ }
+
+ #region Options
+
+ ///
+ /// Set options for mesh generation.
+ ///
+ /// Mesh gerneration option.
+ /// New option value.
+ public void SetOption(Options option, bool value)
+ {
+ if (option == Options.ConformingDelaunay)
+ {
+ Behavior.ConformDel = value;
+ Behavior.Quality = value; // TODO: ok?
+ return;
+ }
+ else if (option == Options.BoundaryMarkers)
+ {
+ Behavior.UseBoundaryMarkers = value;
+ return;
+ }
+ else if (option == Options.Quality)
+ {
+ Behavior.Quality = value;
+
+ if (value)
+ {
+ Behavior.MinAngle = 20.0;
+ Behavior.MaxAngle = 140.0;
+ UpdateOptions();
+ }
+
+ return;
+ }
+ else if (option == Options.Convex)
+ {
+ Behavior.Convex = value;
+ return;
+ }
+
+ logger.Warning("Invalid option value.", "Mesh.SetOption(bool)");
+ }
+
+ ///
+ /// Set options for mesh generation.
+ ///
+ /// Mesh gerneration option.
+ /// New option value.
+ public void SetOption(Options option, double value)
+ {
+
+ if (option == Options.MinAngle)
+ {
+ Behavior.MinAngle = value;
+ Behavior.Quality = (value >= 0); // TODO: ok?
+ UpdateOptions();
+ return;
+ }
+ else if (option == Options.MaxAngle)
+ {
+ Behavior.MaxAngle = value;
+ Behavior.Quality = (value >= 0); // TODO: ok?
+ UpdateOptions();
+ return;
+ }
+ else if (option == Options.MaxArea)
+ {
+ Behavior.MaxArea = value;
+ Behavior.FixedArea = true;
+ Behavior.Quality = (value >= 0); // TODO: ok?
+ return;
+ }
+
+ logger.Warning("Invalid option value.", "Mesh.SetOption(double)");
+ }
+
+ ///
+ /// Set options for mesh generation.
+ ///
+ /// Mesh gerneration option.
+ /// New option value.
+ public void SetOption(Options option, int value)
+ {
+ if (option == Options.NoBisect)
+ {
+ Behavior.NoBisect = value;
+ return;
+ }
+ else if (option == Options.SteinerPoints)
+ {
+ Behavior.Steiner = value;
+ return;
+ }
+ else if (option == Options.MinAngle)
+ {
+ Behavior.MinAngle = value;
+ Behavior.Quality = (value >= 0); // TODO: ok?
+ UpdateOptions();
+ return;
+ }
+ else if (option == Options.MaxAngle)
+ {
+ Behavior.MaxAngle = value;
+ Behavior.Quality = (value >= 0); // TODO: ok?
+ UpdateOptions();
+ return;
+ }
+ else if (option == Options.MaxArea)
+ {
+ Behavior.MaxArea = value;
+ Behavior.Quality = (value >= 0); // TODO: ok?
+ Behavior.FixedArea = true;
+ return;
+ }
+
+ logger.Warning("Invalid option value.", "Mesh.SetOption(int)");
+ }
+
+ ///
+ /// Keeps options synchronized.
+ ///
+ private void UpdateOptions()
+ {
+ Behavior.UseSegments = Behavior.Poly || Behavior.Quality || Behavior.Convex;
+ Behavior.GoodAngle = Math.Cos(Behavior.MinAngle * Math.PI / 180.0);
+ Behavior.MaxGoodAngle = Math.Cos(Behavior.MaxAngle * Math.PI / 180.0);
+
+ if (Behavior.GoodAngle == 1.0)
+ {
+ Behavior.Offconstant = 0.0;
+ }
+ else
+ {
+ Behavior.Offconstant = 0.475 * Math.Sqrt((1.0 + Behavior.GoodAngle) / (1.0 - Behavior.GoodAngle));
+ }
+
+ Behavior.GoodAngle *= Behavior.GoodAngle;
+ }
+
+ #endregion
+
+ #region Misc
+
+ ///
+ /// Form a Delaunay triangulation.
+ ///
+ /// The number of points on the hull.
+ int Delaunay()
+ {
+ int hulledges = 0;
+
+ eextras = 0;
+
+ if (Behavior.Algorithm == TriangulationAlgorithm.Dwyer)
+ {
+ Dwyer alg = new Dwyer();
+ hulledges = alg.Triangulate(this);
+ }
+ else if (Behavior.Algorithm == TriangulationAlgorithm.SweepLine)
+ {
+ SweepLine alg = new SweepLine();
+ hulledges = alg.Triangulate(this);
+ }
+ else
+ {
+ Incremental alg = new Incremental();
+ hulledges = alg.Triangulate(this);
+ }
+
+ if (triangles.Count == 0)
+ {
+ // The input vertices were all collinear,
+ // so there are no triangles.
+ return 0;
+ }
+
+ return hulledges;
+ }
+
+ ///
+ /// Reset all the mesh data. This method will also wipe
+ /// out all mesh data.
+ ///
+ /// The number of input points. Used to initialize
+ /// memory for the mesh data.
+ private void ResetData()
+ {
+ vertices.Clear();
+ triangles.Clear();
+ subsegs.Clear();
+
+ Triangle.ResetHashSeed(0);
+ Vertex.ResetHashSeed(0);
+ Subseg.ResetHashSeed(0);
+
+ viri.Clear();
+ flipstackers.Clear();
+
+ Reset();
+ }
+
+ ///
+ /// Reset the mesh triangulation state.
+ ///
+ private void Reset()
+ {
+ recenttri.triangle = null; // No triangle has been visited yet.
+ undeads = 0; // No eliminated input vertices yet.
+ checksegments = false; // There are no segments in the triangulation yet.
+ checkquality = false; // The quality triangulation stage has not begun.
+
+ Statistic.InCircleCount = 0;
+ Statistic.CounterClockwiseCount = 0;
+ Statistic.Orient3dCount = 0;
+ Statistic.HyperbolaCount = 0;
+ Statistic.CircleTopCount = 0;
+ Statistic.CircumcenterCount = 0;
+ }
+
+ ///
+ /// Initialize the triangle that fills "outer space" and the omnipresent subsegment.
+ ///
+ ///
+ /// The triangle that fills "outer space," called 'dummytri', is pointed to
+ /// by every triangle and subsegment on a boundary (be it outer or inner) of
+ /// the triangulation. Also, 'dummytri' points to one of the triangles on
+ /// the convex hull (until the holes and concavities are carved), making it
+ /// possible to find a starting triangle for point location.
+ //
+ /// The omnipresent subsegment, 'dummysub', is pointed to by every triangle
+ /// or subsegment that doesn't have a full complement of real subsegments
+ /// to point to.
+ //
+ /// 'dummytri' and 'dummysub' are generally required to fulfill only a few
+ /// invariants: their vertices must remain NULL and 'dummytri' must always
+ /// be bonded (at offset zero) to some triangle on the convex hull of the
+ /// mesh, via a boundary edge. Otherwise, the connections of 'dummytri' and
+ /// 'dummysub' may change willy-nilly. This makes it possible to avoid
+ /// writing a good deal of special-case code (in the edge flip, for example)
+ /// for dealing with the boundary of the mesh, places where no subsegment is
+ /// present, and so forth. Other entities are frequently bonded to
+ /// 'dummytri' and 'dummysub' as if they were real mesh entities, with no
+ /// harm done.
+ ///
+ private void DummyInit()
+ {
+ // Set up 'dummytri', the 'triangle' that occupies "outer space."
+ dummytri = new Triangle(0);
+
+ // Initialize the three adjoining triangles to be "outer space." These
+ // will eventually be changed by various bonding operations, but their
+ // values don't really matter, as long as they can legally be
+ // dereferenced.
+ dummytri.neighbors[0].triangle = dummytri;
+ dummytri.neighbors[1].triangle = dummytri;
+ dummytri.neighbors[2].triangle = dummytri;
+
+ if (Behavior.UseSegments)
+ {
+ // Set up 'dummysub', the omnipresent subsegment pointed to by any
+ // triangle side or subsegment end that isn't attached to a real
+ // subsegment.
+ dummysub = new Subseg();
+
+ // Initialize the two adjoining subsegments to be the omnipresent
+ // subsegment. These will eventually be changed by various bonding
+ // operations, but their values don't really matter, as long as they
+ // can legally be dereferenced.
+ dummysub.subsegs[0].ss = dummysub;
+ dummysub.subsegs[1].ss = dummysub;
+
+ // Initialize the three adjoining subsegments of 'dummytri' to be
+ // the omnipresent subsegment.
+ dummytri.subsegs[0].ss = dummysub;
+ dummytri.subsegs[1].ss = dummysub;
+ dummytri.subsegs[2].ss = dummysub;
+ }
+
+ dummyflip = new FlipStacker();
+ }
+
+ ///
+ /// Read the vertices from memory.
+ ///
+ /// The input data.
+ private void TransferNodes(MeshData data)
+ {
+ Vertex vertex;
+ double x, y;
+ int attribs;
+
+ double[][] points = data.Points;
+ attribs = data.PointAttributes == null ? 0 : data.PointAttributes.Length;
+
+ this.invertices = data.Points.Length;
+ this.mesh_dim = 2;
+ this.nextras = attribs;
+
+ if (this.invertices < 3)
+ {
+ logger.Error("Input must have at least three input vertices.", "MeshReader.TransferNodes()");
+ throw new Exception("Input must have at least three input vertices.");
+ }
+
+ // Read the vertices.
+ for (int i = 0; i < this.invertices; i++)
+ {
+ vertex = new Vertex(nextras);
+ vertex.type = VertexType.InputVertex;
+ vertex.mark = 0;
+ vertex.ID = vertex.Hash;
+
+ // Read the vertex coordinates.
+ x = vertex.pt.X = points[i][0];
+ y = vertex.pt.Y = points[i][1];
+
+ // Read the vertex attributes.
+ for (int j = 0; j < attribs; j++)
+ {
+ //vertexloop.pt.attribs[j] = pointattriblist[i][j];
+ }
+ if (data.PointMarkers != null)
+ {
+ // Read a vertex marker.
+ vertex.mark = data.PointMarkers[i];
+ }
+
+ this.vertices.Add(vertex.Hash, vertex);
+
+ // Determine the smallest and largest x and y coordinates.
+ if (i == 0)
+ {
+ this.xmin = this.xmax = x;
+ this.ymin = this.ymax = y;
+ }
+ else
+ {
+ this.xmin = (x < this.xmin) ? x : this.xmin;
+ this.xmax = (x > this.xmax) ? x : this.xmax;
+ this.ymin = (y < this.ymin) ? y : this.ymin;
+ this.ymax = (y > this.ymax) ? y : this.ymax;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Factory
+
+ ///
+ /// Create a new triangle with orientation zero.
+ ///
+ /// Reference to the new triangle.
+ internal void MakeTriangle(ref Otri newotri)
+ {
+ Triangle t = new Triangle(eextras);
+
+ newotri.triangle = t;
+ newotri.orient = 0;
+
+ triangles.Add(t.Hash, t);
+ }
+
+ ///
+ /// Create a new subsegment with orientation zero.
+ ///
+ /// Reference to the new subseg.
+ internal void MakeSubseg(ref Osub newsubseg)
+ {
+ Subseg s = new Subseg();
+
+ newsubseg.ss = s;
+ newsubseg.ssorient = 0;
+
+ subsegs.Add(s.Hash, s);
+ }
+ #endregion
+
+ #region Manipulation
+
+ ///
+ /// Insert a vertex into a Delaunay triangulation, performing flips as necessary
+ /// to maintain the Delaunay property.
+ ///
+ ///
+ /// The point 'newvertex' is located. If 'searchtri.tri' is not NULL,
+ /// the search for the containing triangle begins from 'searchtri'. If
+ /// 'searchtri.tri' is NULL, a full point location procedure is called.
+ /// If 'insertvertex' is found inside a triangle, the triangle is split into
+ /// three; if 'insertvertex' lies on an edge, the edge is split in two,
+ /// thereby splitting the two adjacent triangles into four. Edge flips are
+ /// used to restore the Delaunay property. If 'insertvertex' lies on an
+ /// existing vertex, no action is taken, and the value DUPLICATEVERTEX is
+ /// returned. On return, 'searchtri' is set to a handle whose origin is the
+ /// existing vertex.
+ ///
+ /// InsertVertex() does not use flip() for reasons of speed; some
+ /// information can be reused from edge flip to edge flip, like the
+ /// locations of subsegments.
+ ///
+ /// The point to be inserted.
+ /// The triangle to start the search.
+ /// Normally, the parameter 'splitseg' is set to NULL,
+ /// implying that no subsegment should be split. In this case, if 'insertvertex'
+ /// is found to lie on a segment, no action is taken, and the value VIOLATINGVERTEX
+ /// is returned. On return, 'searchtri' is set to a handle whose primary edge is the
+ /// violated subsegment.
+ ///
+ /// If the calling routine wishes to split a subsegment
+ /// by inserting a vertex in it, the parameter 'splitseg' should be that subsegment.
+ /// In this case, 'searchtri' MUST be the triangle handle reached by pivoting
+ /// from that subsegment; no point location is done.
+ /// Flags that indicate whether or not there should
+ /// be checks for the creation of encroached subsegments. If a newly inserted
+ /// vertex encroaches upon subsegments, these subsegments are added to the list
+ /// of subsegments to be split if 'segmentflaws' is set.
+ /// Flags that indicate whether or not there should be
+ /// checks for the creation of bad quality triangles. If bad triangles are
+ /// created, these are added to the queue if 'triflaws' is set.
+ /// If a duplicate vertex or violated segment does not prevent the
+ /// vertex from being inserted, the return value will be ENCROACHINGVERTEX if
+ /// the vertex encroaches upon a subsegment (and checking is enabled), or
+ /// SUCCESSFULVERTEX otherwise. In either case, 'searchtri' is set to a handle
+ /// whose origin is the newly inserted vertex.
+ internal InsertVertexResult InsertVertex(Vertex newvertex, ref Otri searchtri, ref Osub splitseg, bool segmentflaws, bool triflaws)
+ {
+ Otri horiz = default(Otri);
+ Otri top = default(Otri);
+ Otri botleft = default(Otri), botright = default(Otri);
+ Otri topleft = default(Otri), topright = default(Otri);
+ Otri newbotleft = default(Otri), newbotright = default(Otri);
+ Otri newtopright = default(Otri);
+ Otri botlcasing = default(Otri), botrcasing = default(Otri);
+ Otri toplcasing = default(Otri), toprcasing = default(Otri);
+ Otri testtri = default(Otri);
+ Osub botlsubseg = default(Osub), botrsubseg = default(Osub);
+ Osub toplsubseg = default(Osub), toprsubseg = default(Osub);
+ Osub brokensubseg = default(Osub);
+ Osub checksubseg = default(Osub);
+ Osub rightsubseg = default(Osub);
+ Osub newsubseg = default(Osub);
+ BadSubseg encroached;
+ FlipStacker newflip;
+ Vertex first;
+ Vertex leftvertex, rightvertex, botvertex, topvertex, farvertex;
+ Vertex segmentorg, segmentdest;
+ double attrib;
+ double area;
+ InsertVertexResult success;
+ LocateResult intersect;
+ bool doflip;
+ bool mirrorflag;
+ bool enq;
+ int i;
+
+ if (splitseg.ss == null)
+ {
+ // Find the location of the vertex to be inserted. Check if a good
+ // starting triangle has already been provided by the caller.
+ if (searchtri.triangle == dummytri)
+ {
+ // Find a boundary triangle.
+ horiz.triangle = dummytri;
+ horiz.orient = 0;
+ horiz.SymSelf();
+ // Search for a triangle containing 'newvertex'.
+ intersect = Locate(newvertex.pt, ref horiz);
+ }
+ else
+ {
+ // Start searching from the triangle provided by the caller.
+ searchtri.Copy(ref horiz);
+ intersect = PreciseLocate(newvertex.pt, ref horiz, true);
+ }
+ }
+ else
+ {
+ // The calling routine provides the subsegment in which
+ // the vertex is inserted.
+ searchtri.Copy(ref horiz);
+ intersect = LocateResult.OnEdge;
+ }
+
+ if (intersect == LocateResult.OnVertex)
+ {
+ // There's already a vertex there. Return in 'searchtri' a triangle
+ // whose origin is the existing vertex.
+ horiz.Copy(ref searchtri);
+ horiz.Copy(ref recenttri);
+ return InsertVertexResult.Duplicate;
+ }
+ if ((intersect == LocateResult.OnEdge) || (intersect == LocateResult.Outside))
+ {
+ // The vertex falls on an edge or boundary.
+ if (checksegments && (splitseg.ss == null))
+ {
+ // Check whether the vertex falls on a subsegment.
+ horiz.SegPivot(ref brokensubseg);
+ if (brokensubseg.ss != dummysub)
+ {
+ // The vertex falls on a subsegment, and hence will not be inserted.
+ if (segmentflaws)
+ {
+ enq = Behavior.NoBisect != 2;
+ if (enq && (Behavior.NoBisect == 1))
+ {
+ // This subsegment may be split only if it is an
+ // internal boundary.
+ horiz.Sym(ref testtri);
+ enq = testtri.triangle != dummytri;
+ }
+ if (enq)
+ {
+ // Add the subsegment to the list of encroached subsegments.
+ encroached = new BadSubseg();
+ encroached.encsubseg = brokensubseg;
+ encroached.subsegorg = brokensubseg.Org();
+ encroached.subsegdest = brokensubseg.Dest();
+
+ quality.AddBadSubseg(encroached);
+ }
+ }
+ // Return a handle whose primary edge contains the vertex,
+ // which has not been inserted.
+ horiz.Copy(ref searchtri);
+ horiz.Copy(ref recenttri);
+ return InsertVertexResult.Violating;
+ }
+ }
+
+ // Insert the vertex on an edge, dividing one triangle into two (if
+ // the edge lies on a boundary) or two triangles into four.
+ horiz.Lprev(ref botright);
+ botright.Sym(ref botrcasing);
+ horiz.Sym(ref topright);
+ // Is there a second triangle? (Or does this edge lie on a boundary?)
+ mirrorflag = topright.triangle != dummytri;
+ if (mirrorflag)
+ {
+ topright.LnextSelf();
+ topright.Sym(ref toprcasing);
+ MakeTriangle(ref newtopright);
+ }
+ else
+ {
+ // Splitting a boundary edge increases the number of boundary edges.
+ hullsize++;
+ }
+ MakeTriangle(ref newbotright);
+
+ // Set the vertices of changed and new triangles.
+ rightvertex = horiz.Org();
+ leftvertex = horiz.Dest();
+ botvertex = horiz.Apex();
+ newbotright.SetOrg(botvertex);
+ newbotright.SetDest(rightvertex);
+ newbotright.SetApex(newvertex);
+ horiz.SetOrg(newvertex);
+
+ for (i = 0; i < eextras; i++)
+ {
+ // Set the element attributes of a new triangle.
+ newbotright.triangle.attributes[i] = botright.triangle.attributes[i];
+ }
+
+ if (Behavior.VarArea)
+ {
+ // Set the area constraint of a new triangle.
+ newbotright.triangle.area = botright.triangle.area;
+ }
+
+ if (mirrorflag)
+ {
+ topvertex = topright.Dest();
+ newtopright.SetOrg(rightvertex);
+ newtopright.SetDest(topvertex);
+ newtopright.SetApex(newvertex);
+ topright.SetOrg(newvertex);
+
+ for (i = 0; i < eextras; i++)
+ {
+ // Set the element attributes of another new triangle.
+ newtopright.triangle.attributes[i] = topright.triangle.attributes[i];
+ }
+
+ if (Behavior.VarArea)
+ {
+ // Set the area constraint of another new triangle.
+ newtopright.triangle.area = topright.triangle.area;
+ }
+ }
+
+ // There may be subsegments that need to be bonded
+ // to the new triangle(s).
+ if (checksegments)
+ {
+ botright.SegPivot(ref botrsubseg);
+
+ if (botrsubseg.ss != dummysub)
+ {
+ botright.SegDissolve();
+ newbotright.SegBond(ref botrsubseg);
+ }
+
+ if (mirrorflag)
+ {
+ topright.SegPivot(ref toprsubseg);
+ if (toprsubseg.ss != dummysub)
+ {
+ topright.SegDissolve();
+ newtopright.SegBond(ref toprsubseg);
+ }
+ }
+ }
+
+ // Bond the new triangle(s) to the surrounding triangles.
+ newbotright.Bond(ref botrcasing);
+ newbotright.LprevSelf();
+ newbotright.Bond(ref botright);
+ newbotright.LprevSelf();
+
+ if (mirrorflag)
+ {
+ newtopright.Bond(ref toprcasing);
+ newtopright.LnextSelf();
+ newtopright.Bond(ref topright);
+ newtopright.LnextSelf();
+ newtopright.Bond(ref newbotright);
+ }
+
+ if (splitseg.ss != null)
+ {
+ // Split the subsegment into two.
+ splitseg.SetDest(newvertex);
+ segmentorg = splitseg.SegOrg();
+ segmentdest = splitseg.SegDest();
+ splitseg.SymSelf();
+ splitseg.Pivot(ref rightsubseg);
+ InsertSubseg(ref newbotright, splitseg.ss.boundary);
+ newbotright.SegPivot(ref newsubseg);
+ newsubseg.SetSegOrg(segmentorg);
+ newsubseg.SetSegDest(segmentdest);
+ splitseg.Bond(ref newsubseg);
+ newsubseg.SymSelf();
+ newsubseg.Bond(ref rightsubseg);
+ splitseg.SymSelf();
+
+ // Transfer the subsegment's boundary marker to the vertex if required.
+ if (newvertex.mark == 0)
+ {
+ newvertex.mark = splitseg.ss.boundary;
+ }
+ }
+
+ if (checkquality)
+ {
+ //poolrestart(&m.flipstackers); TODO
+ flipstackers.Clear();
+
+ lastflip = new FlipStacker();
+ lastflip.flippedtri = horiz;
+ lastflip.prevflip = dummyflip;
+
+ flipstackers.Add(lastflip); // TODO: Could be ok???
+
+ //throw new Exception("insertvertex: what to do here??");
+ }
+
+ // Position 'horiz' on the first edge to check for
+ // the Delaunay property.
+ horiz.LnextSelf();
+ }
+ else
+ {
+ // Insert the vertex in a triangle, splitting it into three.
+ horiz.Lnext(ref botleft);
+ horiz.Lprev(ref botright);
+ botleft.Sym(ref botlcasing);
+ botright.Sym(ref botrcasing);
+ MakeTriangle(ref newbotleft);
+ MakeTriangle(ref newbotright);
+
+ // Set the vertices of changed and new triangles.
+ rightvertex = horiz.Org();
+ leftvertex = horiz.Dest();
+ botvertex = horiz.Apex();
+ newbotleft.SetOrg(leftvertex);
+ newbotleft.SetDest(botvertex);
+ newbotleft.SetApex(newvertex);
+ newbotright.SetOrg(botvertex);
+ newbotright.SetDest(rightvertex);
+ newbotright.SetApex(newvertex);
+ horiz.SetApex(newvertex);
+
+ for (i = 0; i < eextras; i++)
+ {
+ // Set the element attributes of the new triangles.
+ attrib = horiz.triangle.attributes[i];
+ newbotleft.triangle.attributes[i] = attrib;
+ newbotright.triangle.attributes[i] = attrib;
+ }
+
+ if (Behavior.VarArea)
+ {
+ // Set the area constraint of the new triangles.
+ area = horiz.triangle.area;
+ newbotleft.triangle.area = area;
+ newbotright.triangle.area = area;
+ }
+
+ // There may be subsegments that need to be bonded
+ // to the new triangles.
+ if (checksegments)
+ {
+ botleft.SegPivot(ref botlsubseg);
+ if (botlsubseg.ss != dummysub)
+ {
+ botleft.SegDissolve();
+ newbotleft.SegBond(ref botlsubseg);
+ }
+ botright.SegPivot(ref botrsubseg);
+ if (botrsubseg.ss != dummysub)
+ {
+ botright.SegDissolve();
+ newbotright.SegBond(ref botrsubseg);
+ }
+ }
+
+ // Bond the new triangles to the surrounding triangles.
+ newbotleft.Bond(ref botlcasing);
+ newbotright.Bond(ref botrcasing);
+ newbotleft.LnextSelf();
+ newbotright.LprevSelf();
+ newbotleft.Bond(ref newbotright);
+ newbotleft.LnextSelf();
+ botleft.Bond(ref newbotleft);
+ newbotright.LprevSelf();
+ botright.Bond(ref newbotright);
+
+ if (checkquality)
+ {
+ // poolrestart(&m->flipstackers); TODO
+ flipstackers.Clear();
+
+ lastflip = new FlipStacker();
+ lastflip.flippedtri = horiz;
+ lastflip.prevflip = null;
+
+ flipstackers.Add(lastflip);
+ }
+ }
+
+ // The insertion is successful by default, unless an encroached
+ // subsegment is found.
+ success = InsertVertexResult.Successful;
+ // Circle around the newly inserted vertex, checking each edge opposite
+ // it for the Delaunay property. Non-Delaunay edges are flipped.
+ // 'horiz' is always the edge being checked. 'first' marks where to
+ // stop circling.
+ first = horiz.Org();
+ rightvertex = first;
+ leftvertex = horiz.Dest();
+ // Circle until finished.
+ while (true)
+ {
+ // By default, the edge will be flipped.
+ doflip = true;
+
+ if (checksegments)
+ {
+ // Check for a subsegment, which cannot be flipped.
+ horiz.SegPivot(ref checksubseg);
+ if (checksubseg.ss != dummysub)
+ {
+ // The edge is a subsegment and cannot be flipped.
+ doflip = false;
+
+ if (segmentflaws)
+ {
+ // Does the new vertex encroach upon this subsegment?
+ if (quality.CheckSeg4Encroach(ref checksubseg) > 0)
+ {
+ success = InsertVertexResult.Encroaching;
+ }
+ }
+ }
+ }
+
+ if (doflip)
+ {
+ // Check if the edge is a boundary edge.
+ horiz.Sym(ref top);
+ if (top.triangle == dummytri)
+ {
+ // The edge is a boundary edge and cannot be flipped.
+ doflip = false;
+ }
+ else
+ {
+ // Find the vertex on the other side of the edge.
+ farvertex = top.Apex();
+ // In the incremental Delaunay triangulation algorithm, any of
+ // 'leftvertex', 'rightvertex', and 'farvertex' could be vertices
+ // of the triangular bounding box. These vertices must be
+ // treated as if they are infinitely distant, even though their
+ // "coordinates" are not.
+ if ((leftvertex == infvertex1) || (leftvertex == infvertex2) ||
+ (leftvertex == infvertex3))
+ {
+ // 'leftvertex' is infinitely distant. Check the convexity of
+ // the boundary of the triangulation. 'farvertex' might be
+ // infinite as well, but trust me, this same condition should
+ // be applied.
+ doflip = Primitives.CounterClockwise(newvertex.pt, rightvertex.pt, farvertex.pt) > 0.0;
+ }
+ else if ((rightvertex == infvertex1) ||
+ (rightvertex == infvertex2) ||
+ (rightvertex == infvertex3))
+ {
+ // 'rightvertex' is infinitely distant. Check the convexity of
+ // the boundary of the triangulation. 'farvertex' might be
+ // infinite as well, but trust me, this same condition should
+ // be applied.
+ doflip = Primitives.CounterClockwise(farvertex.pt, leftvertex.pt, newvertex.pt) > 0.0;
+ }
+ else if ((farvertex == infvertex1) ||
+ (farvertex == infvertex2) ||
+ (farvertex == infvertex3))
+ {
+ // 'farvertex' is infinitely distant and cannot be inside
+ // the circumcircle of the triangle 'horiz'.
+ doflip = false;
+ }
+ else
+ {
+ // Test whether the edge is locally Delaunay.
+ doflip = Primitives.InCircle(leftvertex.pt, newvertex.pt, rightvertex.pt, farvertex.pt) > 0.0;
+ }
+ if (doflip)
+ {
+ // We made it! Flip the edge 'horiz' by rotating its containing
+ // quadrilateral (the two triangles adjacent to 'horiz').
+ // Identify the casing of the quadrilateral.
+ top.Lprev(ref topleft);
+ topleft.Sym(ref toplcasing);
+ top.Lnext(ref topright);
+ topright.Sym(ref toprcasing);
+ horiz.Lnext(ref botleft);
+ botleft.Sym(ref botlcasing);
+ horiz.Lprev(ref botright);
+ botright.Sym(ref botrcasing);
+ // Rotate the quadrilateral one-quarter turn counterclockwise.
+ topleft.Bond(ref botlcasing);
+ botleft.Bond(ref botrcasing);
+ botright.Bond(ref toprcasing);
+ topright.Bond(ref toplcasing);
+ if (checksegments)
+ {
+ // Check for subsegments and rebond them to the quadrilateral.
+ topleft.SegPivot(ref toplsubseg);
+ botleft.SegPivot(ref botlsubseg);
+ botright.SegPivot(ref botrsubseg);
+ topright.SegPivot(ref toprsubseg);
+ if (toplsubseg.ss == dummysub)
+ {
+ topright.SegDissolve();
+ }
+ else
+ {
+ topright.SegBond(ref toplsubseg);
+ }
+ if (botlsubseg.ss == dummysub)
+ {
+ topleft.SegDissolve();
+ }
+ else
+ {
+ topleft.SegBond(ref botlsubseg);
+ }
+ if (botrsubseg.ss == dummysub)
+ {
+ botleft.SegDissolve();
+ }
+ else
+ {
+ botleft.SegBond(ref botrsubseg);
+ }
+ if (toprsubseg.ss == dummysub)
+ {
+ botright.SegDissolve();
+ }
+ else
+ {
+ botright.SegBond(ref toprsubseg);
+ }
+ }
+ // New vertex assignments for the rotated quadrilateral.
+ horiz.SetOrg(farvertex);
+ horiz.SetDest(newvertex);
+ horiz.SetApex(rightvertex);
+ top.SetOrg(newvertex);
+ top.SetDest(farvertex);
+ top.SetApex(leftvertex);
+
+ for (i = 0; i < eextras; i++)
+ {
+ // Take the average of the two triangles' attributes.
+ attrib = 0.5 * (top.triangle.attributes[i] + horiz.triangle.attributes[i]);
+ top.triangle.attributes[i] = attrib;
+ horiz.triangle.attributes[i] = attrib;
+ }
+
+ if (Behavior.VarArea)
+ {
+ if ((top.triangle.area <= 0.0) || (horiz.triangle.area <= 0.0))
+ {
+ area = -1.0;
+ }
+ else
+ {
+ // Take the average of the two triangles' area constraints.
+ // This prevents small area constraints from migrating a
+ // long, long way from their original location due to flips.
+ area = 0.5 * (top.triangle.area + horiz.triangle.area);
+ }
+
+ top.triangle.area = area;
+ horiz.triangle.area = area;
+ }
+
+ if (checkquality)
+ {
+ newflip = new FlipStacker();
+ newflip.flippedtri = horiz;
+ newflip.prevflip = lastflip;
+ lastflip = newflip;
+
+ flipstackers.Add(newflip);
+ }
+
+ // On the next iterations, consider the two edges that were
+ // exposed (this is, are now visible to the newly inserted
+ // vertex) by the edge flip.
+ horiz.LprevSelf();
+ leftvertex = farvertex;
+ }
+ }
+ }
+ if (!doflip)
+ {
+ // The handle 'horiz' is accepted as locally Delaunay.
+ if (triflaws)
+ {
+ // Check the triangle 'horiz' for quality.
+ quality.TestTriangle(ref horiz);
+ }
+
+ // Look for the next edge around the newly inserted vertex.
+ horiz.LnextSelf();
+ horiz.Sym(ref testtri);
+ // Check for finishing a complete revolution about the new vertex, or
+ // falling outside of the triangulation. The latter will happen
+ // when a vertex is inserted at a boundary.
+ if ((leftvertex == first) || (testtri.triangle == dummytri))
+ {
+ // We're done. Return a triangle whose origin is the new vertex.
+ horiz.Lnext(ref searchtri);
+ horiz.Lnext(ref recenttri);
+ return success;
+ }
+ // Finish finding the next edge around the newly inserted vertex.
+ testtri.Lnext(ref horiz);
+ rightvertex = leftvertex;
+ leftvertex = horiz.Dest();
+ }
+ }
+ }
+
+ ///
+ /// Create a new subsegment and inserts it between two triangles. Its
+ /// vertices are properly initialized.
+ ///
+ /// The new subsegment is inserted at the edge
+ /// described by this handle.
+ /// The marker 'subsegmark' is applied to the
+ /// subsegment and, if appropriate, its vertices.
+ internal void InsertSubseg(ref Otri tri, int subsegmark)
+ {
+ Otri oppotri = default(Otri);
+ Osub newsubseg = default(Osub);
+ Vertex triorg, tridest;
+
+ triorg = tri.Org();
+ tridest = tri.Dest();
+ // Mark vertices if possible.
+ if (triorg.mark == 0)
+ {
+ triorg.mark = subsegmark;
+ }
+ if (tridest.mark == 0)
+ {
+ tridest.mark = subsegmark;
+ }
+ // Check if there's already a subsegment here.
+ tri.SegPivot(ref newsubseg);
+ if (newsubseg.ss == dummysub)
+ {
+ // Make new subsegment and initialize its vertices.
+ MakeSubseg(ref newsubseg);
+ newsubseg.SetOrg(tridest);
+ newsubseg.SetDest(triorg);
+ newsubseg.SetSegOrg(tridest);
+ newsubseg.SetSegDest(triorg);
+ // Bond new subsegment to the two triangles it is sandwiched between.
+ // Note that the facing triangle 'oppotri' might be equal to
+ // 'dummytri' (outer space), but the new subsegment is bonded to it
+ // all the same.
+ tri.SegBond(ref newsubseg);
+ tri.Sym(ref oppotri);
+ newsubseg.SymSelf();
+ oppotri.SegBond(ref newsubseg);
+ newsubseg.ss.boundary = subsegmark;
+ }
+ else
+ {
+ if (newsubseg.ss.boundary == 0)
+ {
+ newsubseg.ss.boundary = subsegmark;
+ }
+ }
+ }
+
+ ///
+ /// Transform two triangles to two different triangles by flipping an edge
+ /// counterclockwise within a quadrilateral.
+ ///
+ ///
+ /// Imagine the original triangles, abc and bad, oriented so that the
+ /// shared edge ab lies in a horizontal plane, with the vertex b on the left
+ /// and the vertex a on the right. The vertex c lies below the edge, and
+ /// the vertex d lies above the edge. The 'flipedge' handle holds the edge
+ /// ab of triangle abc, and is directed left, from vertex a to vertex b.
+ ///
+ /// The triangles abc and bad are deleted and replaced by the triangles cdb
+ /// and dca. The triangles that represent abc and bad are NOT deallocated;
+ /// they are reused for dca and cdb, respectively. Hence, any handles that
+ /// may have held the original triangles are still valid, although not
+ /// directed as they were before.
+ ///
+ /// Upon completion of this routine, the 'flipedge' handle holds the edge
+ /// dc of triangle dca, and is directed down, from vertex d to vertex c.
+ /// (Hence, the two triangles have rotated counterclockwise.)
+ ///
+ /// WARNING: This transformation is geometrically valid only if the
+ /// quadrilateral adbc is convex. Furthermore, this transformation is
+ /// valid only if there is not a subsegment between the triangles abc and
+ /// bad. This routine does not check either of these preconditions, and
+ /// it is the responsibility of the calling routine to ensure that they are
+ /// met. If they are not, the streets shall be filled with wailing and
+ /// gnashing of teeth.
+ ///
+ /// Terminology
+ ///
+ /// A "local transformation" replaces a small set of triangles with another
+ /// set of triangles. This may or may not involve inserting or deleting a
+ /// vertex.
+ ///
+ /// The term "casing" is used to describe the set of triangles that are
+ /// attached to the triangles being transformed, but are not transformed
+ /// themselves. Think of the casing as a fixed hollow structure inside
+ /// which all the action happens. A "casing" is only defined relative to
+ /// a single transformation; each occurrence of a transformation will
+ /// involve a different casing.
+ ///
+ internal void Flip(ref Otri flipedge)
+ {
+ Otri botleft = default(Otri), botright = default(Otri);
+ Otri topleft = default(Otri), topright = default(Otri);
+ Otri top = default(Otri);
+ Otri botlcasing = default(Otri), botrcasing = default(Otri);
+ Otri toplcasing = default(Otri), toprcasing = default(Otri);
+ Osub botlsubseg = default(Osub), botrsubseg = default(Osub);
+ Osub toplsubseg = default(Osub), toprsubseg = default(Osub);
+ Vertex leftvertex, rightvertex, botvertex;
+ Vertex farvertex;
+
+ // Identify the vertices of the quadrilateral.
+ rightvertex = flipedge.Org();
+ leftvertex = flipedge.Dest();
+ botvertex = flipedge.Apex();
+ flipedge.Sym(ref top);
+
+ farvertex = top.Apex();
+
+ // Identify the casing of the quadrilateral.
+ top.Lprev(ref topleft);
+ topleft.Sym(ref toplcasing);
+ top.Lnext(ref topright);
+ topright.Sym(ref toprcasing);
+ flipedge.Lnext(ref botleft);
+ botleft.Sym(ref botlcasing);
+ flipedge.Lprev(ref botright);
+ botright.Sym(ref botrcasing);
+ // Rotate the quadrilateral one-quarter turn counterclockwise.
+ topleft.Bond(ref botlcasing);
+ botleft.Bond(ref botrcasing);
+ botright.Bond(ref toprcasing);
+ topright.Bond(ref toplcasing);
+
+ if (checksegments)
+ {
+ // Check for subsegments and rebond them to the quadrilateral.
+ topleft.SegPivot(ref toplsubseg);
+ botleft.SegPivot(ref botlsubseg);
+ botright.SegPivot(ref botrsubseg);
+ topright.SegPivot(ref toprsubseg);
+ if (toplsubseg.ss == Mesh.dummysub)
+ {
+ topright.SegDissolve();
+ }
+ else
+ {
+ topright.SegBond(ref toplsubseg);
+ }
+ if (botlsubseg.ss == Mesh.dummysub)
+ {
+ topleft.SegDissolve();
+ }
+ else
+ {
+ topleft.SegBond(ref botlsubseg);
+ }
+ if (botrsubseg.ss == Mesh.dummysub)
+ {
+ botleft.SegDissolve();
+ }
+ else
+ {
+ botleft.SegBond(ref botrsubseg);
+ }
+ if (toprsubseg.ss == Mesh.dummysub)
+ {
+ botright.SegDissolve();
+ }
+ else
+ {
+ botright.SegBond(ref toprsubseg);
+ }
+ }
+
+ // New vertex assignments for the rotated quadrilateral.
+ flipedge.SetOrg(farvertex);
+ flipedge.SetDest(botvertex);
+ flipedge.SetApex(rightvertex);
+ top.SetOrg(botvertex);
+ top.SetDest(farvertex);
+ top.SetApex(leftvertex);
+ }
+
+ ///
+ /// Transform two triangles to two different triangles by flipping an edge
+ /// clockwise within a quadrilateral. Reverses the flip() operation so that
+ /// the data structures representing the triangles are back where they were
+ /// before the flip().
+ ///
+ ///
+ ///
+ /// Imagine the original triangles, abc and bad, oriented so that the
+ /// shared edge ab lies in a horizontal plane, with the vertex b on the left
+ /// and the vertex a on the right. The vertex c lies below the edge, and
+ /// the vertex d lies above the edge. The 'flipedge' handle holds the edge
+ /// ab of triangle abc, and is directed left, from vertex a to vertex b.
+ ///
+ /// The triangles abc and bad are deleted and replaced by the triangles cdb
+ /// and dca. The triangles that represent abc and bad are NOT deallocated;
+ /// they are reused for cdb and dca, respectively. Hence, any handles that
+ /// may have held the original triangles are still valid, although not
+ /// directed as they were before.
+ ///
+ /// Upon completion of this routine, the 'flipedge' handle holds the edge
+ /// cd of triangle cdb, and is directed up, from vertex c to vertex d.
+ /// (Hence, the two triangles have rotated clockwise.)
+ ///
+ /// WARNING: This transformation is geometrically valid only if the
+ /// quadrilateral adbc is convex. Furthermore, this transformation is
+ /// valid only if there is not a subsegment between the triangles abc and
+ /// bad. This routine does not check either of these preconditions, and
+ /// it is the responsibility of the calling routine to ensure that they are
+ /// met. If they are not, the streets shall be filled with wailing and
+ /// gnashing of teeth.
+ ///
+ internal void Unflip(ref Otri flipedge)
+ {
+ Otri botleft = default(Otri), botright = default(Otri);
+ Otri topleft = default(Otri), topright = default(Otri);
+ Otri top = default(Otri);
+ Otri botlcasing = default(Otri), botrcasing = default(Otri);
+ Otri toplcasing = default(Otri), toprcasing = default(Otri);
+ Osub botlsubseg = default(Osub), botrsubseg = default(Osub);
+ Osub toplsubseg = default(Osub), toprsubseg = default(Osub);
+ Vertex leftvertex, rightvertex, botvertex;
+ Vertex farvertex;
+
+ // Identify the vertices of the quadrilateral.
+ rightvertex = flipedge.Org();
+ leftvertex = flipedge.Dest();
+ botvertex = flipedge.Apex();
+ flipedge.Sym(ref top);
+
+ farvertex = top.Apex();
+
+ // Identify the casing of the quadrilateral.
+ top.Lprev(ref topleft);
+ topleft.Sym(ref toplcasing);
+ top.Lnext(ref topright);
+ topright.Sym(ref toprcasing);
+ flipedge.Lnext(ref botleft);
+ botleft.Sym(ref botlcasing);
+ flipedge.Lprev(ref botright);
+ botright.Sym(ref botrcasing);
+ // Rotate the quadrilateral one-quarter turn clockwise.
+ topleft.Bond(ref toprcasing);
+ botleft.Bond(ref toplcasing);
+ botright.Bond(ref botlcasing);
+ topright.Bond(ref botrcasing);
+
+ if (checksegments)
+ {
+ // Check for subsegments and rebond them to the quadrilateral.
+ topleft.SegPivot(ref toplsubseg);
+ botleft.SegPivot(ref botlsubseg);
+ botright.SegPivot(ref botrsubseg);
+ topright.SegPivot(ref toprsubseg);
+ if (toplsubseg.ss == Mesh.dummysub)
+ {
+ botleft.SegDissolve();
+ }
+ else
+ {
+ botleft.SegBond(ref toplsubseg);
+ }
+ if (botlsubseg.ss == Mesh.dummysub)
+ {
+ botright.SegDissolve();
+ }
+ else
+ {
+ botright.SegBond(ref botlsubseg);
+ }
+ if (botrsubseg.ss == Mesh.dummysub)
+ {
+ topright.SegDissolve();
+ }
+ else
+ {
+ topright.SegBond(ref botrsubseg);
+ }
+ if (toprsubseg.ss == Mesh.dummysub)
+ {
+ topleft.SegDissolve();
+ }
+ else
+ {
+ topleft.SegBond(ref toprsubseg);
+ }
+ }
+
+ // New vertex assignments for the rotated quadrilateral.
+ flipedge.SetOrg(botvertex);
+ flipedge.SetDest(farvertex);
+ flipedge.SetApex(leftvertex);
+ top.SetOrg(farvertex);
+ top.SetDest(botvertex);
+ top.SetApex(rightvertex);
+ }
+
+ ///
+ /// Find the Delaunay triangulation of a polygon that has a certain "nice" shape.
+ /// This includes the polygons that result from deletion of a vertex or insertion
+ /// of a segment.
+ ///
+ /// The primary edge of the first triangle.
+ /// The primary edge of the last triangle.
+ /// The number of sides of the polygon, including its
+ /// base.
+ /// A flag, wether to perform the last flip.
+ /// A flag that determines whether the new triangles should
+ /// be tested for quality, and enqueued if they are bad.
+ ///
+ // This is a conceptually difficult routine. The starting assumption is
+ // that we have a polygon with n sides. n - 1 of these sides are currently
+ // represented as edges in the mesh. One side, called the "base", need not
+ // be.
+ //
+ // Inside the polygon is a structure I call a "fan", consisting of n - 1
+ // triangles that share a common origin. For each of these triangles, the
+ // edge opposite the origin is one of the sides of the polygon. The
+ // primary edge of each triangle is the edge directed from the origin to
+ // the destination; note that this is not the same edge that is a side of
+ // the polygon. 'firstedge' is the primary edge of the first triangle.
+ // From there, the triangles follow in counterclockwise order about the
+ // polygon, until 'lastedge', the primary edge of the last triangle.
+ // 'firstedge' and 'lastedge' are probably connected to other triangles
+ // beyond the extremes of the fan, but their identity is not important, as
+ // long as the fan remains connected to them.
+ //
+ // Imagine the polygon oriented so that its base is at the bottom. This
+ // puts 'firstedge' on the far right, and 'lastedge' on the far left.
+ // The right vertex of the base is the destination of 'firstedge', and the
+ // left vertex of the base is the apex of 'lastedge'.
+ //
+ // The challenge now is to find the right sequence of edge flips to
+ // transform the fan into a Delaunay triangulation of the polygon. Each
+ // edge flip effectively removes one triangle from the fan, committing it
+ // to the polygon. The resulting polygon has one fewer edge. If 'doflip'
+ // is set, the final flip will be performed, resulting in a fan of one
+ // (useless?) triangle. If 'doflip' is not set, the final flip is not
+ // performed, resulting in a fan of two triangles, and an unfinished
+ // triangular polygon that is not yet filled out with a single triangle.
+ // On completion of the routine, 'lastedge' is the last remaining triangle,
+ // or the leftmost of the last two.
+ //
+ // Although the flips are performed in the order described above, the
+ // decisions about what flips to perform are made in precisely the reverse
+ // order. The recursive triangulatepolygon() procedure makes a decision,
+ // uses up to two recursive calls to triangulate the "subproblems"
+ // (polygons with fewer edges), and then performs an edge flip.
+ //
+ // The "decision" it makes is which vertex of the polygon should be
+ // connected to the base. This decision is made by testing every possible
+ // vertex. Once the best vertex is found, the two edges that connect this
+ // vertex to the base become the bases for two smaller polygons. These
+ // are triangulated recursively. Unfortunately, this approach can take
+ // O(n^2) time not only in the worst case, but in many common cases. It's
+ // rarely a big deal for vertex deletion, where n is rarely larger than
+ // ten, but it could be a big deal for segment insertion, especially if
+ // there's a lot of long segments that each cut many triangles. I ought to
+ // code a faster algorithm some day.
+ ///
+ private void TriangulatePolygon(Otri firstedge, Otri lastedge,
+ int edgecount, bool doflip, bool triflaws)
+ {
+ Otri testtri = default(Otri);
+ Otri besttri = default(Otri);
+ Otri tempedge = default(Otri);
+ Vertex leftbasevertex, rightbasevertex;
+ Vertex testvertex;
+ Vertex bestvertex;
+ int bestnumber;
+ int i;
+
+ // Identify the base vertices.
+ leftbasevertex = lastedge.Apex();
+ rightbasevertex = firstedge.Dest();
+
+ // Find the best vertex to connect the base to.
+ firstedge.Onext(ref besttri);
+ bestvertex = besttri.Dest();
+ besttri.Copy(ref testtri);
+ bestnumber = 1;
+ for (i = 2; i <= edgecount - 2; i++)
+ {
+ testtri.OnextSelf();
+ testvertex = testtri.Dest();
+ // Is this a better vertex?
+ if (Primitives.InCircle(leftbasevertex.pt, rightbasevertex.pt, bestvertex.pt, testvertex.pt) > 0.0)
+ {
+ testtri.Copy(ref besttri);
+ bestvertex = testvertex;
+ bestnumber = i;
+ }
+ }
+
+ if (bestnumber > 1)
+ {
+ // Recursively triangulate the smaller polygon on the right.
+ besttri.Oprev(ref tempedge);
+ TriangulatePolygon(firstedge, tempedge, bestnumber + 1, true, triflaws);
+ }
+
+ if (bestnumber < edgecount - 2)
+ {
+ // Recursively triangulate the smaller polygon on the left.
+ besttri.Sym(ref tempedge);
+ TriangulatePolygon(besttri, lastedge, edgecount - bestnumber, true, triflaws);
+ // Find 'besttri' again; it may have been lost to edge flips.
+ tempedge.Sym(ref besttri);
+ }
+
+ if (doflip)
+ {
+ // Do one final edge flip.
+ Flip(ref besttri);
+ if (triflaws)
+ {
+ // Check the quality of the newly committed triangle.
+ besttri.Sym(ref testtri);
+ quality.TestTriangle(ref testtri);
+ }
+ }
+ // Return the base triangle.
+ besttri.Copy(ref lastedge);
+ }
+
+ ///
+ /// Delete a vertex from a Delaunay triangulation, ensuring that the
+ /// triangulation remains Delaunay.
+ ///
+ ///
+ /// The origin of 'deltri' is deleted. The union of the triangles adjacent
+ /// to this vertex is a polygon, for which the Delaunay triangulation is
+ /// found. Two triangles are removed from the mesh.
+ ///
+ /// Only interior vertices that do not lie on segments or boundaries may be
+ /// deleted.
+ ///
+ internal void DeleteVertex(ref Otri deltri)
+ {
+ Otri countingtri = default(Otri);
+ Otri firstedge = default(Otri), lastedge = default(Otri);
+ Otri deltriright = default(Otri);
+ Otri lefttri = default(Otri), righttri = default(Otri);
+ Otri leftcasing = default(Otri), rightcasing = default(Otri);
+ Osub leftsubseg = default(Osub), rightsubseg = default(Osub);
+ Vertex delvertex;
+ Vertex neworg;
+ int edgecount;
+
+ delvertex = deltri.Org();
+
+ VertexDealloc(delvertex);
+
+ // Count the degree of the vertex being deleted.
+ deltri.Onext(ref countingtri);
+ edgecount = 1;
+ while (!deltri.Equal(countingtri))
+ {
+ edgecount++;
+ countingtri.OnextSelf();
+ }
+
+ if (edgecount > 3)
+ {
+ // Triangulate the polygon defined by the union of all triangles
+ // adjacent to the vertex being deleted. Check the quality of
+ // the resulting triangles.
+ deltri.Onext(ref firstedge);
+ deltri.Oprev(ref lastedge);
+ TriangulatePolygon(firstedge, lastedge, edgecount, false, Behavior.NoBisect == 0);
+ }
+ // Splice out two triangles.
+ deltri.Lprev(ref deltriright);
+ deltri.Dnext(ref lefttri);
+ lefttri.Sym(ref leftcasing);
+ deltriright.Oprev(ref righttri);
+ righttri.Sym(ref rightcasing);
+ deltri.Bond(ref leftcasing);
+ deltriright.Bond(ref rightcasing);
+ lefttri.SegPivot(ref leftsubseg);
+ if (leftsubseg.ss != Mesh.dummysub)
+ {
+ deltri.SegBond(ref leftsubseg);
+ }
+ righttri.SegPivot(ref rightsubseg);
+ if (rightsubseg.ss != Mesh.dummysub)
+ {
+ deltriright.SegBond(ref rightsubseg);
+ }
+
+ // Set the new origin of 'deltri' and check its quality.
+ neworg = lefttri.Org();
+ deltri.SetOrg(neworg);
+ if (Behavior.NoBisect == 0)
+ {
+ quality.TestTriangle(ref deltri);
+ }
+
+ // Delete the two spliced-out triangles.
+ TriangleDealloc(lefttri.triangle);
+ TriangleDealloc(righttri.triangle);
+ }
+
+ ///
+ /// Undo the most recent vertex insertion.
+ ///
+ ///
+ /// Walks through the list of transformations (flips and a vertex insertion)
+ /// in the reverse of the order in which they were done, and undoes them.
+ /// The inserted vertex is removed from the triangulation and deallocated.
+ /// Two triangles (possibly just one) are also deallocated.
+ ///
+ internal void UndoVertex()
+ {
+ Otri fliptri = default(Otri);
+ Otri botleft = default(Otri), botright = default(Otri), topright = default(Otri);
+ Otri botlcasing = default(Otri), botrcasing = default(Otri), toprcasing = default(Otri);
+ Otri gluetri = default(Otri);
+ Osub botlsubseg = default(Osub), botrsubseg = default(Osub), toprsubseg = default(Osub);
+ Vertex botvertex, rightvertex;
+
+ // Walk through the list of transformations (flips and a vertex insertion)
+ // in the reverse of the order in which they were done, and undo them.
+ while (lastflip != null)
+ {
+ // Find a triangle involved in the last unreversed transformation.
+ fliptri = lastflip.flippedtri;
+
+ // We are reversing one of three transformations: a trisection of one
+ // triangle into three (by inserting a vertex in the triangle), a
+ // bisection of two triangles into four (by inserting a vertex in an
+ // edge), or an edge flip.
+ if (lastflip.prevflip == null)
+ {
+ // Restore a triangle that was split into three triangles,
+ // so it is again one triangle.
+ fliptri.Dprev(ref botleft);
+ botleft.LnextSelf();
+ fliptri.Onext(ref botright);
+ botright.LprevSelf();
+ botleft.Sym(ref botlcasing);
+ botright.Sym(ref botrcasing);
+ botvertex = botleft.Dest();
+
+ fliptri.SetApex(botvertex);
+ fliptri.LnextSelf();
+ fliptri.Bond(ref botlcasing);
+ botleft.SegPivot(ref botlsubseg);
+ fliptri.SegBond(ref botlsubseg);
+ fliptri.LnextSelf();
+ fliptri.Bond(ref botrcasing);
+ botright.SegPivot(ref botrsubseg);
+ fliptri.SegBond(ref botrsubseg);
+
+ // Delete the two spliced-out triangles.
+ TriangleDealloc(botleft.triangle);
+ TriangleDealloc(botright.triangle);
+ }
+ else if (lastflip.prevflip == dummyflip)
+ {
+ // Restore two triangles that were split into four triangles,
+ // so they are again two triangles.
+ fliptri.Lprev(ref gluetri);
+ gluetri.Sym(ref botright);
+ botright.LnextSelf();
+ botright.Sym(ref botrcasing);
+ rightvertex = botright.Dest();
+
+ fliptri.SetOrg(rightvertex);
+ gluetri.Bond(ref botrcasing);
+ botright.SegPivot(ref botrsubseg);
+ gluetri.SegBond(ref botrsubseg);
+
+ // Delete the spliced-out triangle.
+ TriangleDealloc(botright.triangle);
+
+ fliptri.Sym(ref gluetri);
+ if (gluetri.triangle != Mesh.dummytri)
+ {
+ gluetri.LnextSelf();
+ gluetri.Dnext(ref topright);
+ topright.Sym(ref toprcasing);
+
+ gluetri.SetOrg(rightvertex);
+ gluetri.Bond(ref toprcasing);
+ topright.SegPivot(ref toprsubseg);
+ gluetri.SegBond(ref toprsubseg);
+
+ // Delete the spliced-out triangle.
+ TriangleDealloc(topright.triangle);
+ }
+
+ // This is the end of the list, sneakily encoded.
+ lastflip.prevflip = null;
+ }
+ else
+ {
+ // Undo an edge flip.
+ Unflip(ref fliptri);
+ }
+
+ // Go on and process the next transformation.
+ lastflip = lastflip.prevflip;
+ }
+ }
+
+ #endregion
+
+ #region Location
+
+ ///
+ /// Construct a mapping from vertices to triangles to improve the speed of
+ /// point location for segment insertion.
+ ///
+ ///
+ /// Traverses all the triangles, and provides each corner of each triangle
+ /// with a pointer to that triangle. Of course, pointers will be
+ /// overwritten by other pointers because (almost) each vertex is a corner
+ /// of several triangles, but in the end every vertex will point to some
+ /// triangle that contains it.
+ ///
+ void MakeVertexMap()
+ {
+ Otri tri = default(Otri);
+ Vertex triorg;
+
+ foreach (var t in this.triangles.Values)
+ {
+ tri.triangle = t;
+ // Check all three vertices of the triangle.
+ for (tri.orient = 0; tri.orient < 3; tri.orient++)
+ {
+ triorg = tri.Org();
+ triorg.tri = tri;
+ }
+ }
+ }
+
+ ///
+ /// Find a triangle or edge containing a given point.
+ ///
+ /// The point to locate.
+ /// The triangle to start the search at.
+ /// If 'stopatsubsegment' is set, the search
+ /// will stop if it tries to walk through a subsegment, and will return OUTSIDE.
+ /// Location information.
+ ///
+ /// Begins its search from 'searchtri'. It is important that 'searchtri'
+ /// be a handle with the property that 'searchpoint' is strictly to the left
+ /// of the edge denoted by 'searchtri', or is collinear with that edge and
+ /// does not intersect that edge. (In particular, 'searchpoint' should not
+ /// be the origin or destination of that edge.)
+ ///
+ /// These conditions are imposed because preciselocate() is normally used in
+ /// one of two situations:
+ ///
+ /// (1) To try to find the location to insert a new point. Normally, we
+ /// know an edge that the point is strictly to the left of. In the
+ /// incremental Delaunay algorithm, that edge is a bounding box edge.
+ /// In Ruppert's Delaunay refinement algorithm for quality meshing,
+ /// that edge is the shortest edge of the triangle whose circumcenter
+ /// is being inserted.
+ ///
+ /// (2) To try to find an existing point. In this case, any edge on the
+ /// convex hull is a good starting edge. You must screen out the
+ /// possibility that the vertex sought is an endpoint of the starting
+ /// edge before you call preciselocate().
+ ///
+ /// On completion, 'searchtri' is a triangle that contains 'searchpoint'.
+ ///
+ /// This implementation differs from that given by Guibas and Stolfi. It
+ /// walks from triangle to triangle, crossing an edge only if 'searchpoint'
+ /// is on the other side of the line containing that edge. After entering
+ /// a triangle, there are two edges by which one can leave that triangle.
+ /// If both edges are valid ('searchpoint' is on the other side of both
+ /// edges), one of the two is chosen by drawing a line perpendicular to
+ /// the entry edge (whose endpoints are 'forg' and 'fdest') passing through
+ /// 'fapex'. Depending on which side of this perpendicular 'searchpoint'
+ /// falls on, an exit edge is chosen.
+ ///
+ /// This implementation is empirically faster than the Guibas and Stolfi
+ /// point location routine (which I originally used), which tends to spiral
+ /// in toward its target.
+ ///
+ /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri'
+ /// is a handle whose origin is the existing vertex.
+ ///
+ /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a
+ /// handle whose primary edge is the edge on which the point lies.
+ ///
+ /// Returns INTRIANGLE if the point lies strictly within a triangle.
+ /// 'searchtri' is a handle on the triangle that contains the point.
+ ///
+ /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a
+ /// handle whose primary edge the point is to the right of. This might
+ /// occur when the circumcenter of a triangle falls just slightly outside
+ /// the mesh due to floating-point roundoff error. It also occurs when
+ /// seeking a hole or region point that a foolish user has placed outside
+ /// the mesh.
+ ///
+ /// WARNING: This routine is designed for convex triangulations, and will
+ /// not generally work after the holes and concavities have been carved.
+ /// However, it can still be used to find the circumcenter of a triangle, as
+ /// long as the search is begun from the triangle in question.
+ internal LocateResult PreciseLocate(Point2 searchpoint, ref Otri searchtri,
+ bool stopatsubsegment)
+ {
+ Otri backtracktri = default(Otri);
+ Osub checkedge = default(Osub);
+ Vertex forg, fdest, fapex;
+ double orgorient, destorient;
+ bool moveleft;
+
+ // Where are we?
+ forg = searchtri.Org();
+ fdest = searchtri.Dest();
+ fapex = searchtri.Apex();
+ while (true)
+ {
+ // Check whether the apex is the point we seek.
+ if ((fapex.pt.X == searchpoint.X) && (fapex.pt.Y == searchpoint.Y))
+ {
+ searchtri.LprevSelf();
+ return LocateResult.OnVertex;
+ }
+ // Does the point lie on the other side of the line defined by the
+ // triangle edge opposite the triangle's destination?
+ destorient = Primitives.CounterClockwise(forg.pt, fapex.pt, searchpoint);
+ // Does the point lie on the other side of the line defined by the
+ // triangle edge opposite the triangle's origin?
+ orgorient = Primitives.CounterClockwise(fapex.pt, fdest.pt, searchpoint);
+ if (destorient > 0.0)
+ {
+ if (orgorient > 0.0)
+ {
+ // Move left if the inner product of (fapex - searchpoint) and
+ // (fdest - forg) is positive. This is equivalent to drawing
+ // a line perpendicular to the line (forg, fdest) and passing
+ // through 'fapex', and determining which side of this line
+ // 'searchpoint' falls on.
+ moveleft = (fapex.pt.X - searchpoint.X) * (fdest.pt.X - forg.pt.X) +
+ (fapex.pt.Y - searchpoint.Y) * (fdest.pt.Y - forg.pt.Y) > 0.0;
+ }
+ else
+ {
+ moveleft = true;
+ }
+ }
+ else
+ {
+ if (orgorient > 0.0)
+ {
+ moveleft = false;
+ }
+ else
+ {
+ // The point we seek must be on the boundary of or inside this
+ // triangle.
+ if (destorient == 0.0)
+ {
+ searchtri.LprevSelf();
+ return LocateResult.OnEdge;
+ }
+ if (orgorient == 0.0)
+ {
+ searchtri.LnextSelf();
+ return LocateResult.OnEdge;
+ }
+ return LocateResult.InTriangle;
+ }
+ }
+
+ // Move to another triangle. Leave a trace 'backtracktri' in case
+ // floating-point roundoff or some such bogey causes us to walk
+ // off a boundary of the triangulation.
+ if (moveleft)
+ {
+ searchtri.Lprev(ref backtracktri);
+ fdest = fapex;
+ }
+ else
+ {
+ searchtri.Lnext(ref backtracktri);
+ forg = fapex;
+ }
+ backtracktri.Sym(ref searchtri);
+
+ if (checksegments && stopatsubsegment)
+ {
+ // Check for walking through a subsegment.
+ backtracktri.SegPivot(ref checkedge);
+ if (checkedge.ss != dummysub)
+ {
+ // Go back to the last triangle.
+ backtracktri.Copy(ref searchtri);
+ return LocateResult.Outside;
+ }
+ }
+ // Check for walking right out of the triangulation.
+ if (searchtri.triangle == dummytri)
+ {
+ // Go back to the last triangle.
+ backtracktri.Copy(ref searchtri);
+ return LocateResult.Outside;
+ }
+
+ fapex = searchtri.Apex();
+ }
+ }
+
+ ///
+ /// Find a triangle or edge containing a given point.
+ ///
+ /// The point to locate.
+ /// The triangle to start the search at.
+ /// Location information.
+ ///
+ /// Searching begins from one of: the input 'searchtri', a recently
+ /// encountered triangle 'recenttri', or from a triangle chosen from a
+ /// random sample. The choice is made by determining which triangle's
+ /// origin is closest to the point we are searching for. Normally,
+ /// 'searchtri' should be a handle on the convex hull of the triangulation.
+ ///
+ /// Details on the random sampling method can be found in the Mucke, Saias,
+ /// and Zhu paper cited in the header of this code.
+ ///
+ /// On completion, 'searchtri' is a triangle that contains 'searchpoint'.
+ ///
+ /// Returns ONVERTEX if the point lies on an existing vertex. 'searchtri'
+ /// is a handle whose origin is the existing vertex.
+ ///
+ /// Returns ONEDGE if the point lies on a mesh edge. 'searchtri' is a
+ /// handle whose primary edge is the edge on which the point lies.
+ ///
+ /// Returns INTRIANGLE if the point lies strictly within a triangle.
+ /// 'searchtri' is a handle on the triangle that contains the point.
+ ///
+ /// Returns OUTSIDE if the point lies outside the mesh. 'searchtri' is a
+ /// handle whose primary edge the point is to the right of. This might
+ /// occur when the circumcenter of a triangle falls just slightly outside
+ /// the mesh due to floating-point roundoff error. It also occurs when
+ /// seeking a hole or region point that a foolish user has placed outside
+ /// the mesh.
+ ///
+ /// WARNING: This routine is designed for convex triangulations, and will
+ /// not generally work after the holes and concavities have been carved.
+ ///
+ internal LocateResult Locate(Point2 searchpoint, ref Otri searchtri)
+ {
+ Otri sampletri = default(Otri);
+ Vertex torg, tdest;
+ double searchdist, dist;
+ double ahead;
+ //long samplesperblock, totalsamplesleft, samplesleft;
+ //long population, totalpopulation;
+
+ // Record the distance from the suggested starting triangle to the
+ // point we seek.
+ torg = searchtri.Org();
+ searchdist = (searchpoint.X - torg.pt.X) * (searchpoint.X - torg.pt.X) +
+ (searchpoint.Y - torg.pt.Y) * (searchpoint.Y - torg.pt.Y);
+
+ // If a recently encountered triangle has been recorded and has not been
+ // deallocated, test it as a good starting point.
+ if (recenttri.triangle != null)
+ {
+ if (!Otri.IsDead(recenttri.triangle))
+ {
+ torg = recenttri.Org();
+ if ((torg.pt.X == searchpoint.X) && (torg.pt.Y == searchpoint.Y))
+ {
+ recenttri.Copy(ref searchtri);
+ return LocateResult.OnVertex;
+ }
+ dist = (searchpoint.X - torg.pt.X) * (searchpoint.X - torg.pt.X) +
+ (searchpoint.Y - torg.pt.Y) * (searchpoint.Y - torg.pt.Y);
+ if (dist < searchdist)
+ {
+ recenttri.Copy(ref searchtri);
+ searchdist = dist;
+ }
+ }
+ }
+
+ // TODO: Improve sampling.
+ sampler.Update(this);
+ int[] samples = sampler.GetSamples();
+
+ foreach (var key in samples)
+ {
+ sampletri.triangle = this.triangles[key];
+ if (!Otri.IsDead(sampletri.triangle))
+ {
+ torg = sampletri.Org();
+ dist = (searchpoint.X - torg.pt.X) * (searchpoint.X - torg.pt.X) +
+ (searchpoint.Y - torg.pt.Y) * (searchpoint.Y - torg.pt.Y);
+ if (dist < searchdist)
+ {
+ sampletri.Copy(ref searchtri);
+ searchdist = dist;
+ }
+ }
+ }
+
+ // Where are we?
+ torg = searchtri.Org();
+ tdest = searchtri.Dest();
+ // Check the starting triangle's vertices.
+ if ((torg.pt.X == searchpoint.X) && (torg.pt.Y == searchpoint.Y))
+ {
+ return LocateResult.OnVertex;
+ }
+ if ((tdest.pt.X == searchpoint.X) && (tdest.pt.Y == searchpoint.Y))
+ {
+ searchtri.LnextSelf();
+ return LocateResult.OnVertex;
+ }
+ // Orient 'searchtri' to fit the preconditions of calling preciselocate().
+ ahead = Primitives.CounterClockwise(torg.pt, tdest.pt, searchpoint);
+ if (ahead < 0.0)
+ {
+ // Turn around so that 'searchpoint' is to the left of the
+ // edge specified by 'searchtri'.
+ searchtri.SymSelf();
+ }
+ else if (ahead == 0.0)
+ {
+ // Check if 'searchpoint' is between 'torg' and 'tdest'.
+ if (((torg.pt.X < searchpoint.X) == (searchpoint.X < tdest.pt.X)) &&
+ ((torg.pt.Y < searchpoint.Y) == (searchpoint.Y < tdest.pt.Y)))
+ {
+ return LocateResult.OnEdge;
+ }
+ }
+ return PreciseLocate(searchpoint, ref searchtri, false);
+ }
+
+ #endregion
+
+ #region Segment insertion
+
+ ///
+ /// Find the first triangle on the path from one point to another.
+ ///
+ ///
+ ///
+ ///
+ /// The return value notes whether the destination or apex of the found
+ /// triangle is collinear with the two points in question.
+ ///
+ /// Finds the triangle that intersects a line segment drawn from the
+ /// origin of 'searchtri' to the point 'searchpoint', and returns the result
+ /// in 'searchtri'. The origin of 'searchtri' does not change, even though
+ /// the triangle returned may differ from the one passed in. This routine
+ /// is used to find the direction to move in to get from one point to
+ /// another.
+ ///
+ FindDirectionResult FindDirection(ref Otri searchtri, Vertex searchpoint)
+ {
+ Otri checktri = default(Otri);
+ Vertex startvertex;
+ Vertex leftvertex, rightvertex;
+ double leftccw, rightccw;
+ bool leftflag, rightflag;
+
+ startvertex = searchtri.Org();
+ rightvertex = searchtri.Dest();
+ leftvertex = searchtri.Apex();
+ // Is 'searchpoint' to the left?
+ leftccw = Primitives.CounterClockwise(searchpoint.pt, startvertex.pt, leftvertex.pt);
+ leftflag = leftccw > 0.0;
+ // Is 'searchpoint' to the right?
+ rightccw = Primitives.CounterClockwise(startvertex.pt, searchpoint.pt, rightvertex.pt);
+ rightflag = rightccw > 0.0;
+ if (leftflag && rightflag)
+ {
+ // 'searchtri' faces directly away from 'searchpoint'. We could go left
+ // or right. Ask whether it's a triangle or a boundary on the left.
+ searchtri.Onext(ref checktri);
+ if (checktri.triangle == Mesh.dummytri)
+ {
+ leftflag = false;
+ }
+ else
+ {
+ rightflag = false;
+ }
+ }
+ while (leftflag)
+ {
+ // Turn left until satisfied.
+ searchtri.OnextSelf();
+ if (searchtri.triangle == Mesh.dummytri)
+ {
+ logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().1");
+ throw new Exception("Unable to find a triangle on path.");
+ }
+ leftvertex = searchtri.Apex();
+ rightccw = leftccw;
+ leftccw = Primitives.CounterClockwise(searchpoint.pt, startvertex.pt, leftvertex.pt);
+ leftflag = leftccw > 0.0;
+ }
+ while (rightflag)
+ {
+ // Turn right until satisfied.
+ searchtri.OprevSelf();
+ if (searchtri.triangle == Mesh.dummytri)
+ {
+ logger.Error("Unable to find a triangle on path.", "Mesh.FindDirection().2");
+ throw new Exception("Unable to find a triangle on path.");
+ }
+ rightvertex = searchtri.Dest();
+ leftccw = rightccw;
+ rightccw = Primitives.CounterClockwise(startvertex.pt, searchpoint.pt, rightvertex.pt);
+ rightflag = rightccw > 0.0;
+ }
+ if (leftccw == 0.0)
+ {
+ return FindDirectionResult.Leftcollinear;
+ }
+ else if (rightccw == 0.0)
+ {
+ return FindDirectionResult.Rightcollinear;
+ }
+ else
+ {
+ return FindDirectionResult.Within;
+ }
+ }
+
+ ///
+ /// Find the intersection of an existing segment and a segment that is being
+ /// inserted. Insert a vertex at the intersection, splitting an existing subsegment.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The segment being inserted connects the apex of splittri to endpoint2.
+ /// splitsubseg is the subsegment being split, and MUST adjoin splittri.
+ /// Hence, endpoints of the subsegment being split are the origin and
+ /// destination of splittri.
+ ///
+ /// On completion, splittri is a handle having the newly inserted
+ /// intersection point as its origin, and endpoint1 as its destination.
+ ///
+ void SegmentIntersection(ref Otri splittri, ref Osub splitsubseg, Vertex endpoint2)
+ {
+ Osub opposubseg = default(Osub);
+ Vertex endpoint1;
+ Vertex torg, tdest;
+ Vertex leftvertex, rightvertex;
+ Vertex newvertex;
+ InsertVertexResult success;
+ FindDirectionResult collinear;
+ double ex, ey;
+ double tx, ty;
+ double etx, ety;
+ double split, denom;
+ int i;
+
+ // Find the other three segment endpoints.
+ endpoint1 = splittri.Apex();
+ torg = splittri.Org();
+ tdest = splittri.Dest();
+ // Segment intersection formulae; see the Antonio reference.
+ tx = tdest.pt.X - torg.pt.X;
+ ty = tdest.pt.Y - torg.pt.Y;
+ ex = endpoint2.pt.X - endpoint1.pt.X;
+ ey = endpoint2.pt.Y - endpoint1.pt.Y;
+ etx = torg.pt.X - endpoint2.pt.X;
+ ety = torg.pt.Y - endpoint2.pt.Y;
+ denom = ty * ex - tx * ey;
+ if (denom == 0.0)
+ {
+ logger.Error("Attempt to find intersection of parallel segments.",
+ "Mesh.SegmentIntersection()");
+ throw new Exception("Attempt to find intersection of parallel segments.");
+ }
+ split = (ey * etx - ex * ety) / denom;
+ // Create the new vertex.
+ newvertex = new Vertex(nextras); //(vertex) poolalloc(&m.vertices);
+ vertices.Add(newvertex.Hash, newvertex);
+ // Interpolate its coordinate and attributes.
+ //for (i = 0; i < 2 + nextras; i++) {
+ newvertex.pt.X = torg.pt.X + split * (tdest.pt.X - torg.pt.X);
+ newvertex.pt.Y = torg.pt.Y + split * (tdest.pt.Y - torg.pt.Y);
+
+ newvertex.mark = splitsubseg.ss.boundary;
+ newvertex.type = VertexType.InputVertex;
+
+ // Insert the intersection vertex. This should always succeed.
+ success = InsertVertex(newvertex, ref splittri, ref splitsubseg, false, false);
+ if (success != InsertVertexResult.Successful)
+ {
+ logger.Error("Failure to split a segment.", "Mesh.SegmentIntersection()");
+ throw new Exception("Failure to split a segment.");
+ }
+ // Record a triangle whose origin is the new vertex.
+ newvertex.tri = splittri;
+ if (steinerleft > 0)
+ {
+ steinerleft--;
+ }
+
+ // Divide the segment into two, and correct the segment endpoints.
+ splitsubseg.SymSelf();
+ splitsubseg.Pivot(ref opposubseg);
+ splitsubseg.Dissolve();
+ opposubseg.Dissolve();
+ do
+ {
+ splitsubseg.SetSegOrg(newvertex);
+ splitsubseg.NextSelf();
+ } while (splitsubseg.ss != Mesh.dummysub);
+ do
+ {
+ opposubseg.SetSegOrg(newvertex);
+ opposubseg.NextSelf();
+ } while (opposubseg.ss != Mesh.dummysub);
+
+ // Inserting the vertex may have caused edge flips. We wish to rediscover
+ // the edge connecting endpoint1 to the new intersection vertex.
+ collinear = FindDirection(ref splittri, endpoint1);
+ rightvertex = splittri.Dest();
+ leftvertex = splittri.Apex();
+ if ((leftvertex.pt.X == endpoint1.pt.X) && (leftvertex.pt.Y == endpoint1.pt.Y))
+ {
+ splittri.OnextSelf();
+ }
+ else if ((rightvertex.pt.X != endpoint1.pt.X) || (rightvertex.pt.Y != endpoint1.pt.Y))
+ {
+ logger.Error("Topological inconsistency after splitting a segment.", "Mesh.SegmentIntersection()");
+ throw new Exception("Topological inconsistency after splitting a segment.");
+ }
+ // 'splittri' should have destination endpoint1.
+ }
+
+ ///
+ /// Scout the first triangle on the path from one endpoint to another, and check
+ /// for completion (reaching the second endpoint), a collinear vertex, or the
+ /// intersection of two segments.
+ ///
+ ///
+ ///
+ ///
+ /// Returns one if the entire segment is successfully inserted, and zero
+ /// if the job must be finished by conformingedge() or constrainededge().
+ ///
+ /// If the first triangle on the path has the second endpoint as its
+ /// destination or apex, a subsegment is inserted and the job is done.
+ ///
+ /// If the first triangle on the path has a destination or apex that lies on
+ /// the segment, a subsegment is inserted connecting the first endpoint to
+ /// the collinear vertex, and the search is continued from the collinear
+ /// vertex.
+ ///
+ /// If the first triangle on the path has a subsegment opposite its origin,
+ /// then there is a segment that intersects the segment being inserted.
+ /// Their intersection vertex is inserted, splitting the subsegment.
+ ///
+ bool ScoutSegment(ref Otri searchtri, Vertex endpoint2, int newmark)
+ {
+ Otri crosstri = default(Otri);
+ Osub crosssubseg = default(Osub);
+ Vertex leftvertex, rightvertex;
+ FindDirectionResult collinear;
+
+ collinear = FindDirection(ref searchtri, endpoint2);
+ rightvertex = searchtri.Dest();
+ leftvertex = searchtri.Apex();
+ if (((leftvertex.pt.X == endpoint2.pt.X) && (leftvertex.pt.Y == endpoint2.pt.Y)) ||
+ ((rightvertex.pt.X == endpoint2.pt.X) && (rightvertex.pt.Y == endpoint2.pt.Y)))
+ {
+ // The segment is already an edge in the mesh.
+ if ((leftvertex.pt.X == endpoint2.pt.X) && (leftvertex.pt.Y == endpoint2.pt.Y))
+ {
+ searchtri.LprevSelf();
+ }
+ // Insert a subsegment, if there isn't already one there.
+ InsertSubseg(ref searchtri, newmark);
+ return true;
+ }
+ else if (collinear == FindDirectionResult.Leftcollinear)
+ {
+ // We've collided with a vertex between the segment's endpoints.
+ // Make the collinear vertex be the triangle's origin.
+ searchtri.LprevSelf();
+ InsertSubseg(ref searchtri, newmark);
+ // Insert the remainder of the segment.
+ return ScoutSegment(ref searchtri, endpoint2, newmark);
+ }
+ else if (collinear == FindDirectionResult.Rightcollinear)
+ {
+ // We've collided with a vertex between the segment's endpoints.
+ InsertSubseg(ref searchtri, newmark);
+ // Make the collinear vertex be the triangle's origin.
+ searchtri.LnextSelf();
+ // Insert the remainder of the segment.
+ return ScoutSegment(ref searchtri, endpoint2, newmark);
+ }
+ else
+ {
+ searchtri.Lnext(ref crosstri);
+ crosstri.SegPivot(ref crosssubseg);
+ // Check for a crossing segment.
+ if (crosssubseg.ss == Mesh.dummysub)
+ {
+ return false;
+ }
+ else
+ {
+ // Insert a vertex at the intersection.
+ SegmentIntersection(ref crosstri, ref crosssubseg, endpoint2);
+ crosstri.Copy(ref searchtri);
+ InsertSubseg(ref searchtri, newmark);
+ // Insert the remainder of the segment.
+ return ScoutSegment(ref searchtri, endpoint2, newmark);
+ }
+ }
+ }
+
+ /*
+ ///
+ /// Force a segment into a conforming Delaunay triangulation by inserting a
+ /// vertex at its midpoint, and recursively forcing in the two half-segments
+ /// if necessary.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Generates a sequence of subsegments connecting 'endpoint1' to
+ /// 'endpoint2'. 'newmark' is the boundary marker of the segment, assigned
+ /// to each new splitting vertex and subsegment.
+ ///
+ /// Note that conformingedge() does not always maintain the conforming
+ /// Delaunay property. Once inserted, segments are locked into place;
+ /// vertices inserted later (to force other segments in) may render these
+ /// fixed segments non-Delaunay. The conforming Delaunay property will be
+ /// restored by enforcequality() by splitting encroached subsegments.
+ ///
+ void ConformingEdge(Vertex endpoint1, Vertex endpoint2, int newmark)
+ {
+ Otri searchtri1 = default(Otri), searchtri2 = default(Otri);
+ Osub brokensubseg = default(Osub);
+ Vertex newvertex;
+ Vertex midvertex1, midvertex2;
+ InsertVertexResult success;
+ int i;
+
+ // Create a new vertex to insert in the middle of the segment.
+ //newvertex = (vertex) poolalloc(&m.vertices);
+ newvertex = new Vertex(nextras);
+ vertices.Add(newvertex.Hash, newvertex);
+
+ // Interpolate coordinates and attributes.
+ for (i = 0; i < 2 + nextras; i++)
+ {
+ //newvertex.pt[i] = 0.5 * (endpoint1.pt[i] + endpoint2.pt[i]);
+ }
+ newvertex.mark = newmark;
+ newvertex.type = VertexType.SegmentVertex;
+ // No known triangle to search from.
+ searchtri1.triangle = Mesh.dummytri;
+ // Attempt to insert the new vertex.
+ Osub tmp = default(Osub);
+ success = InsertVertex(newvertex, ref searchtri1, ref tmp, false, false);
+ if (success == InsertVertexResult.Duplicate)
+ {
+ // Segment intersects existing vertex. Use the vertex that's already there.
+ VertexDealloc(newvertex);
+ newvertex = searchtri1.Org();
+ }
+ else
+ {
+ if (success == InsertVertexResult.Violating)
+ {
+ // Two segments intersect. By fluke, we've landed right on another segment. Split it.
+ searchtri1.SegPivot(ref brokensubseg);
+ success = InsertVertex(newvertex, ref searchtri1, ref brokensubseg, false, false);
+ if (success != InsertVertexResult.Successful)
+ {
+ logger.Error("Failure to split a segment.", "Mesh.ConformingEdge()");
+ throw new Exception("Failure to split a segment.");
+ }
+ }
+ // The vertex has been inserted successfully.
+ if (steinerleft > 0)
+ {
+ steinerleft--;
+ }
+ }
+ searchtri1.Copy(ref searchtri2);
+ // 'searchtri1' and 'searchtri2' are fastened at their origins to
+ // 'newvertex', and will be directed toward 'endpoint1' and 'endpoint2'
+ // respectively. First, we must get 'searchtri2' out of the way so it
+ // won't be invalidated during the insertion of the first half of the
+ // segment.
+ FindDirection(ref searchtri2, endpoint2);
+ if (!ScoutSegment(ref searchtri1, endpoint1, newmark))
+ {
+ // The origin of searchtri1 may have changed if a collision with an
+ // intervening vertex on the segment occurred.
+ midvertex1 = searchtri1.Org();
+ ConformingEdge(midvertex1, endpoint1, newmark);
+ }
+ if (!ScoutSegment(ref searchtri2, endpoint2, newmark))
+ {
+ // The origin of searchtri2 may have changed if a collision with an
+ // intervening vertex on the segment occurred.
+ midvertex2 = searchtri2.Org();
+ ConformingEdge(midvertex2, endpoint2, newmark);
+ }
+ }
+ * */
+
+ ///
+ /// Enforce the Delaunay condition at an edge, fanning out recursively from
+ /// an existing vertex. Pay special attention to stacking inverted triangles.
+ ///
+ ///
+ /// indicates whether or not fixuptri is to the left of
+ /// the segment being inserted. (Imagine that the segment is pointing up from
+ /// endpoint1 to endpoint2.)
+ ///
+ /// This is a support routine for inserting segments into a constrained
+ /// Delaunay triangulation.
+ ///
+ /// The origin of fixuptri is treated as if it has just been inserted, and
+ /// the local Delaunay condition needs to be enforced. It is only enforced
+ /// in one sector, however, that being the angular range defined by
+ /// fixuptri.
+ ///
+ /// This routine also needs to make decisions regarding the "stacking" of
+ /// triangles. (Read the description of constrainededge() below before
+ /// reading on here, so you understand the algorithm.) If the position of
+ /// the new vertex (the origin of fixuptri) indicates that the vertex before
+ /// it on the polygon is a reflex vertex, then "stack" the triangle by
+ /// doing nothing. (fixuptri is an inverted triangle, which is how stacked
+ /// triangles are identified.)
+ ///
+ /// Otherwise, check whether the vertex before that was a reflex vertex.
+ /// If so, perform an edge flip, thereby eliminating an inverted triangle
+ /// (popping it off the stack). The edge flip may result in the creation
+ /// of a new inverted triangle, depending on whether or not the new vertex
+ /// is visible to the vertex three edges behind on the polygon.
+ ///
+ /// If neither of the two vertices behind the new vertex are reflex
+ /// vertices, fixuptri and fartri, the triangle opposite it, are not
+ /// inverted; hence, ensure that the edge between them is locally Delaunay.
+ ///
+ void DelaunayFixup(ref Otri fixuptri, bool leftside)
+ {
+ Otri neartri = default(Otri);
+ Otri fartri = default(Otri);
+ Osub faredge = default(Osub);
+ Vertex nearvertex, leftvertex, rightvertex, farvertex;
+
+ fixuptri.Lnext(ref neartri);
+ neartri.Sym(ref fartri);
+ // Check if the edge opposite the origin of fixuptri can be flipped.
+ if (fartri.triangle == Mesh.dummytri)
+ {
+ return;
+ }
+ neartri.SegPivot(ref faredge);
+ if (faredge.ss != Mesh.dummysub)
+ {
+ return;
+ }
+ // Find all the relevant vertices.
+ nearvertex = neartri.Apex();
+ leftvertex = neartri.Org();
+ rightvertex = neartri.Dest();
+ farvertex = fartri.Apex();
+ // Check whether the previous polygon vertex is a reflex vertex.
+ if (leftside)
+ {
+ if (Primitives.CounterClockwise(nearvertex.pt, leftvertex.pt, farvertex.pt) <= 0.0)
+ {
+ // leftvertex is a reflex vertex too. Nothing can
+ // be done until a convex section is found.
+ return;
+ }
+ }
+ else
+ {
+ if (Primitives.CounterClockwise(farvertex.pt, rightvertex.pt, nearvertex.pt) <= 0.0)
+ {
+ // rightvertex is a reflex vertex too. Nothing can
+ // be done until a convex section is found.
+ return;
+ }
+ }
+ if (Primitives.CounterClockwise(rightvertex.pt, leftvertex.pt, farvertex.pt) > 0.0)
+ {
+ // fartri is not an inverted triangle, and farvertex is not a reflex
+ // vertex. As there are no reflex vertices, fixuptri isn't an
+ // inverted triangle, either. Hence, test the edge between the
+ // triangles to ensure it is locally Delaunay.
+ if (Primitives.InCircle(leftvertex.pt, farvertex.pt, rightvertex.pt, nearvertex.pt) <= 0.0)
+ {
+ return;
+ }
+ // Not locally Delaunay; go on to an edge flip.
+ }
+ // else fartri is inverted; remove it from the stack by flipping.
+ Flip(ref neartri);
+ fixuptri.LprevSelf(); // Restore the origin of fixuptri after the flip.
+ // Recursively process the two triangles that result from the flip.
+ DelaunayFixup(ref fixuptri, leftside);
+ DelaunayFixup(ref fartri, leftside);
+ }
+
+ ///
+ /// Force a segment into a constrained Delaunay triangulation by deleting the
+ /// triangles it intersects, and triangulating the polygons that form on each
+ /// side of it.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Generates a single subsegment connecting 'endpoint1' to 'endpoint2'.
+ /// The triangle 'starttri' has 'endpoint1' as its origin. 'newmark' is the
+ /// boundary marker of the segment.
+ ///
+ /// To insert a segment, every triangle whose interior intersects the
+ /// segment is deleted. The union of these deleted triangles is a polygon
+ /// (which is not necessarily monotone, but is close enough), which is
+ /// divided into two polygons by the new segment. This routine's task is
+ /// to generate the Delaunay triangulation of these two polygons.
+ ///
+ /// You might think of this routine's behavior as a two-step process. The
+ /// first step is to walk from endpoint1 to endpoint2, flipping each edge
+ /// encountered. This step creates a fan of edges connected to endpoint1,
+ /// including the desired edge to endpoint2. The second step enforces the
+ /// Delaunay condition on each side of the segment in an incremental manner:
+ /// proceeding along the polygon from endpoint1 to endpoint2 (this is done
+ /// independently on each side of the segment), each vertex is "enforced"
+ /// as if it had just been inserted, but affecting only the previous
+ /// vertices. The result is the same as if the vertices had been inserted
+ /// in the order they appear on the polygon, so the result is Delaunay.
+ ///
+ /// In truth, constrainededge() interleaves these two steps. The procedure
+ /// walks from endpoint1 to endpoint2, and each time an edge is encountered
+ /// and flipped, the newly exposed vertex (at the far end of the flipped
+ /// edge) is "enforced" upon the previously flipped edges, usually affecting
+ /// only one side of the polygon (depending upon which side of the segment
+ /// the vertex falls on).
+ ///
+ /// The algorithm is complicated by the need to handle polygons that are not
+ /// convex. Although the polygon is not necessarily monotone, it can be
+ /// triangulated in a manner similar to the stack-based algorithms for
+ /// monotone polygons. For each reflex vertex (local concavity) of the
+ /// polygon, there will be an inverted triangle formed by one of the edge
+ /// flips. (An inverted triangle is one with negative area - that is, its
+ /// vertices are arranged in clockwise order - and is best thought of as a
+ /// wrinkle in the fabric of the mesh.) Each inverted triangle can be
+ /// thought of as a reflex vertex pushed on the stack, waiting to be fixed
+ /// later.
+ ///
+ /// A reflex vertex is popped from the stack when a vertex is inserted that
+ /// is visible to the reflex vertex. (However, if the vertex behind the
+ /// reflex vertex is not visible to the reflex vertex, a new inverted
+ /// triangle will take its place on the stack.) These details are handled
+ /// by the delaunayfixup() routine above.
+ ///
+ void ConstrainedEdge(ref Otri starttri, Vertex endpoint2, int newmark)
+ {
+ Otri fixuptri = default(Otri), fixuptri2 = default(Otri);
+ Osub crosssubseg = default(Osub);
+ Vertex endpoint1;
+ Vertex farvertex;
+ double area;
+ bool collision;
+ bool done;
+
+ endpoint1 = starttri.Org();
+ starttri.Lnext(ref fixuptri);
+ Flip(ref fixuptri);
+ // 'collision' indicates whether we have found a vertex directly
+ // between endpoint1 and endpoint2.
+ collision = false;
+ done = false;
+ do
+ {
+ farvertex = fixuptri.Org();
+ // 'farvertex' is the extreme point of the polygon we are "digging"
+ // to get from endpoint1 to endpoint2.
+ if ((farvertex.pt.X == endpoint2.pt.X) && (farvertex.pt.Y == endpoint2.pt.Y))
+ {
+ fixuptri.Oprev(ref fixuptri2);
+ // Enforce the Delaunay condition around endpoint2.
+ DelaunayFixup(ref fixuptri, false);
+ DelaunayFixup(ref fixuptri2, true);
+ done = true;
+ }
+ else
+ {
+ // Check whether farvertex is to the left or right of the segment
+ // being inserted, to decide which edge of fixuptri to dig
+ // through next.
+ area = Primitives.CounterClockwise(endpoint1.pt, endpoint2.pt, farvertex.pt);
+ if (area == 0.0)
+ {
+ // We've collided with a vertex between endpoint1 and endpoint2.
+ collision = true;
+ fixuptri.Oprev(ref fixuptri2);
+ // Enforce the Delaunay condition around farvertex.
+ DelaunayFixup(ref fixuptri, false);
+ DelaunayFixup(ref fixuptri2, true);
+ done = true;
+ }
+ else
+ {
+ if (area > 0.0)
+ { // farvertex is to the left of the segment.
+ fixuptri.Oprev(ref fixuptri2);
+ // Enforce the Delaunay condition around farvertex, on the
+ // left side of the segment only.
+ DelaunayFixup(ref fixuptri2, true);
+ // Flip the edge that crosses the segment. After the edge is
+ // flipped, one of its endpoints is the fan vertex, and the
+ // destination of fixuptri is the fan vertex.
+ fixuptri.LprevSelf();
+ }
+ else
+ { // farvertex is to the right of the segment.
+ DelaunayFixup(ref fixuptri, false);
+ // Flip the edge that crosses the segment. After the edge is
+ // flipped, one of its endpoints is the fan vertex, and the
+ // destination of fixuptri is the fan vertex.
+ fixuptri.OprevSelf();
+ }
+ // Check for two intersecting segments.
+ fixuptri.SegPivot(ref crosssubseg);
+ if (crosssubseg.ss == Mesh.dummysub)
+ {
+ Flip(ref fixuptri); // May create inverted triangle at left.
+ }
+ else
+ {
+ // We've collided with a segment between endpoint1 and endpoint2.
+ collision = true;
+ // Insert a vertex at the intersection.
+ SegmentIntersection(ref fixuptri, ref crosssubseg, endpoint2);
+ done = true;
+ }
+ }
+ }
+ } while (!done);
+ // Insert a subsegment to make the segment permanent.
+ InsertSubseg(ref fixuptri, newmark);
+ // If there was a collision with an interceding vertex, install another
+ // segment connecting that vertex with endpoint2.
+ if (collision)
+ {
+ // Insert the remainder of the segment.
+ if (!ScoutSegment(ref fixuptri, endpoint2, newmark))
+ {
+ ConstrainedEdge(ref fixuptri, endpoint2, newmark);
+ }
+ }
+ }
+
+ ///
+ /// Insert a PSLG segment into a triangulation.
+ ///
+ ///
+ ///
+ ///
+ void InsertSegment(Vertex endpoint1, Vertex endpoint2, int newmark)
+ {
+ Otri searchtri1 = default(Otri), searchtri2 = default(Otri);
+ Vertex checkvertex;
+
+ // Find a triangle whose origin is the segment's first endpoint.
+ checkvertex = null;
+ searchtri1 = endpoint1.tri;
+ if (searchtri1.triangle != null)
+ {
+ checkvertex = searchtri1.Org();
+ }
+ if (checkvertex != endpoint1)
+ {
+ // Find a boundary triangle to search from.
+ searchtri1.triangle = Mesh.dummytri;
+ searchtri1.orient = 0;
+ searchtri1.SymSelf();
+ // Search for the segment's first endpoint by point location.
+ if (Locate(endpoint1.pt, ref searchtri1) != LocateResult.OnVertex)
+ {
+ logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().1");
+ throw new Exception("Unable to locate PSLG vertex in triangulation.");
+ }
+ }
+ // Remember this triangle to improve subsequent point location.
+ searchtri1.Copy(ref recenttri);
+
+ // Scout the beginnings of a path from the first endpoint
+ // toward the second.
+ if (ScoutSegment(ref searchtri1, endpoint2, newmark))
+ {
+ // The segment was easily inserted.
+ return;
+ }
+ // The first endpoint may have changed if a collision with an intervening
+ // vertex on the segment occurred.
+ endpoint1 = searchtri1.Org();
+
+ // Find a triangle whose origin is the segment's second endpoint.
+ checkvertex = null;
+ searchtri2 = endpoint2.tri;
+ if (searchtri2.triangle != null)
+ {
+ checkvertex = searchtri2.Org();
+ }
+ if (checkvertex != endpoint2)
+ {
+ // Find a boundary triangle to search from.
+ searchtri2.triangle = Mesh.dummytri;
+ searchtri2.orient = 0;
+ searchtri2.SymSelf();
+ // Search for the segment's second endpoint by point location.
+ if (Locate(endpoint2.pt, ref searchtri2) != LocateResult.OnVertex)
+ {
+ logger.Error("Unable to locate PSLG vertex in triangulation.", "Mesh.InsertSegment().2");
+ throw new Exception("Unable to locate PSLG vertex in triangulation.");
+ }
+ }
+ // Remember this triangle to improve subsequent point location.
+ searchtri2.Copy(ref recenttri);
+ // Scout the beginnings of a path from the second endpoint
+ // toward the first.
+ if (ScoutSegment(ref searchtri2, endpoint1, newmark))
+ {
+ // The segment was easily inserted.
+ return;
+ }
+ // The second endpoint may have changed if a collision with an intervening
+ // vertex on the segment occurred.
+ endpoint2 = searchtri2.Org();
+
+ //if (Behavior.SplitSeg)
+ //{
+ // // Insert vertices to force the segment into the triangulation.
+ // ConformingEdge(endpoint1, endpoint2, newmark);
+ //}
+ //else
+
+ // Insert the segment directly into the triangulation.
+ ConstrainedEdge(ref searchtri1, endpoint2, newmark);
+ }
+
+ ///
+ /// Cover the convex hull of a triangulation with subsegments.
+ ///
+ void MarkHull()
+ {
+ Otri hulltri = default(Otri);
+ Otri nexttri = default(Otri);
+ Otri starttri = default(Otri);
+
+ // Find a triangle handle on the hull.
+ hulltri.triangle = Mesh.dummytri;
+ hulltri.orient = 0;
+ hulltri.SymSelf();
+ // Remember where we started so we know when to stop.
+ hulltri.Copy(ref starttri);
+ // Go once counterclockwise around the convex hull.
+ do
+ {
+ // Create a subsegment if there isn't already one here.
+ InsertSubseg(ref hulltri, 1);
+ // To find the next hull edge, go clockwise around the next vertex.
+ hulltri.LnextSelf();
+ hulltri.Oprev(ref nexttri);
+ while (nexttri.triangle != Mesh.dummytri)
+ {
+ nexttri.Copy(ref hulltri);
+ hulltri.Oprev(ref nexttri);
+ }
+ } while (!hulltri.Equal(starttri));
+ }
+
+ ///
+ /// Create the segments of a triangulation, including PSLG segments and edges
+ /// on the convex hull.
+ ///
+ ///
+ ///
+ ///
+ void FormSkeleton(MeshData data)
+ {
+ Vertex endpoint1, endpoint2;
+ bool segmentmarkers;
+ int end1, end2;
+ int boundmarker;
+ int numberofsegments = data.Segments == null ? 0 : data.Segments.Length;
+
+ if (Behavior.Poly)
+ {
+ insegments = numberofsegments;
+ segmentmarkers = data.SegmentMarkers != null;
+
+ // If the input vertices are collinear, there is no triangulation,
+ // so don't try to insert segments.
+ if (triangles.Count == 0)
+ {
+ return;
+ }
+
+ // If segments are to be inserted, compute a mapping
+ // from vertices to triangles.
+ if (insegments > 0)
+ {
+ MakeVertexMap();
+ }
+
+ boundmarker = 0;
+ // Read and insert the segments.
+ for (int i = 0; i < insegments; i++)
+ {
+ end1 = data.Segments[i][0];
+ end2 = data.Segments[i][1];
+ if (segmentmarkers)
+ {
+ boundmarker = data.SegmentMarkers[i];
+ }
+
+ if ((end1 < 0) || (end1 >= invertices))
+ {
+ if (Behavior.Verbose)
+ {
+ logger.Warning("Invalid first endpoint of segment.", "Mesh.FormSkeleton().1");
+ }
+ }
+ else if ((end2 < 0) || (end2 >= invertices))
+ {
+ if (Behavior.Verbose)
+ {
+ logger.Warning("Invalid second endpoint of segment.", "Mesh.FormSkeleton().2");
+ }
+ }
+ else
+ {
+ // TODO: Is using the vertex ID reliable???
+ // It should be. The ID gets appropriately set in TransferNodes().
+
+ // Find the vertices numbered 'end1' and 'end2'.
+ endpoint1 = vertices[end1]; // getvertex(end1);
+ endpoint2 = vertices[end2]; // getvertex(end2);
+ if ((endpoint1.pt.X == endpoint2.pt.X) && (endpoint1.pt.Y == endpoint2.pt.Y))
+ {
+ if (Behavior.Verbose)
+ {
+ logger.Warning("Endpoints of segments are coincident.", "Mesh.FormSkeleton()");
+ }
+ }
+ else
+ {
+ InsertSegment(endpoint1, endpoint2, boundmarker);
+ }
+ }
+ }
+ }
+ else
+ {
+ insegments = 0;
+ }
+
+ if (Behavior.Convex || !Behavior.Poly)
+ {
+ // Enclose the convex hull with subsegments.
+ MarkHull();
+ }
+ }
+
+ #endregion
+
+ #region Dealloc
+
+ ///
+ /// Deallocate space for a triangle, marking it dead.
+ ///
+ ///
+ internal void TriangleDealloc(Triangle dyingtriangle)
+ {
+ // Mark the triangle as dead. This makes it possible to detect dead
+ // triangles when traversing the list of all triangles.
+ Otri.Kill(dyingtriangle);
+ triangles.Remove(dyingtriangle.Hash);
+ }
+
+ ///
+ /// Deallocate space for a vertex, marking it dead.
+ ///
+ ///
+ internal void VertexDealloc(Vertex dyingvertex)
+ {
+ // Mark the vertex as dead. This makes it possible to detect dead
+ // vertices when traversing the list of all vertices.
+ dyingvertex.type = VertexType.DeadVertex;
+ vertices.Remove(dyingvertex.Hash);
+ }
+
+ ///
+ /// Deallocate space for a subsegment, marking it dead.
+ ///
+ ///
+ internal void SubsegDealloc(Subseg dyingsubseg)
+ {
+ // Mark the subsegment as dead. This makes it possible to detect dead
+ // subsegments when traversing the list of all subsegments.
+ Osub.Kill(dyingsubseg);
+ subsegs.Remove(dyingsubseg.Hash);
+ }
+
+ #endregion
+ }
+}
diff --git a/Triangle.NET/Triangle/NewLocation.cs b/Triangle.NET/Triangle/NewLocation.cs
new file mode 100644
index 0000000..0967532
--- /dev/null
+++ b/Triangle.NET/Triangle/NewLocation.cs
@@ -0,0 +1,4329 @@
+// -----------------------------------------------------------------------
+//
+// Original code by Hale Erten and Alper Üngör, http://www.cise.ufl.edu/~ungor/aCute/index.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet
+{
+ using System;
+ using TriangleNet.Data;
+
+ ///
+ /// Find new Steiner Point locations.
+ ///
+ ///
+ /// http://www.cise.ufl.edu/~ungor/aCute/index.html
+ ///
+ static class NewLocation
+ {
+ const double EPS = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000001;
+
+ ///
+ /// Find a new location for a Steiner point.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static void FindLocation(Mesh m, Vertex torg, Vertex tdest, Vertex tapex,
+ Vertex circumcenter, ref double xi, ref double eta, bool offcenter, Otri badotri) // TODO: ref circumcenter???
+ {
+ // Based on using -U switch, call the corresponding function
+ if (Behavior.MaxAngle == 0.0)
+ {
+ FindNewLocationWithoutMaxAngle(m, torg, tdest, tapex, circumcenter, ref xi, ref eta, true, badotri);
+ }
+ else
+ {
+ FindNewLocation(m, torg, tdest, tapex, circumcenter, ref xi, ref eta, true, badotri);
+ }
+
+ }
+
+ ///
+ /// Find a new location for a Steiner point.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ static void FindNewLocationWithoutMaxAngle(Mesh m, Vertex torg, Vertex tdest, Vertex tapex,
+ Vertex circumcenter, ref double xi, ref double eta, bool offcenter, Otri badotri)
+ {
+
+ // for calculating the distances of the edges
+ double xdo, ydo, xao, yao, xda, yda;
+ double dodist, aodist, dadist;
+ // for exact calculation
+ double denominator;
+ double dx, dy, dxoff, dyoff;
+
+ ////////////////////////////// HALE'S VARIABLES //////////////////////////////
+ // keeps the difference of coordinates edge
+ double xShortestEdge = 0, yShortestEdge = 0, xMiddleEdge, yMiddleEdge, xLongestEdge, yLongestEdge;
+
+ // keeps the square of edge lengths
+ double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0;
+
+ // keeps the vertices according to the angle incident to that vertex in a triangle
+ Point2 smallestAngleCorner, middleAngleCorner, largestAngleCorner;
+
+ // keeps the type of orientation if the triangle
+ int orientation = 0;
+ // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter
+ Point2 myCircumcenter = default(Point2), neighborCircumcenter = default(Point2);
+
+ // keeps if bad triangle is almost good or not
+ int almostGood = 0;
+ // keeps the cosine of the largest angle
+ double cosMaxAngle;
+ bool isObtuse; // 1: obtuse 0: nonobtuse
+ // keeps the radius of petal
+ double petalRadius;
+ // for calculating petal center
+ double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge;
+ double dxcenter1, dycenter1, dxcenter2, dycenter2;
+ // for finding neighbor
+ Otri neighborotri = default(Otri);
+ double[] thirdPoint = new double[2];
+ //int neighborNotFound = -1;
+ bool neighborNotFound;
+ // for keeping the vertices of the neighbor triangle
+ Vertex neighborvertex_1;
+ Vertex neighborvertex_2;
+ Vertex neighborvertex_3;
+ // dummy variables
+ double xi_tmp = 0, eta_tmp = 0;
+ //vertex thirdVertex;
+ // for petal intersection
+ double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y;
+ double[] p = new double[5], voronoiOrInter = new double[4];
+ bool isCorrect;
+
+ // for vector calculations in perturbation
+ double ax, ay, d;
+ double pertConst = 0.06; // perturbation constant
+
+ double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance
+ double justAcute = 1; // used for making the program working for one direction only
+ // for smoothing
+ int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point
+ double[] newloc = new double[2]; // new location suggested by smoothing
+ double origin_x = 0, origin_y = 0; // for keeping torg safe
+ Otri delotri; // keeping the original orientation for relocation process
+ // keeps the first and second direction suggested points
+ double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion;
+ // second direction variables
+ double xMidOfMiddleEdge, yMidOfMiddleEdge;
+ ////////////////////////////// END OF HALE'S VARIABLES //////////////////////////////
+
+ Statistic.CircumcenterCount++;
+
+ // Compute the circumcenter of the triangle.
+ xdo = tdest.pt.X - torg.pt.X;
+ ydo = tdest.pt.Y - torg.pt.Y;
+ xao = tapex.pt.X - torg.pt.X;
+ yao = tapex.pt.Y - torg.pt.Y;
+ xda = tapex.pt.X - tdest.pt.X;
+ yda = tapex.pt.Y - tdest.pt.Y;
+ // keeps the square of the distances
+ dodist = xdo * xdo + ydo * ydo;
+ aodist = xao * xao + yao * yao;
+ dadist = (tdest.pt.X - tapex.pt.X) * (tdest.pt.X - tapex.pt.X) +
+ (tdest.pt.Y - tapex.pt.Y) * (tdest.pt.Y - tapex.pt.Y);
+ // checking if the user wanted exact arithmetic or not
+ if (Behavior.NoExact)
+ {
+ denominator = 0.5 / (xdo * yao - xao * ydo);
+ }
+ else
+ {
+ // Use the counterclockwise() routine to ensure a positive (and
+ // reasonably accurate) result, avoiding any possibility of
+ // division by zero.
+ denominator = 0.5 / Primitives.CounterClockwise(tdest.pt, tapex.pt, torg.pt);
+ // Don't count the above as an orientation test.
+ Statistic.CounterClockwiseCount--;
+ }
+ // calculate the circumcenter in terms of distance to origin point
+ dx = (yao * dodist - ydo * aodist) * denominator;
+ dy = (xdo * aodist - xao * dodist) * denominator;
+ // for debugging and for keeping circumcenter to use later
+ // coordinate value of the circumcenter
+ myCircumcenter.X = torg.pt.X + dx;
+ myCircumcenter.Y = torg.pt.Y + dy;
+
+ delotri = badotri; // save for later
+ ///////////////// FINDING THE ORIENTATION OF TRIANGLE //////////////////
+ // Find the (squared) length of the triangle's shortest edge. This
+ // serves as a conservative estimate of the insertion radius of the
+ // circumcenter's parent. The estimate is used to ensure that
+ // the algorithm terminates even if very small angles appear in
+ // the input PSLG.
+ // find the orientation of the triangle, basically shortest and longest edges
+ orientation = LongestShortestEdge(aodist, dadist, dodist);
+ //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]);
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist //
+ // middle: dadist // middle: aodist // middle: aodist //
+ // longest: dodist // longest: dodist // longest: dadist //
+ // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist //
+ // middle: dodist // middle: dodist // middle: dadist //
+ // longest: dadist // longest: aodist // longest: aodist //
+ /////////////////////////////////////////////////////////////////////////////////////////////
+
+ switch (orientation)
+ {
+ case 123: // assign necessary information
+ /// smallest angle corner: dest
+ /// largest angle corner: apex
+ xShortestEdge = xao; yShortestEdge = yao;
+ xMiddleEdge = xda; yMiddleEdge = yda;
+ xLongestEdge = xdo; yLongestEdge = ydo;
+
+ shortestEdgeDist = aodist;
+ middleEdgeDist = dadist;
+ longestEdgeDist = dodist;
+
+ smallestAngleCorner = tdest.pt;
+ middleAngleCorner = torg.pt;
+ largestAngleCorner = tapex.pt;
+ break;
+
+ case 132: // assign necessary information
+ /// smallest angle corner: dest
+ /// largest angle corner: org
+ xShortestEdge = xao; yShortestEdge = yao;
+ xMiddleEdge = xdo; yMiddleEdge = ydo;
+ xLongestEdge = xda; yLongestEdge = yda;
+
+ shortestEdgeDist = aodist;
+ middleEdgeDist = dodist;
+ longestEdgeDist = dadist;
+
+ smallestAngleCorner = tdest.pt;
+ middleAngleCorner = tapex.pt;
+ largestAngleCorner = torg.pt;
+
+ break;
+ case 213: // assign necessary information
+ /// smallest angle corner: org
+ /// largest angle corner: apex
+ xShortestEdge = xda; yShortestEdge = yda;
+ xMiddleEdge = xao; yMiddleEdge = yao;
+ xLongestEdge = xdo; yLongestEdge = ydo;
+
+ shortestEdgeDist = dadist;
+ middleEdgeDist = aodist;
+ longestEdgeDist = dodist;
+
+ smallestAngleCorner = torg.pt;
+ middleAngleCorner = tdest.pt;
+ largestAngleCorner = tapex.pt;
+ break;
+ case 231: // assign necessary information
+ /// smallest angle corner: org
+ /// largest angle corner: dest
+ xShortestEdge = xda; yShortestEdge = yda;
+ xMiddleEdge = xdo; yMiddleEdge = ydo;
+ xLongestEdge = xao; yLongestEdge = yao;
+
+ shortestEdgeDist = dadist;
+ middleEdgeDist = dodist;
+ longestEdgeDist = aodist;
+
+ smallestAngleCorner = torg.pt;
+ middleAngleCorner = tapex.pt;
+ largestAngleCorner = tdest.pt;
+ break;
+ case 312: // assign necessary information
+ /// smallest angle corner: apex
+ /// largest angle corner: org
+ xShortestEdge = xdo; yShortestEdge = ydo;
+ xMiddleEdge = xao; yMiddleEdge = yao;
+ xLongestEdge = xda; yLongestEdge = yda;
+
+ shortestEdgeDist = dodist;
+ middleEdgeDist = aodist;
+ longestEdgeDist = dadist;
+
+ smallestAngleCorner = tapex.pt;
+ middleAngleCorner = tdest.pt;
+ largestAngleCorner = torg.pt;
+ break;
+ case 321: // assign necessary information
+ default: // TODO: is this safe?
+ /// smallest angle corner: apex
+ /// largest angle corner: dest
+ xShortestEdge = xdo; yShortestEdge = ydo;
+ xMiddleEdge = xda; yMiddleEdge = yda;
+ xLongestEdge = xao; yLongestEdge = yao;
+
+ shortestEdgeDist = dodist;
+ middleEdgeDist = dadist;
+ longestEdgeDist = aodist;
+
+ smallestAngleCorner = tapex.pt;
+ middleAngleCorner = torg.pt;
+ largestAngleCorner = tdest.pt;
+ break;
+
+ }// end of switch
+ // check for offcenter condition
+ if (offcenter && (Behavior.Offconstant > 0.0))
+ {
+ // origin has the smallest angle
+ if (orientation == 213 || orientation == 231)
+ {
+ // Find the position of the off-center, as described by Alper Ungor.
+ dxoff = 0.5 * xShortestEdge - Behavior.Offconstant * yShortestEdge;
+ dyoff = 0.5 * yShortestEdge + Behavior.Offconstant * xShortestEdge;
+ // If the off-center is closer to destination than the
+ // circumcenter, use the off-center instead.
+ /// doubleLY BAD CASE ///
+ if (dxoff * dxoff + dyoff * dyoff <
+ (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo))
+ {
+ dx = xdo + dxoff;
+ dy = ydo + dyoff;
+ }
+ /// ALMOST GOOD CASE ///
+ else
+ {
+ almostGood = 1;
+ }
+ // destination has the smallest angle
+ }
+ else if (orientation == 123 || orientation == 132)
+ {
+ // Find the position of the off-center, as described by Alper Ungor.
+ dxoff = 0.5 * xShortestEdge + Behavior.Offconstant * yShortestEdge;
+ dyoff = 0.5 * yShortestEdge - Behavior.Offconstant * xShortestEdge;
+ // If the off-center is closer to the origin than the
+ // circumcenter, use the off-center instead.
+ /// doubleLY BAD CASE ///
+ if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy)
+ {
+ dx = dxoff;
+ dy = dyoff;
+ }
+ /// ALMOST GOOD CASE ///
+ else
+ {
+ almostGood = 1;
+ }
+ // apex has the smallest angle
+ }
+ else
+ {//orientation == 312 || orientation == 321
+ // Find the position of the off-center, as described by Alper Ungor.
+ dxoff = 0.5 * xShortestEdge - Behavior.Offconstant * yShortestEdge;
+ dyoff = 0.5 * yShortestEdge + Behavior.Offconstant * xShortestEdge;
+ // If the off-center is closer to the origin than the
+ // circumcenter, use the off-center instead.
+ /// doubleLY BAD CASE ///
+ if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy)
+ {
+ dx = dxoff;
+ dy = dyoff;
+ }
+ /// ALMOST GOOD CASE ///
+ else
+ {
+ almostGood = 1;
+ }
+ }
+ }
+ // if the bad triangle is almost good, apply our approach
+ if (almostGood == 1)
+ {
+
+ /// calculate cosine of largest angle ///
+ cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist));
+ if (cosMaxAngle < 0.0)
+ {
+ // obtuse
+ isObtuse = true;
+ }
+ else if (Math.Abs(cosMaxAngle - 0.0) <= EPS)
+ {
+ // right triangle (largest angle is 90 degrees)
+ isObtuse = true;
+ }
+ else
+ {
+ // nonobtuse
+ isObtuse = false;
+ }
+ /// RELOCATION (LOCAL SMOOTHING) ///
+ /// check for possible relocation of one of triangle's points ///
+ relocated = DoSmoothing(m, delotri, torg, tdest, tapex, ref newloc);
+ /// if relocation is possible, delete that vertex and insert a vertex at the new location ///
+ if (relocated > 0)
+ {
+ Statistic.RelocationCount++;
+
+ dx = newloc[0] - torg.pt.X;
+ dy = newloc[1] - torg.pt.Y;
+ origin_x = torg.pt.X; // keep for later use
+ origin_y = torg.pt.Y;
+ switch (relocated)
+ {
+ case 1:
+ //printf("Relocate: (%f,%f)\n", torg[0],torg[1]);
+ m.DeleteVertex(ref delotri);
+ break;
+ case 2:
+ //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]);
+ delotri.LnextSelf();
+ m.DeleteVertex(ref delotri);
+ break;
+ case 3:
+ //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]);
+ delotri.LprevSelf();
+ m.DeleteVertex(ref delotri);
+ break;
+
+ }
+ }
+ else
+ {
+ // calculate radius of the petal according to angle constraint
+ // first find the visible region, PETAL
+ // find the center of the circle and radius
+ petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(Behavior.MinAngle * Math.PI / 180.0));
+ /// compute two possible centers of the petal ///
+ // finding the center
+ // first find the middle point of smallest edge
+ xMidOfShortestEdge = (middleAngleCorner.X + largestAngleCorner.X) / 2.0;
+ yMidOfShortestEdge = (middleAngleCorner.Y + largestAngleCorner.Y) / 2.0;
+ // two possible centers
+ xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.Y -
+ largestAngleCorner.Y) / Math.Sqrt(shortestEdgeDist);
+ yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.X -
+ middleAngleCorner.X) / Math.Sqrt(shortestEdgeDist);
+
+ xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.Y -
+ largestAngleCorner.Y) / Math.Sqrt(shortestEdgeDist);
+ yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.X -
+ middleAngleCorner.X) / Math.Sqrt(shortestEdgeDist);
+ // find the correct circle since there will be two possible circles
+ // calculate the distance to smallest angle corner
+ dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.X) * (xPetalCtr_1 - smallestAngleCorner.X);
+ dycenter1 = (yPetalCtr_1 - smallestAngleCorner.Y) * (yPetalCtr_1 - smallestAngleCorner.Y);
+ dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.X) * (xPetalCtr_2 - smallestAngleCorner.X);
+ dycenter2 = (yPetalCtr_2 - smallestAngleCorner.Y) * (yPetalCtr_2 - smallestAngleCorner.Y);
+
+ // whichever is closer to smallest angle corner, it must be the center
+ if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2)
+ {
+ xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1;
+ }
+ else
+ {
+ xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2;
+ }
+
+ /// find the third point of the neighbor triangle ///
+ neighborNotFound = GetNeighborsVertex(m, badotri, middleAngleCorner.X, middleAngleCorner.Y,
+ smallestAngleCorner.X, smallestAngleCorner.Y, ref thirdPoint, ref neighborotri);
+ /// find the circumcenter of the neighbor triangle ///
+ dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter
+ dyFirstSuggestion = dy;
+ // if there is a neighbor triangle
+ if (!neighborNotFound)
+ {
+ neighborvertex_1 = neighborotri.Org();
+ neighborvertex_2 = neighborotri.Dest();
+ neighborvertex_3 = neighborotri.Apex();
+ // now calculate neighbor's circumcenter which is the voronoi site
+ neighborCircumcenter = Primitives.FindCircumcenter(neighborvertex_1.pt, neighborvertex_2.pt, neighborvertex_3.pt, ref xi_tmp, ref eta_tmp, false);
+ /// compute petal and Voronoi edge intersection ///
+ // in order to avoid degenerate cases, we need to do a vector based calculation for line
+ vector_x = (middleAngleCorner.Y - smallestAngleCorner.Y);//(-y, x)
+ vector_y = smallestAngleCorner.X - middleAngleCorner.X;
+ vector_x = myCircumcenter.X + vector_x;
+ vector_y = myCircumcenter.Y + vector_y;
+
+
+ // by intersecting bisectors you will end up with the one you want to walk on
+ // then this line and circle should be intersected
+ CircleLineIntersection(myCircumcenter.X, myCircumcenter.Y, vector_x, vector_y,
+ xPetalCtr, yPetalCtr, petalRadius, ref p);
+ /// choose the correct intersection point ///
+ // calculate middle point of the longest edge(bisector)
+ xMidOfLongestEdge = (middleAngleCorner.X + smallestAngleCorner.X) / 2.0;
+ yMidOfLongestEdge = (middleAngleCorner.Y + smallestAngleCorner.Y) / 2.0;
+ // we need to find correct intersection point, since line intersects circle twice
+ isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4],
+ myCircumcenter.X, myCircumcenter.Y, isObtuse);
+ // make sure which point is the correct one to be considered
+ if (isCorrect)
+ {
+ inter_x = p[3];
+ inter_y = p[4];
+ }
+ else
+ {
+ inter_x = p[1];
+ inter_y = p[2];
+ }
+ /// check if there is a Voronoi vertex between before intersection ///
+ // check if the voronoi vertex is between the intersection and circumcenter
+ PointBetweenPoints(inter_x, inter_y, myCircumcenter.X, myCircumcenter.Y,
+ neighborCircumcenter.X, neighborCircumcenter.Y, ref voronoiOrInter);
+
+ /// determine the point to be suggested ///
+ if (p[0] > 0.0)
+ { // there is at least one intersection point
+ // if it is between circumcenter and intersection
+ // if it returns 1.0 this means we have a voronoi vertex within feasible region
+ if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS)
+ {
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, neighborCircumcenter.X, neighborCircumcenter.Y))
+ {
+ // go back to circumcenter
+ dxFirstSuggestion = dx;
+ dyFirstSuggestion = dy;
+
+ }
+ else
+ { // we are not creating a bad triangle
+ // neighbor's circumcenter is suggested
+ dxFirstSuggestion = voronoiOrInter[2] - torg.pt.X;
+ dyFirstSuggestion = voronoiOrInter[3] - torg.pt.Y;
+ }
+
+ }
+ else
+ { // there is no voronoi vertex between intersection point and circumcenter
+ if (IsBadTriangleAngle(largestAngleCorner.X, largestAngleCorner.Y, middleAngleCorner.X, middleAngleCorner.Y, inter_x, inter_y))
+ {
+ // if it is inside feasible region, then insert v2
+ // apply perturbation
+ // find the distance between circumcenter and intersection point
+ d = Math.Sqrt((inter_x - myCircumcenter.X) * (inter_x - myCircumcenter.X) +
+ (inter_y - myCircumcenter.Y) * (inter_y - myCircumcenter.Y));
+ // then find the vector going from intersection point to circumcenter
+ ax = myCircumcenter.X - inter_x;
+ ay = myCircumcenter.Y - inter_y;
+
+ ax = ax / d;
+ ay = ay / d;
+ // now calculate the new intersection point which is perturbated towards the circumcenter
+ inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist);
+ inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist);
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, inter_x, inter_y))
+ {
+ // go back to circumcenter
+ dxFirstSuggestion = dx;
+ dyFirstSuggestion = dy;
+
+ }
+ else
+ {
+ // intersection point is suggested
+ dxFirstSuggestion = inter_x - torg.pt.X;
+ dyFirstSuggestion = inter_y - torg.pt.Y;
+
+ }
+ }
+ else
+ {
+ // intersection point is suggested
+ dxFirstSuggestion = inter_x - torg.pt.X;
+ dyFirstSuggestion = inter_y - torg.pt.Y;
+ }
+ }
+ /// if it is an acute triangle, check if it is a good enough location ///
+ // for acute triangle case, we need to check if it is ok to use either of them
+ if ((smallestAngleCorner.X - myCircumcenter.X) * (smallestAngleCorner.X - myCircumcenter.X) +
+ (smallestAngleCorner.Y - myCircumcenter.Y) * (smallestAngleCorner.Y - myCircumcenter.Y) >
+ lengthConst * ((smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y))))
+ {
+ // use circumcenter
+ dxFirstSuggestion = dx;
+ dyFirstSuggestion = dy;
+ }// else we stick to what we have found
+ }// intersection point
+
+ }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction
+
+ /// DO THE SAME THING FOR THE OTHER DIRECTION ///
+ /// find the third point of the neighbor triangle ///
+ neighborNotFound = GetNeighborsVertex(m, badotri, largestAngleCorner.X, largestAngleCorner.Y,
+ smallestAngleCorner.X, smallestAngleCorner.Y, ref thirdPoint, ref neighborotri);
+ /// find the circumcenter of the neighbor triangle ///
+ dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter
+ dySecondSuggestion = dy;
+ // if there is a neighbor triangle
+ if (!neighborNotFound)
+ {
+ neighborvertex_1 = neighborotri.Org();
+ neighborvertex_2 = neighborotri.Dest();
+ neighborvertex_3 = neighborotri.Apex();
+ // now calculate neighbor's circumcenter which is the voronoi site
+ neighborCircumcenter = Primitives.FindCircumcenter(neighborvertex_1.pt, neighborvertex_2.pt, neighborvertex_3.pt, ref xi_tmp, ref eta_tmp, false);
+
+ /// compute petal and Voronoi edge intersection ///
+ // in order to avoid degenerate cases, we need to do a vector based calculation for line
+ vector_x = (largestAngleCorner.Y - smallestAngleCorner.Y);//(-y, x)
+ vector_y = smallestAngleCorner.X - largestAngleCorner.X;
+ vector_x = myCircumcenter.X + vector_x;
+ vector_y = myCircumcenter.Y + vector_y;
+
+
+ // by intersecting bisectors you will end up with the one you want to walk on
+ // then this line and circle should be intersected
+ CircleLineIntersection(myCircumcenter.X, myCircumcenter.Y, vector_x, vector_y,
+ xPetalCtr, yPetalCtr, petalRadius, ref p);
+
+ /// choose the correct intersection point ///
+ // calcuwedgeslate middle point of the longest edge(bisector)
+ xMidOfMiddleEdge = (largestAngleCorner.X + smallestAngleCorner.X) / 2.0;
+ yMidOfMiddleEdge = (largestAngleCorner.Y + smallestAngleCorner.Y) / 2.0;
+ // we need to find correct intersection point, since line intersects circle twice
+ // this direction is always ACUTE
+ isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4],
+ myCircumcenter.X, myCircumcenter.Y, false/*(isObtuse+1)%2*/);
+ // make sure which point is the correct one to be considered
+ if (isCorrect)
+ {
+ inter_x = p[3];
+ inter_y = p[4];
+ }
+ else
+ {
+ inter_x = p[1];
+ inter_y = p[2];
+ }
+
+ /// check if there is a Voronoi vertex between before intersection ///
+ // check if the voronoi vertex is between the intersection and circumcenter
+ PointBetweenPoints(inter_x, inter_y, myCircumcenter.X, myCircumcenter.Y,
+ neighborCircumcenter.X, neighborCircumcenter.Y, ref voronoiOrInter);
+
+ /// determine the point to be suggested ///
+ if (p[0] > 0.0)
+ { // there is at least one intersection point
+ // if it is between circumcenter and intersection
+ // if it returns 1.0 this means we have a voronoi vertex within feasible region
+ if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS)
+ {
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, neighborCircumcenter.X, neighborCircumcenter.Y))
+ {
+ // go back to circumcenter
+ dxSecondSuggestion = dx;
+ dySecondSuggestion = dy;
+
+ }
+ else
+ { // we are not creating a bad triangle
+ // neighbor's circumcenter is suggested
+ dxSecondSuggestion = voronoiOrInter[2] - torg.pt.X;
+ dySecondSuggestion = voronoiOrInter[3] - torg.pt.Y;
+
+ }
+
+ }
+ else
+ { // there is no voronoi vertex between intersection point and circumcenter
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, inter_x, inter_y))
+ {
+ // if it is inside feasible region, then insert v2
+ // apply perturbation
+ // find the distance between circumcenter and intersection point
+ d = Math.Sqrt((inter_x - myCircumcenter.X) * (inter_x - myCircumcenter.X) +
+ (inter_y - myCircumcenter.Y) * (inter_y - myCircumcenter.Y));
+ // then find the vector going from intersection point to circumcenter
+ ax = myCircumcenter.X - inter_x;
+ ay = myCircumcenter.Y - inter_y;
+
+ ax = ax / d;
+ ay = ay / d;
+ // now calculate the new intersection point which is perturbated towards the circumcenter
+ inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist);
+ inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist);
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, inter_x, inter_y))
+ {
+ // go back to circumcenter
+ dxSecondSuggestion = dx;
+ dySecondSuggestion = dy;
+
+ }
+ else
+ {
+ // intersection point is suggested
+ dxSecondSuggestion = inter_x - torg.pt.X;
+ dySecondSuggestion = inter_y - torg.pt.Y;
+ }
+ }
+ else
+ {
+
+ // intersection point is suggested
+ dxSecondSuggestion = inter_x - torg.pt.X;
+ dySecondSuggestion = inter_y - torg.pt.Y;
+ }
+ }
+ /// if it is an acute triangle, check if it is a good enough location ///
+ // for acute triangle case, we need to check if it is ok to use either of them
+ if ((smallestAngleCorner.X - myCircumcenter.X) * (smallestAngleCorner.X - myCircumcenter.X) +
+ (smallestAngleCorner.Y - myCircumcenter.Y) * (smallestAngleCorner.Y - myCircumcenter.Y) >
+ lengthConst * ((smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y))))
+ {
+ // use circumcenter
+ dxSecondSuggestion = dx;
+ dySecondSuggestion = dy;
+ }// else we stick on what we have found
+ }
+ }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok
+ if (isObtuse)
+ {
+ //obtuse: do nothing
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+ }
+ else
+ { // acute : consider other direction
+ if (justAcute * ((smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y))) >
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)))
+ {
+ dx = dxSecondSuggestion;
+ dy = dySecondSuggestion;
+ }
+ else
+ {
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+ }
+
+ }// end if obtuse
+ }// end of relocation
+ }// end of almostGood
+
+ if (relocated <= 0)
+ {
+ circumcenter.pt.X = torg.pt.X + dx;
+ circumcenter.pt.Y = torg.pt.Y + dy;
+ }
+ else
+ {
+ circumcenter.pt.X = origin_x + dx;
+ circumcenter.pt.Y = origin_y + dy;
+ }
+
+ xi = (yao * dx - xao * dy) * (2.0 * denominator);
+ eta = (xdo * dy - ydo * dx) * (2.0 * denominator);
+
+ }
+
+ ///
+ /// Find a new location for a Steiner point.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ static void FindNewLocation(Mesh m,
+ Vertex torg, Vertex tdest, Vertex tapex,
+ Vertex circumcenter, ref double xi, ref double eta, bool offcenter, Otri badotri)
+ {
+
+ // for calculating the distances of the edges
+ double xdo, ydo, xao, yao, xda, yda;
+ double dodist, aodist, dadist;
+ // for exact calculation
+ double denominator;
+ double dx, dy, dxoff, dyoff;
+
+ ////////////////////////////// HALE'S VARIABLES //////////////////////////////
+ // keeps the difference of coordinates edge
+ double xShortestEdge = 0, yShortestEdge = 0, xMiddleEdge, yMiddleEdge, xLongestEdge, yLongestEdge;
+
+ // keeps the square of edge lengths
+ double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0;
+
+ // keeps the vertices according to the angle incident to that vertex in a triangle
+ Point2 smallestAngleCorner, middleAngleCorner, largestAngleCorner;
+
+ // keeps the type of orientation if the triangle
+ int orientation = 0;
+ // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter
+ Point2 myCircumcenter, neighborCircumcenter;
+
+ // keeps if bad triangle is almost good or not
+ int almostGood = 0;
+ // keeps the cosine of the largest angle
+ double cosMaxAngle;
+ bool isObtuse; // 1: obtuse 0: nonobtuse
+ // keeps the radius of petal
+ double petalRadius;
+ // for calculating petal center
+ double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge;
+ double dxcenter1, dycenter1, dxcenter2, dycenter2;
+ // for finding neighbor
+ Otri neighborotri = default(Otri);
+ double[] thirdPoint = new double[2];
+ //int neighborNotFound = -1;
+ // for keeping the vertices of the neighbor triangle
+ Vertex neighborvertex_1;
+ Vertex neighborvertex_2;
+ Vertex neighborvertex_3;
+ // dummy variables
+ double xi_tmp = 0, eta_tmp = 0;
+ //vertex thirdVertex;
+ // for petal intersection
+ double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y;
+ double[] p = new double[5], voronoiOrInter = new double[4];
+ bool isCorrect;
+
+ // for vector calculations in perturbation
+ double ax, ay, d;
+ double pertConst = 0.06; // perturbation constant
+
+ double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance
+ double justAcute = 1; // used for making the program working for one direction only
+ // for smoothing
+ int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point
+ double[] newloc = new double[2]; // new location suggested by smoothing
+ double origin_x = 0, origin_y = 0; // for keeping torg safe
+ Otri delotri; // keeping the original orientation for relocation process
+ // keeps the first and second direction suggested points
+ double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion;
+ // second direction variables
+ double xMidOfMiddleEdge, yMidOfMiddleEdge;
+
+ double minangle; // in order to make sure that the circumcircle of the bad triangle is greater than petal
+ // for calculating the slab
+ double linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y; // two points of the line
+ double line_inter_x = 0, line_inter_y = 0;
+ double line_vector_x, line_vector_y;
+ double[] line_p = new double[3]; // used for getting the return values of functions related to line intersection
+ double[] line_result = new double[4];
+ // intersection of slab and the petal
+ double petal_slab_inter_x_first, petal_slab_inter_y_first, petal_slab_inter_x_second, petal_slab_inter_y_second, x_1, y_1, x_2, y_2;
+ double petal_bisector_x, petal_bisector_y, dist;
+ double alpha;
+ bool neighborNotFound_first;
+ bool neighborNotFound_second;
+ ////////////////////////////// END OF HALE'S VARIABLES //////////////////////////////
+
+ Statistic.CircumcenterCount++;
+
+ // Compute the circumcenter of the triangle.
+ xdo = tdest.pt.X - torg.pt.X;
+ ydo = tdest.pt.Y - torg.pt.Y;
+ xao = tapex.pt.X - torg.pt.X;
+ yao = tapex.pt.Y - torg.pt.Y;
+ xda = tapex.pt.X - tdest.pt.X;
+ yda = tapex.pt.Y - tdest.pt.Y;
+ // keeps the square of the distances
+ dodist = xdo * xdo + ydo * ydo;
+ aodist = xao * xao + yao * yao;
+ dadist = (tdest.pt.X - tapex.pt.X) * (tdest.pt.X - tapex.pt.X) +
+ (tdest.pt.Y - tapex.pt.Y) * (tdest.pt.Y - tapex.pt.Y);
+ // checking if the user wanted exact arithmetic or not
+ if (Behavior.NoExact)
+ {
+ denominator = 0.5 / (xdo * yao - xao * ydo);
+ }
+ else
+ {
+ // Use the counterclockwise() routine to ensure a positive (and
+ // reasonably accurate) result, avoiding any possibility of
+ // division by zero.
+ denominator = 0.5 / Primitives.CounterClockwise(tdest.pt, tapex.pt, torg.pt);
+ // Don't count the above as an orientation test.
+ Statistic.CounterClockwiseCount--;
+ }
+ // calculate the circumcenter in terms of distance to origin point
+ dx = (yao * dodist - ydo * aodist) * denominator;
+ dy = (xdo * aodist - xao * dodist) * denominator;
+ // for debugging and for keeping circumcenter to use later
+ // coordinate value of the circumcenter
+ myCircumcenter.X = torg.pt.X + dx;
+ myCircumcenter.Y = torg.pt.Y + dy;
+
+ delotri = badotri; // save for later
+ ///////////////// FINDING THE ORIENTATION OF TRIANGLE //////////////////
+ // Find the (squared) length of the triangle's shortest edge. This
+ // serves as a conservative estimate of the insertion radius of the
+ // circumcenter's parent. The estimate is used to ensure that
+ // the algorithm terminates even if very small angles appear in
+ // the input PSLG.
+ // find the orientation of the triangle, basically shortest and longest edges
+ orientation = LongestShortestEdge(aodist, dadist, dodist);
+ //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]);
+ /////////////////////////////////////////////////////////////////////////////////////////////
+ // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist //
+ // middle: dadist // middle: aodist // middle: aodist //
+ // longest: dodist // longest: dodist // longest: dadist //
+ // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist //
+ // middle: dodist // middle: dodist // middle: dadist //
+ // longest: dadist // longest: aodist // longest: aodist //
+ /////////////////////////////////////////////////////////////////////////////////////////////
+
+ switch (orientation)
+ {
+ case 123: // assign necessary information
+ /// smallest angle corner: dest
+ /// largest angle corner: apex
+ xShortestEdge = xao; yShortestEdge = yao;
+ xMiddleEdge = xda; yMiddleEdge = yda;
+ xLongestEdge = xdo; yLongestEdge = ydo;
+
+ shortestEdgeDist = aodist;
+ middleEdgeDist = dadist;
+ longestEdgeDist = dodist;
+
+ smallestAngleCorner = tdest.pt;
+ middleAngleCorner = torg.pt;
+ largestAngleCorner = tapex.pt;
+ break;
+
+ case 132: // assign necessary information
+ /// smallest angle corner: dest
+ /// largest angle corner: org
+ xShortestEdge = xao; yShortestEdge = yao;
+ xMiddleEdge = xdo; yMiddleEdge = ydo;
+ xLongestEdge = xda; yLongestEdge = yda;
+
+ shortestEdgeDist = aodist;
+ middleEdgeDist = dodist;
+ longestEdgeDist = dadist;
+
+ smallestAngleCorner = tdest.pt;
+ middleAngleCorner = tapex.pt;
+ largestAngleCorner = torg.pt;
+
+ break;
+ case 213: // assign necessary information
+ /// smallest angle corner: org
+ /// largest angle corner: apex
+ xShortestEdge = xda; yShortestEdge = yda;
+ xMiddleEdge = xao; yMiddleEdge = yao;
+ xLongestEdge = xdo; yLongestEdge = ydo;
+
+ shortestEdgeDist = dadist;
+ middleEdgeDist = aodist;
+ longestEdgeDist = dodist;
+
+ smallestAngleCorner = torg.pt;
+ middleAngleCorner = tdest.pt;
+ largestAngleCorner = tapex.pt;
+ break;
+ case 231: // assign necessary information
+ /// smallest angle corner: org
+ /// largest angle corner: dest
+ xShortestEdge = xda; yShortestEdge = yda;
+ xMiddleEdge = xdo; yMiddleEdge = ydo;
+ xLongestEdge = xao; yLongestEdge = yao;
+
+ shortestEdgeDist = dadist;
+ middleEdgeDist = dodist;
+ longestEdgeDist = aodist;
+
+ smallestAngleCorner = torg.pt;
+ middleAngleCorner = tapex.pt;
+ largestAngleCorner = tdest.pt;
+ break;
+ case 312: // assign necessary information
+ /// smallest angle corner: apex
+ /// largest angle corner: org
+ xShortestEdge = xdo; yShortestEdge = ydo;
+ xMiddleEdge = xao; yMiddleEdge = yao;
+ xLongestEdge = xda; yLongestEdge = yda;
+
+ shortestEdgeDist = dodist;
+ middleEdgeDist = aodist;
+ longestEdgeDist = dadist;
+
+ smallestAngleCorner = tapex.pt;
+ middleAngleCorner = tdest.pt;
+ largestAngleCorner = torg.pt;
+ break;
+ case 321: // assign necessary information
+ default: // TODO: is this safe?
+ /// smallest angle corner: apex
+ /// largest angle corner: dest
+ xShortestEdge = xdo; yShortestEdge = ydo;
+ xMiddleEdge = xda; yMiddleEdge = yda;
+ xLongestEdge = xao; yLongestEdge = yao;
+
+ shortestEdgeDist = dodist;
+ middleEdgeDist = dadist;
+ longestEdgeDist = aodist;
+
+ smallestAngleCorner = tapex.pt;
+ middleAngleCorner = torg.pt;
+ largestAngleCorner = tdest.pt;
+ break;
+
+ }// end of switch
+ // check for offcenter condition
+ if (offcenter && (Behavior.Offconstant > 0.0))
+ {
+ // origin has the smallest angle
+ if (orientation == 213 || orientation == 231)
+ {
+ // Find the position of the off-center, as described by Alper Ungor.
+ dxoff = 0.5 * xShortestEdge - Behavior.Offconstant * yShortestEdge;
+ dyoff = 0.5 * yShortestEdge + Behavior.Offconstant * xShortestEdge;
+ // If the off-center is closer to destination than the
+ // circumcenter, use the off-center instead.
+ /// doubleLY BAD CASE ///
+ if (dxoff * dxoff + dyoff * dyoff <
+ (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo))
+ {
+ dx = xdo + dxoff;
+ dy = ydo + dyoff;
+ }
+ /// ALMOST GOOD CASE ///
+ else
+ {
+ almostGood = 1;
+ }
+ // destination has the smallest angle
+ }
+ else if (orientation == 123 || orientation == 132)
+ {
+ // Find the position of the off-center, as described by Alper Ungor.
+ dxoff = 0.5 * xShortestEdge + Behavior.Offconstant * yShortestEdge;
+ dyoff = 0.5 * yShortestEdge - Behavior.Offconstant * xShortestEdge;
+ // If the off-center is closer to the origin than the
+ // circumcenter, use the off-center instead.
+ /// doubleLY BAD CASE ///
+ if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy)
+ {
+ dx = dxoff;
+ dy = dyoff;
+ }
+ /// ALMOST GOOD CASE ///
+ else
+ {
+ almostGood = 1;
+ }
+ // apex has the smallest angle
+ }
+ else
+ {//orientation == 312 || orientation == 321
+ // Find the position of the off-center, as described by Alper Ungor.
+ dxoff = 0.5 * xShortestEdge - Behavior.Offconstant * yShortestEdge;
+ dyoff = 0.5 * yShortestEdge + Behavior.Offconstant * xShortestEdge;
+ // If the off-center is closer to the origin than the
+ // circumcenter, use the off-center instead.
+ /// doubleLY BAD CASE ///
+ if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy)
+ {
+ dx = dxoff;
+ dy = dyoff;
+ }
+ /// ALMOST GOOD CASE ///
+ else
+ {
+ almostGood = 1;
+ }
+ }
+ }
+ // if the bad triangle is almost good, apply our approach
+ if (almostGood == 1)
+ {
+
+ /// calculate cosine of largest angle ///
+ cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist));
+ if (cosMaxAngle < 0.0)
+ {
+ // obtuse
+ isObtuse = true;
+ }
+ else if (Math.Abs(cosMaxAngle - 0.0) <= EPS)
+ {
+ // right triangle (largest angle is 90 degrees)
+ isObtuse = true;
+ }
+ else
+ {
+ // nonobtuse
+ isObtuse = false;
+ }
+ /// RELOCATION (LOCAL SMOOTHING) ///
+ /// check for possible relocation of one of triangle's points ///
+ relocated = DoSmoothing(m, delotri, torg, tdest, tapex, ref newloc);
+ /// if relocation is possible, delete that vertex and insert a vertex at the new location ///
+ if (relocated > 0)
+ {
+ Statistic.RelocationCount++;
+
+ dx = newloc[0] - torg.pt.X;
+ dy = newloc[1] - torg.pt.Y;
+ origin_x = torg.pt.X; // keep for later use
+ origin_y = torg.pt.Y;
+ switch (relocated)
+ {
+ case 1:
+ //printf("Relocate: (%f,%f)\n", torg[0],torg[1]);
+ m.DeleteVertex(ref delotri);
+ break;
+ case 2:
+ //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]);
+ delotri.LnextSelf();
+ m.DeleteVertex(ref delotri);
+ break;
+ case 3:
+ //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]);
+ delotri.LprevSelf();
+ m.DeleteVertex(ref delotri);
+ break;
+ }
+ }
+ else
+ {
+ // calculate radius of the petal according to angle constraint
+ // first find the visible region, PETAL
+ // find the center of the circle and radius
+ // choose minimum angle as the maximum of quality angle and the minimum angle of the bad triangle
+ minangle = Math.Acos((middleEdgeDist + longestEdgeDist - shortestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(longestEdgeDist))) * 180.0 / Math.PI;
+ if (Behavior.MinAngle > minangle)
+ {
+ minangle = Behavior.MinAngle;
+ }
+ else
+ {
+ minangle = minangle + 0.5;
+ }
+ petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(minangle * Math.PI / 180.0));
+ /// compute two possible centers of the petal ///
+ // finding the center
+ // first find the middle point of smallest edge
+ xMidOfShortestEdge = (middleAngleCorner.X + largestAngleCorner.X) / 2.0;
+ yMidOfShortestEdge = (middleAngleCorner.Y + largestAngleCorner.Y) / 2.0;
+ // two possible centers
+ xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.Y -
+ largestAngleCorner.Y) / Math.Sqrt(shortestEdgeDist);
+ yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.X -
+ middleAngleCorner.X) / Math.Sqrt(shortestEdgeDist);
+
+ xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.Y -
+ largestAngleCorner.Y) / Math.Sqrt(shortestEdgeDist);
+ yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.X -
+ middleAngleCorner.X) / Math.Sqrt(shortestEdgeDist);
+ // find the correct circle since there will be two possible circles
+ // calculate the distance to smallest angle corner
+ dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.X) * (xPetalCtr_1 - smallestAngleCorner.X);
+ dycenter1 = (yPetalCtr_1 - smallestAngleCorner.Y) * (yPetalCtr_1 - smallestAngleCorner.Y);
+ dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.X) * (xPetalCtr_2 - smallestAngleCorner.X);
+ dycenter2 = (yPetalCtr_2 - smallestAngleCorner.Y) * (yPetalCtr_2 - smallestAngleCorner.Y);
+
+ // whichever is closer to smallest angle corner, it must be the center
+ if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2)
+ {
+ xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1;
+ }
+ else
+ {
+ xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2;
+ }
+ /// find the third point of the neighbor triangle ///
+ neighborNotFound_first = GetNeighborsVertex(m, badotri, middleAngleCorner.X, middleAngleCorner.Y,
+ smallestAngleCorner.X, smallestAngleCorner.Y, ref thirdPoint, ref neighborotri);
+ /// find the circumcenter of the neighbor triangle ///
+ dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter
+ dyFirstSuggestion = dy;
+ /// before checking the neighbor, find the petal and slab intersections ///
+ // calculate the intersection point of the petal and the slab lines
+ // first find the vector
+ // distance between xmid and petal center
+ dist = Math.Sqrt((xPetalCtr - xMidOfShortestEdge) * (xPetalCtr - xMidOfShortestEdge) + (yPetalCtr - yMidOfShortestEdge) * (yPetalCtr - yMidOfShortestEdge));
+ // find the unit vector goes from mid point to petal center
+ line_vector_x = (xPetalCtr - xMidOfShortestEdge) / dist;
+ line_vector_y = (yPetalCtr - yMidOfShortestEdge) / dist;
+ // find the third point other than p and q
+ petal_bisector_x = xPetalCtr + line_vector_x * petalRadius;
+ petal_bisector_y = yPetalCtr + line_vector_y * petalRadius;
+ alpha = (2.0 * Behavior.MaxAngle + minangle - 180.0) * Math.PI / 180.0;
+ // rotate the vector cw around the petal center
+ x_1 = petal_bisector_x * Math.Cos(alpha) + petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) - yPetalCtr * Math.Sin(alpha);
+ y_1 = -petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr + xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha);
+ // rotate the vector ccw around the petal center
+ x_2 = petal_bisector_x * Math.Cos(alpha) - petal_bisector_y * Math.Sin(alpha) + xPetalCtr - xPetalCtr * Math.Cos(alpha) + yPetalCtr * Math.Sin(alpha);
+ y_2 = petal_bisector_x * Math.Sin(alpha) + petal_bisector_y * Math.Cos(alpha) + yPetalCtr - xPetalCtr * Math.Sin(alpha) - yPetalCtr * Math.Cos(alpha);
+ // we need to find correct intersection point, since there are two possibilities
+ // weather it is obtuse/acute the one closer to the minimum angle corner is the first direction
+ isCorrect = ChooseCorrectPoint(x_2, y_2, middleAngleCorner.X, middleAngleCorner.Y, x_1, y_1, true);
+ // make sure which point is the correct one to be considered
+ if (isCorrect)
+ {
+ petal_slab_inter_x_first = x_1;
+ petal_slab_inter_y_first = y_1;
+ petal_slab_inter_x_second = x_2;
+ petal_slab_inter_y_second = y_2;
+ }
+ else
+ {
+ petal_slab_inter_x_first = x_2;
+ petal_slab_inter_y_first = y_2;
+ petal_slab_inter_x_second = x_1;
+ petal_slab_inter_y_second = y_1;
+ }
+ /// choose the correct intersection point ///
+ // calculate middle point of the longest edge(bisector)
+ xMidOfLongestEdge = (middleAngleCorner.X + smallestAngleCorner.X) / 2.0;
+ yMidOfLongestEdge = (middleAngleCorner.Y + smallestAngleCorner.Y) / 2.0;
+ // if there is a neighbor triangle
+ if (!neighborNotFound_first)
+ {
+ neighborvertex_1=neighborotri.Org();
+ neighborvertex_2=neighborotri.Dest();
+ neighborvertex_3=neighborotri.Apex();
+ // now calculate neighbor's circumcenter which is the voronoi site
+ neighborCircumcenter = Primitives.FindCircumcenter(neighborvertex_1.pt, neighborvertex_2.pt, neighborvertex_3.pt, ref xi_tmp, ref eta_tmp, false);
+ /// compute petal and Voronoi edge intersection ///
+ // in order to avoid degenerate cases, we need to do a vector based calculation for line
+ vector_x = (middleAngleCorner.Y - smallestAngleCorner.Y);//(-y, x)
+ vector_y = smallestAngleCorner.X - middleAngleCorner.X;
+ vector_x = myCircumcenter.X + vector_x;
+ vector_y = myCircumcenter.Y + vector_y;
+ // by intersecting bisectors you will end up with the one you want to walk on
+ // then this line and circle should be intersected
+ CircleLineIntersection(myCircumcenter.X, myCircumcenter.Y, vector_x, vector_y,
+ xPetalCtr, yPetalCtr, petalRadius, ref p);
+ // we need to find correct intersection point, since line intersects circle twice
+ isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4],
+ myCircumcenter.X, myCircumcenter.Y, isObtuse);
+ // make sure which point is the correct one to be considered
+ if (isCorrect)
+ {
+ inter_x = p[3];
+ inter_y = p[4];
+ }
+ else
+ {
+ inter_x = p[1];
+ inter_y = p[2];
+ }
+ //----------------------hale new first direction: for slab calculation---------------//
+ // calculate the intersection of angle lines and Voronoi
+ linepnt1_x = middleAngleCorner.X;
+ linepnt1_y = middleAngleCorner.Y;
+ // vector from middleAngleCorner to largestAngleCorner
+ line_vector_x = largestAngleCorner.X - middleAngleCorner.X;
+ line_vector_y = largestAngleCorner.Y - middleAngleCorner.Y;
+ // rotate the vector around middleAngleCorner in cw by maxangle degrees
+ linepnt2_x = petal_slab_inter_x_first;
+ linepnt2_y = petal_slab_inter_y_first;
+ // now calculate the intersection of two lines
+ LineLineIntersection(myCircumcenter.X, myCircumcenter.Y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p);
+ // check if there is a suitable intersection
+ if (line_p[0] > 0.0)
+ {
+ line_inter_x = line_p[1];
+ line_inter_y = line_p[2];
+ }
+ else
+ {
+ // for debugging (to make sure)
+ //printf("1) No intersection between two lines!!!\n");
+ //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.X,myCircumcenter.Y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y);
+ }
+
+ //---------------------------------------------------------------------//
+ /// check if there is a Voronoi vertex between before intersection ///
+ // check if the voronoi vertex is between the intersection and circumcenter
+ PointBetweenPoints(inter_x, inter_y, myCircumcenter.X, myCircumcenter.Y,
+ neighborCircumcenter.X, neighborCircumcenter.Y, ref voronoiOrInter);
+
+ /// determine the point to be suggested ///
+ if (p[0] > 0.0)
+ { // there is at least one intersection point
+ // if it is between circumcenter and intersection
+ // if it returns 1.0 this means we have a voronoi vertex within feasible region
+ if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS)
+ {
+ //-----------------hale new continues 1------------------//
+ // now check if the line intersection is between cc and voronoi
+ PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.X, myCircumcenter.Y, line_inter_x, line_inter_y, ref line_result);
+ if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0)
+ {
+ // check if we can go further by picking the slab line and petal intersection
+ // calculate the distance to the smallest angle corner
+ // check if we create a bad triangle or not
+ if (((smallestAngleCorner.X - petal_slab_inter_x_first) * (smallestAngleCorner.X - petal_slab_inter_x_first) +
+ (smallestAngleCorner.Y - petal_slab_inter_y_first) * (smallestAngleCorner.Y - petal_slab_inter_y_first) >
+ lengthConst * ((smallestAngleCorner.X - line_inter_x) *
+ (smallestAngleCorner.X - line_inter_x) +
+ (smallestAngleCorner.Y - line_inter_y) *
+ (smallestAngleCorner.Y - line_inter_y)))
+ && (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, petal_slab_inter_x_first, petal_slab_inter_y_first))
+ && MinDistanceToNeighbor(m, petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(m, line_inter_x, line_inter_y, ref neighborotri))
+ {
+ //
+ /// check the neighbor's vertices also, which one if better
+
+ //slab and petal intersection is advised
+ dxFirstSuggestion = petal_slab_inter_x_first - torg.pt.X;
+ dyFirstSuggestion = petal_slab_inter_y_first - torg.pt.Y;
+ }
+ else
+ { // slab intersection point is further away
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, line_inter_x, line_inter_y))
+ {
+ // apply perturbation
+ // find the distance between circumcenter and intersection point
+ d = Math.Sqrt((line_inter_x - myCircumcenter.X) * (line_inter_x - myCircumcenter.X) +
+ (line_inter_y - myCircumcenter.Y) * (line_inter_y - myCircumcenter.Y));
+ // then find the vector going from intersection point to circumcenter
+ ax = myCircumcenter.X - line_inter_x;
+ ay = myCircumcenter.Y - line_inter_y;
+
+ ax = ax / d;
+ ay = ay / d;
+ // now calculate the new intersection point which is perturbated towards the circumcenter
+ line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist);
+ line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist);
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, line_inter_x, line_inter_y))
+ {
+ // go back to circumcenter
+ dxFirstSuggestion = dx;
+ dyFirstSuggestion = dy;
+
+
+ }
+ else
+ {
+ // intersection point is suggested
+ dxFirstSuggestion = line_inter_x - torg.pt.X;
+ dyFirstSuggestion = line_inter_y - torg.pt.Y;
+
+
+ }
+
+
+ }
+ else
+ {// we are not creating a bad triangle
+ // slab intersection is advised
+ dxFirstSuggestion = line_result[2] - torg.pt.X;
+ dyFirstSuggestion = line_result[3] - torg.pt.Y;
+
+ }
+ }
+ //------------------------------------------------------//
+ }
+ else
+ {
+ /// NOW APPLY A BREADTH-FIRST SEARCH ON THE VORONOI
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, neighborCircumcenter.X, neighborCircumcenter.Y))
+ {
+ // go back to circumcenter
+ dxFirstSuggestion = dx;
+ dyFirstSuggestion = dy;
+
+
+ }
+ else
+ {
+ // we are not creating a bad triangle
+ // neighbor's circumcenter is suggested
+ dxFirstSuggestion = voronoiOrInter[2] - torg.pt.X;
+ dyFirstSuggestion = voronoiOrInter[3] - torg.pt.Y;
+
+
+ }
+ }
+
+ }
+ else
+ { // there is no voronoi vertex between intersection point and circumcenter
+ //-----------------hale new continues 2-----------------//
+ // now check if the line intersection is between cc and intersection point
+ PointBetweenPoints(inter_x, inter_y, myCircumcenter.X, myCircumcenter.Y, line_inter_x, line_inter_y, ref line_result);
+ if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0)
+ {
+ // check if we can go further by picking the slab line and petal intersection
+ // calculate the distance to the smallest angle corner
+ if (((smallestAngleCorner.X - petal_slab_inter_x_first) * (smallestAngleCorner.X - petal_slab_inter_x_first) +
+ (smallestAngleCorner.Y - petal_slab_inter_y_first) * (smallestAngleCorner.Y - petal_slab_inter_y_first) >
+ lengthConst * ((smallestAngleCorner.X - line_inter_x) *
+ (smallestAngleCorner.X - line_inter_x) +
+ (smallestAngleCorner.Y - line_inter_y) *
+ (smallestAngleCorner.Y - line_inter_y)))
+ && (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, petal_slab_inter_x_first, petal_slab_inter_y_first))
+ && MinDistanceToNeighbor(m, petal_slab_inter_x_first, petal_slab_inter_y_first, ref neighborotri) > MinDistanceToNeighbor(m, line_inter_x, line_inter_y, ref neighborotri))
+ {
+ //slab and petal intersection is advised
+ dxFirstSuggestion = petal_slab_inter_x_first - torg.pt.X;
+ dyFirstSuggestion = petal_slab_inter_y_first - torg.pt.Y;
+
+ }
+ else
+ { // slab intersection point is further away
+ if (IsBadTriangleAngle(largestAngleCorner.X, largestAngleCorner.Y, middleAngleCorner.X, middleAngleCorner.Y, line_inter_x, line_inter_y))
+ {
+ // apply perturbation
+ // find the distance between circumcenter and intersection point
+ d = Math.Sqrt((line_inter_x - myCircumcenter.X) * (line_inter_x - myCircumcenter.X) +
+ (line_inter_y - myCircumcenter.Y) * (line_inter_y - myCircumcenter.Y));
+ // then find the vector going from intersection point to circumcenter
+ ax = myCircumcenter.X - line_inter_x;
+ ay = myCircumcenter.Y - line_inter_y;
+
+ ax = ax / d;
+ ay = ay / d;
+ // now calculate the new intersection point which is perturbated towards the circumcenter
+ line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist);
+ line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist);
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, line_inter_x, line_inter_y))
+ {
+ // go back to circumcenter
+ dxFirstSuggestion = dx;
+ dyFirstSuggestion = dy;
+
+ }
+ else
+ {
+ // intersection point is suggested
+ dxFirstSuggestion = line_inter_x - torg.pt.X;
+ dyFirstSuggestion = line_inter_y - torg.pt.Y;
+
+ }
+
+
+ }
+ else
+ {// we are not creating a bad triangle
+ // slab intersection is advised
+ dxFirstSuggestion = line_result[2] - torg.pt.X;
+ dyFirstSuggestion = line_result[3] - torg.pt.Y;
+
+ }
+ }
+ //------------------------------------------------------//
+
+ }
+ else
+ {
+
+ if (IsBadTriangleAngle(largestAngleCorner.X, largestAngleCorner.Y, middleAngleCorner.X, middleAngleCorner.Y, inter_x, inter_y))
+ {
+ //printf("testtriangle returned false! bad triangle\n");
+ // if it is inside feasible region, then insert v2
+ // apply perturbation
+ // find the distance between circumcenter and intersection point
+ d = Math.Sqrt((inter_x - myCircumcenter.X) * (inter_x - myCircumcenter.X) +
+ (inter_y - myCircumcenter.Y) * (inter_y - myCircumcenter.Y));
+ // then find the vector going from intersection point to circumcenter
+ ax = myCircumcenter.X - inter_x;
+ ay = myCircumcenter.Y - inter_y;
+
+ ax = ax / d;
+ ay = ay / d;
+ // now calculate the new intersection point which is perturbated towards the circumcenter
+ inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist);
+ inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist);
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, inter_x, inter_y))
+ {
+ // go back to circumcenter
+ dxFirstSuggestion = dx;
+ dyFirstSuggestion = dy;
+
+
+
+ }
+ else
+ {
+ // intersection point is suggested
+ dxFirstSuggestion = inter_x - torg.pt.X;
+ dyFirstSuggestion = inter_y - torg.pt.Y;
+
+ }
+ }
+ else
+ {
+ // intersection point is suggested
+ dxFirstSuggestion = inter_x - torg.pt.X;
+ dyFirstSuggestion = inter_y - torg.pt.Y;
+
+ }
+ }
+ }
+ /// if it is an acute triangle, check if it is a good enough location ///
+ // for acute triangle case, we need to check if it is ok to use either of them
+ if ((smallestAngleCorner.X - myCircumcenter.X) * (smallestAngleCorner.X - myCircumcenter.X) +
+ (smallestAngleCorner.Y - myCircumcenter.Y) * (smallestAngleCorner.Y - myCircumcenter.Y) >
+ lengthConst * ((smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y))))
+ {
+ // use circumcenter
+ dxFirstSuggestion = dx;
+ dyFirstSuggestion = dy;
+
+ }// else we stick to what we have found
+ }// intersection point
+
+ }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction
+
+ /// DO THE SAME THING FOR THE OTHER DIRECTION ///
+ /// find the third point of the neighbor triangle ///
+ neighborNotFound_second = GetNeighborsVertex(m, badotri, largestAngleCorner.X, largestAngleCorner.Y,
+ smallestAngleCorner.X, smallestAngleCorner.Y, ref thirdPoint, ref neighborotri);
+ /// find the circumcenter of the neighbor triangle ///
+ dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter
+ dySecondSuggestion = dy;
+
+ /// choose the correct intersection point ///
+ // calculate middle point of the longest edge(bisector)
+ xMidOfMiddleEdge = (largestAngleCorner.X + smallestAngleCorner.X) / 2.0;
+ yMidOfMiddleEdge = (largestAngleCorner.Y + smallestAngleCorner.Y) / 2.0;
+ // if there is a neighbor triangle
+ if (!neighborNotFound_second)
+ {
+ neighborvertex_1=neighborotri.Org();
+ neighborvertex_2=neighborotri.Dest();
+ neighborvertex_3=neighborotri.Apex();
+ // now calculate neighbor's circumcenter which is the voronoi site
+ neighborCircumcenter = Primitives.FindCircumcenter(neighborvertex_1.pt, neighborvertex_2.pt, neighborvertex_3.pt, ref xi_tmp, ref eta_tmp, false);
+
+ /// compute petal and Voronoi edge intersection ///
+ // in order to avoid degenerate cases, we need to do a vector based calculation for line
+ vector_x = (largestAngleCorner.Y - smallestAngleCorner.Y);//(-y, x)
+ vector_y = smallestAngleCorner.X - largestAngleCorner.X;
+ vector_x = myCircumcenter.X + vector_x;
+ vector_y = myCircumcenter.Y + vector_y;
+
+
+ // by intersecting bisectors you will end up with the one you want to walk on
+ // then this line and circle should be intersected
+ CircleLineIntersection(myCircumcenter.X, myCircumcenter.Y, vector_x, vector_y,
+ xPetalCtr, yPetalCtr, petalRadius, ref p);
+
+ // we need to find correct intersection point, since line intersects circle twice
+ // this direction is always ACUTE
+ isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4],
+ myCircumcenter.X, myCircumcenter.Y, false/*(isObtuse+1)%2*/);
+ // make sure which point is the correct one to be considered
+ if (isCorrect)
+ {
+ inter_x = p[3];
+ inter_y = p[4];
+ }
+ else
+ {
+ inter_x = p[1];
+ inter_y = p[2];
+ }
+ //----------------------hale new second direction:for slab calculation---------------//
+ // calculate the intersection of angle lines and Voronoi
+ linepnt1_x = largestAngleCorner.X;
+ linepnt1_y = largestAngleCorner.Y;
+ // vector from largestAngleCorner to middleAngleCorner
+ line_vector_x = middleAngleCorner.X - largestAngleCorner.X;
+ line_vector_y = middleAngleCorner.Y - largestAngleCorner.Y;
+ // rotate the vector around largestAngleCorner in ccw by maxangle degrees
+ linepnt2_x = petal_slab_inter_x_second;
+ linepnt2_y = petal_slab_inter_y_second;
+ // now calculate the intersection of two lines
+ LineLineIntersection(myCircumcenter.X, myCircumcenter.Y, vector_x, vector_y, linepnt1_x, linepnt1_y, linepnt2_x, linepnt2_y, ref line_p);
+ // check if there is a suitable intersection
+ if (line_p[0] > 0.0)
+ {
+ line_inter_x = line_p[1];
+ line_inter_y = line_p[2];
+ }
+ else
+ {
+ // for debugging (to make sure)
+ //printf("1) No intersection between two lines!!!\n");
+ //printf("(%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f) (%.14f,%.14f)\n",myCircumcenter.X,myCircumcenter.Y,vector_x,vector_y,linepnt1_x,linepnt1_y,linepnt2_x,linepnt2_y);
+ }
+ //---------------------------------------------------------------------//
+ /// check if there is a Voronoi vertex between before intersection ///
+ // check if the voronoi vertex is between the intersection and circumcenter
+ PointBetweenPoints(inter_x, inter_y, myCircumcenter.X, myCircumcenter.Y,
+ neighborCircumcenter.X, neighborCircumcenter.Y, ref voronoiOrInter);
+ /// determine the point to be suggested ///
+ if (p[0] > 0.0)
+ { // there is at least one intersection point
+ // if it is between circumcenter and intersection
+ // if it returns 1.0 this means we have a voronoi vertex within feasible region
+ if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS)
+ {
+ //-----------------hale new continues 1------------------//
+ // now check if the line intersection is between cc and voronoi
+ PointBetweenPoints(voronoiOrInter[2], voronoiOrInter[3], myCircumcenter.X, myCircumcenter.Y, line_inter_x, line_inter_y, ref line_result);
+ if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0)
+ {
+ // check if we can go further by picking the slab line and petal intersection
+ // calculate the distance to the smallest angle corner
+ //
+ if (((smallestAngleCorner.X - petal_slab_inter_x_second) * (smallestAngleCorner.X - petal_slab_inter_x_second) +
+ (smallestAngleCorner.Y - petal_slab_inter_y_second) * (smallestAngleCorner.Y - petal_slab_inter_y_second) >
+ lengthConst * ((smallestAngleCorner.X - line_inter_x) *
+ (smallestAngleCorner.X - line_inter_x) +
+ (smallestAngleCorner.Y - line_inter_y) *
+ (smallestAngleCorner.Y - line_inter_y)))
+ && (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, petal_slab_inter_x_second, petal_slab_inter_y_second))
+ && MinDistanceToNeighbor(m, petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(m, line_inter_x, line_inter_y, ref neighborotri))
+ {
+ // slab and petal intersection is advised
+ dxSecondSuggestion = petal_slab_inter_x_second - torg.pt.X;
+ dySecondSuggestion = petal_slab_inter_y_second - torg.pt.Y;
+
+
+ }
+ else
+ { // slab intersection point is further away
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, line_inter_x, line_inter_y))
+ {
+ // apply perturbation
+ // find the distance between circumcenter and intersection point
+ d = Math.Sqrt((line_inter_x - myCircumcenter.X) * (line_inter_x - myCircumcenter.X) +
+ (line_inter_y - myCircumcenter.Y) * (line_inter_y - myCircumcenter.Y));
+ // then find the vector going from intersection point to circumcenter
+ ax = myCircumcenter.X - line_inter_x;
+ ay = myCircumcenter.Y - line_inter_y;
+
+ ax = ax / d;
+ ay = ay / d;
+ // now calculate the new intersection point which is perturbated towards the circumcenter
+ line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist);
+ line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist);
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, line_inter_x, line_inter_y))
+ {
+ // go back to circumcenter
+ dxSecondSuggestion = dx;
+ dySecondSuggestion = dy;
+
+
+ }
+ else
+ {
+ // intersection point is suggested
+ dxSecondSuggestion = line_inter_x - torg.pt.X;
+ dySecondSuggestion = line_inter_y - torg.pt.Y;
+
+ }
+
+
+ }
+ else
+ {// we are not creating a bad triangle
+ // slab intersection is advised
+ dxSecondSuggestion = line_result[2] - torg.pt.X;
+ dySecondSuggestion = line_result[3] - torg.pt.Y;
+
+ }
+ }
+ //------------------------------------------------------//
+ }
+ else
+ {
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, neighborCircumcenter.X, neighborCircumcenter.Y))
+ {
+ // go back to circumcenter
+ dxSecondSuggestion = dx;
+ dySecondSuggestion = dy;
+
+
+ }
+ else
+ { // we are not creating a bad triangle
+ // neighbor's circumcenter is suggested
+ dxSecondSuggestion = voronoiOrInter[2] - torg.pt.X;
+ dySecondSuggestion = voronoiOrInter[3] - torg.pt.Y;
+
+
+ }
+ }
+ }
+ else
+ { // there is no voronoi vertex between intersection point and circumcenter
+ //-----------------hale new continues 2-----------------//
+ // now check if the line intersection is between cc and intersection point
+ PointBetweenPoints(inter_x, inter_y, myCircumcenter.X, myCircumcenter.Y, line_inter_x, line_inter_y, ref line_result);
+ if (Math.Abs(line_result[0] - 1.0) <= EPS && line_p[0] > 0.0)
+ {
+ // check if we can go further by picking the slab line and petal intersection
+ // calculate the distance to the smallest angle corner
+ if (((smallestAngleCorner.X - petal_slab_inter_x_second) * (smallestAngleCorner.X - petal_slab_inter_x_second) +
+ (smallestAngleCorner.Y - petal_slab_inter_y_second) * (smallestAngleCorner.Y - petal_slab_inter_y_second) >
+ lengthConst * ((smallestAngleCorner.X - line_inter_x) *
+ (smallestAngleCorner.X - line_inter_x) +
+ (smallestAngleCorner.Y - line_inter_y) *
+ (smallestAngleCorner.Y - line_inter_y)))
+ && (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, petal_slab_inter_x_second, petal_slab_inter_y_second))
+ && MinDistanceToNeighbor(m, petal_slab_inter_x_second, petal_slab_inter_y_second, ref neighborotri) > MinDistanceToNeighbor(m, line_inter_x, line_inter_y, ref neighborotri))
+ {
+ // slab and petal intersection is advised
+ dxSecondSuggestion = petal_slab_inter_x_second - torg.pt.X;
+ dySecondSuggestion = petal_slab_inter_y_second - torg.pt.Y;
+
+ }
+ else
+ { // slab intersection point is further away ;
+ if (IsBadTriangleAngle(largestAngleCorner.X, largestAngleCorner.Y, middleAngleCorner.X, middleAngleCorner.Y, line_inter_x, line_inter_y))
+ {
+ // apply perturbation
+ // find the distance between circumcenter and intersection point
+ d = Math.Sqrt((line_inter_x - myCircumcenter.X) * (line_inter_x - myCircumcenter.X) +
+ (line_inter_y - myCircumcenter.Y) * (line_inter_y - myCircumcenter.Y));
+ // then find the vector going from intersection point to circumcenter
+ ax = myCircumcenter.X - line_inter_x;
+ ay = myCircumcenter.Y - line_inter_y;
+
+ ax = ax / d;
+ ay = ay / d;
+ // now calculate the new intersection point which is perturbated towards the circumcenter
+ line_inter_x = line_inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist);
+ line_inter_y = line_inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist);
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, line_inter_x, line_inter_y))
+ {
+ // go back to circumcenter
+ dxSecondSuggestion = dx;
+ dySecondSuggestion = dy;
+
+ }
+ else
+ {
+ // intersection point is suggested
+ dxSecondSuggestion = line_inter_x - torg.pt.X;
+ dySecondSuggestion = line_inter_y - torg.pt.Y;
+
+ }
+
+
+ }
+ else
+ {// we are not creating a bad triangle
+ // slab intersection is advised
+ dxSecondSuggestion = line_result[2] - torg.pt.X;
+ dySecondSuggestion = line_result[3] - torg.pt.Y;
+
+ }
+ }
+ //------------------------------------------------------//
+
+ }
+ else
+ {
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, inter_x, inter_y))
+ {
+ // if it is inside feasible region, then insert v2
+ // apply perturbation
+ // find the distance between circumcenter and intersection point
+ d = Math.Sqrt((inter_x - myCircumcenter.X) * (inter_x - myCircumcenter.X) +
+ (inter_y - myCircumcenter.Y) * (inter_y - myCircumcenter.Y));
+ // then find the vector going from intersection point to circumcenter
+ ax = myCircumcenter.X - inter_x;
+ ay = myCircumcenter.Y - inter_y;
+
+ ax = ax / d;
+ ay = ay / d;
+ // now calculate the new intersection point which is perturbated towards the circumcenter
+ inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist);
+ inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist);
+ if (IsBadTriangleAngle(middleAngleCorner.X, middleAngleCorner.Y, largestAngleCorner.X, largestAngleCorner.Y, inter_x, inter_y))
+ {
+ // go back to circumcenter
+ dxSecondSuggestion = dx;
+ dySecondSuggestion = dy;
+
+
+ }
+ else
+ {
+ // intersection point is suggested
+ dxSecondSuggestion = inter_x - torg.pt.X;
+ dySecondSuggestion = inter_y - torg.pt.Y;
+
+ }
+ }
+ else
+ {
+
+ // intersection point is suggested
+ dxSecondSuggestion = inter_x - torg.pt.X;
+ dySecondSuggestion = inter_y - torg.pt.Y;
+
+ }
+ }
+ }
+
+ /// if it is an acute triangle, check if it is a good enough location ///
+ // for acute triangle case, we need to check if it is ok to use either of them
+ if ((smallestAngleCorner.X - myCircumcenter.X) * (smallestAngleCorner.X - myCircumcenter.X) +
+ (smallestAngleCorner.Y - myCircumcenter.Y) * (smallestAngleCorner.Y - myCircumcenter.Y) >
+ lengthConst * ((smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y))))
+ {
+ // use circumcenter
+ dxSecondSuggestion = dx;
+ dySecondSuggestion = dy;
+
+ }// else we stick on what we have found
+ }
+ }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok
+ if (isObtuse)
+ {
+ if (neighborNotFound_first && neighborNotFound_second)
+ {
+ //obtuse: check if the other direction works
+ if (justAcute * ((smallestAngleCorner.X - (xMidOfMiddleEdge)) *
+ (smallestAngleCorner.X - (xMidOfMiddleEdge)) +
+ (smallestAngleCorner.Y - (yMidOfMiddleEdge)) *
+ (smallestAngleCorner.Y - (yMidOfMiddleEdge))) >
+ (smallestAngleCorner.X - (xMidOfLongestEdge)) *
+ (smallestAngleCorner.X - (xMidOfLongestEdge)) +
+ (smallestAngleCorner.Y - (yMidOfLongestEdge)) *
+ (smallestAngleCorner.Y - (yMidOfLongestEdge)))
+ {
+ dx = dxSecondSuggestion;
+ dy = dySecondSuggestion;
+
+ }
+ else
+ {
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+
+ }
+ }
+ else if (neighborNotFound_first)
+ {
+ //obtuse: check if the other direction works
+ if (justAcute * ((smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y))) >
+ (smallestAngleCorner.X - (xMidOfLongestEdge)) *
+ (smallestAngleCorner.X - (xMidOfLongestEdge)) +
+ (smallestAngleCorner.Y - (yMidOfLongestEdge)) *
+ (smallestAngleCorner.Y - (yMidOfLongestEdge)))
+ {
+ dx = dxSecondSuggestion;
+ dy = dySecondSuggestion;
+
+ }
+ else
+ {
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+
+ }
+ }
+ else if (neighborNotFound_second)
+ {
+ //obtuse: check if the other direction works
+ if (justAcute * ((smallestAngleCorner.X - (xMidOfMiddleEdge)) *
+ (smallestAngleCorner.X - (xMidOfMiddleEdge)) +
+ (smallestAngleCorner.Y - (yMidOfMiddleEdge)) *
+ (smallestAngleCorner.Y - (yMidOfMiddleEdge))) >
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)))
+ {
+ dx = dxSecondSuggestion;
+ dy = dySecondSuggestion;
+
+ }
+ else
+ {
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+
+ }
+ }
+ else
+ {
+ //obtuse: check if the other direction works
+ if (justAcute * ((smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y))) >
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)))
+ {
+ dx = dxSecondSuggestion;
+ dy = dySecondSuggestion;
+
+ }
+ else
+ {
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+
+ }
+ }
+
+ }
+ else
+ { // acute : consider other direction
+ if (neighborNotFound_first && neighborNotFound_second)
+ {
+ //obtuse: check if the other direction works
+ if (justAcute * ((smallestAngleCorner.X - (xMidOfMiddleEdge)) *
+ (smallestAngleCorner.X - (xMidOfMiddleEdge)) +
+ (smallestAngleCorner.Y - (yMidOfMiddleEdge)) *
+ (smallestAngleCorner.Y - (yMidOfMiddleEdge))) >
+ (smallestAngleCorner.X - (xMidOfLongestEdge)) *
+ (smallestAngleCorner.X - (xMidOfLongestEdge)) +
+ (smallestAngleCorner.Y - (yMidOfLongestEdge)) *
+ (smallestAngleCorner.Y - (yMidOfLongestEdge)))
+ {
+ dx = dxSecondSuggestion;
+ dy = dySecondSuggestion;
+
+ }
+ else
+ {
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+
+ }
+ }
+ else if (neighborNotFound_first)
+ {
+ //obtuse: check if the other direction works
+ if (justAcute * ((smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y))) >
+ (smallestAngleCorner.X - (xMidOfLongestEdge)) *
+ (smallestAngleCorner.X - (xMidOfLongestEdge)) +
+ (smallestAngleCorner.Y - (yMidOfLongestEdge)) *
+ (smallestAngleCorner.Y - (yMidOfLongestEdge)))
+ {
+ dx = dxSecondSuggestion;
+ dy = dySecondSuggestion;
+
+ }
+ else
+ {
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+
+ }
+ }
+ else if (neighborNotFound_second)
+ {
+ //obtuse: check if the other direction works
+ if (justAcute * ((smallestAngleCorner.X - (xMidOfMiddleEdge)) *
+ (smallestAngleCorner.X - (xMidOfMiddleEdge)) +
+ (smallestAngleCorner.Y - (yMidOfMiddleEdge)) *
+ (smallestAngleCorner.Y - (yMidOfMiddleEdge))) >
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)))
+ {
+ dx = dxSecondSuggestion;
+ dy = dySecondSuggestion;
+
+ }
+ else
+ {
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+
+ }
+ }
+ else
+ {
+ //obtuse: check if the other direction works
+ if (justAcute * ((smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxSecondSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dySecondSuggestion + torg.pt.Y))) >
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) *
+ (smallestAngleCorner.X - (dxFirstSuggestion + torg.pt.X)) +
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)) *
+ (smallestAngleCorner.Y - (dyFirstSuggestion + torg.pt.Y)))
+ {
+ dx = dxSecondSuggestion;
+ dy = dySecondSuggestion;
+
+ }
+ else
+ {
+ dx = dxFirstSuggestion;
+ dy = dyFirstSuggestion;
+
+ }
+ }
+
+ }// end if obtuse
+ }// end of relocation
+ }// end of almostGood
+
+ if (relocated <= 0)
+ {
+ circumcenter.pt.X = torg.pt.X + dx;
+ circumcenter.pt.Y = torg.pt.Y + dy;
+ }
+ else
+ {
+ circumcenter.pt.X = origin_x + dx;
+ circumcenter.pt.Y = origin_y + dy;
+ }
+ xi = (yao * dx - xao * dy) * (2.0 * denominator);
+ eta = (xdo * dy - ydo * dx) * (2.0 * denominator);
+
+ }
+
+ ///
+ /// Given square of edge lengths of a triangle,
+ // determine its orientation
+ ///
+ ///
+ ///
+ ///
+ /// Returns a number indicating an orientation.
+ static int LongestShortestEdge(double aodist, double dadist, double dodist)
+ {
+ // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist
+ // middle: dadist // middle: aodist // middle: aodist
+ // longest: dodist // longest: dodist // longest: dadist
+ // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist
+ // middle: dodist // middle: dodist // middle: dadist
+ // longest: dadist // longest: aodist // longest: aodist
+
+ int max = 0, min = 0, mid = 0, minMidMax;
+ if (dodist < aodist && dodist < dadist)
+ {
+ min = 3; // apex is the smallest angle, dodist is the longest edge
+ if (aodist < dadist)
+ {
+ max = 2; // dadist is the longest edge
+ mid = 1; // aodist is the middle longest edge
+ }
+ else
+ {
+ max = 1; // aodist is the longest edge
+ mid = 2; // dadist is the middle longest edge
+ }
+ }
+ else if (aodist < dadist)
+ {
+ min = 1; // dest is the smallest angle, aodist is the biggest edge
+ if (dodist < dadist)
+ {
+ max = 2; // dadist is the longest edge
+ mid = 3; // dodist is the middle longest edge
+ }
+ else
+ {
+ max = 3; // dodist is the longest edge
+ mid = 2; // dadist is the middle longest edge
+ }
+ }
+ else
+ {
+ min = 2; // origin is the smallest angle, dadist is the biggest edge
+ if (aodist < dodist)
+ {
+ max = 3; // dodist is the longest edge
+ mid = 1; // aodist is the middle longest edge
+ }
+ else
+ {
+ max = 1; // aodist is the longest edge
+ mid = 3; // dodist is the middle longest edge
+ }
+ }
+ minMidMax = min * 100 + mid * 10 + max;
+ // HANDLE ISOSCELES TRIANGLE CASE
+ return minMidMax;
+ }
+
+ ///
+ /// Checks if smothing is possible for a given bad triangle.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The new location for the point, if somothing is possible.
+ /// Returns 1, 2 or 3 if smoothing will work, 0 otherwise.
+ static int DoSmoothing(Mesh m, Otri badotri,
+ Vertex torg, Vertex tdest, Vertex tapex, ref double[] newloc)
+ {
+
+ int numpoints_p = 0;// keeps the number of points in a star of point p, q, r
+ int numpoints_q = 0;
+ int numpoints_r = 0;
+ //int i;
+ double[] possibilities = new double[6];//there can be more than one possibilities
+ int num_pos = 0; // number of possibilities
+ int flag1 = 0, flag2 = 0, flag3 = 0;
+ bool newLocFound = false;
+
+ double[] points_p = new double[500];// keeps the points in a star of point p, q, r
+ double[] points_q = new double[500];
+ double[] points_r = new double[500];
+
+ //vertex v1, v2, v3; // for ccw test
+ //double p1[2], p2[2], p3[2];
+ //double temp[2];
+
+ //********************* TRY TO RELOCATE POINT "p" ***************
+
+ // get the surrounding points of p, so this gives us the triangles
+ numpoints_p = GetStarPoints(m, badotri, torg, tdest, tapex, 1, ref points_p);
+ // check if the points in counterclockwise order
+ // p1[0] = points_p[0]; p1[1] = points_p[1];
+ // p2[0] = points_p[2]; p2[1] = points_p[3];
+ // p3[0] = points_p[4]; p3[1] = points_p[5];
+ // v1 = (vertex)p1; v2 = (vertex)p2; v3 = (vertex)p3;
+ // if(counterclockwise(m,b,v1,v2,v3) < 0){
+ // // reverse the order to ccw
+ // for(i = 0; i < numpoints_p/2; i++){
+ // temp[0] = points_p[2*i];
+ // temp[1] = points_p[2*i+1];
+ // points_p[2*i] = points_p[2*(numpoints_p-1)-2*i];
+ // points_p[2*i+1] = points_p[2*(numpoints_p-1)+1-2*i];
+ // points_p[2*(numpoints_p-1)-2*i] = temp[0];
+ // points_p[2*(numpoints_p-1)+1-2*i] = temp[1];
+ // }
+ // }
+ // m.counterclockcount--;
+ // INTERSECTION OF PETALS
+ // first check whether the star angles are appropriate for relocation
+ if (torg.type == VertexType.FreeVertex && numpoints_p != 0 && ValidPolygonAngles(numpoints_p, points_p))
+ {
+ //newLocFound = getPetalIntersection(m, b, numpoints_p, points_p, newloc);
+ //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_p, points_p, newloc,torg[0],torg[1]);
+ if (Behavior.MaxAngle == 0.0)
+ {
+ newLocFound = GetWedgeIntersectionWithoutMaxAngle(m, numpoints_p, points_p, ref newloc);
+ }
+ else
+ {
+ newLocFound = GetWedgeIntersection(m, numpoints_p, points_p, ref newloc);
+ }
+ //printf("call petal intersection for p\n");
+ // make sure the relocated point is a free vertex
+ if (newLocFound)
+ {
+ possibilities[0] = newloc[0];// something found
+ possibilities[1] = newloc[1];
+ num_pos++;// increase the number of possibilities
+ flag1 = 1;
+ }
+ }
+
+ //********************* TRY TO RELOCATE POINT "q" ***************
+
+ // get the surrounding points of q, so this gives us the triangles
+ numpoints_q = GetStarPoints(m, badotri, torg, tdest, tapex, 2, ref points_q);
+ // // check if the points in counterclockwise order
+ // v1[0] = points_q[0]; v1[1] = points_q[1];
+ // v2[0] = points_q[2]; v2[1] = points_q[3];
+ // v3[0] = points_q[4]; v3[1] = points_q[5];
+ // if(counterclockwise(m,b,v1,v2,v3) < 0){
+ // // reverse the order to ccw
+ // for(i = 0; i < numpoints_q/2; i++){
+ // temp[0] = points_q[2*i];
+ // temp[1] = points_q[2*i+1];
+ // points_q[2*i] = points_q[2*(numpoints_q-1)-2*i];
+ // points_q[2*i+1] = points_q[2*(numpoints_q-1)+1-2*i];
+ // points_q[2*(numpoints_q-1)-2*i] = temp[0];
+ // points_q[2*(numpoints_q-1)+1-2*i] = temp[1];
+ // }
+ // }
+ // m.counterclockcount--;
+ // INTERSECTION OF PETALS
+ // first check whether the star angles are appropriate for relocation
+ if (tdest.type == VertexType.FreeVertex && numpoints_q != 0 && ValidPolygonAngles(numpoints_q, points_q))
+ {
+ //newLocFound = getPetalIntersection(m, b,numpoints_q, points_q, newloc);
+ //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_q, points_q, newloc,tapex[0],tapex[1]);
+ if (Behavior.MaxAngle == 0.0)
+ {
+ newLocFound = GetWedgeIntersectionWithoutMaxAngle(m, numpoints_q, points_q, ref newloc);
+ }
+ else
+ {
+ newLocFound = GetWedgeIntersection(m, numpoints_q, points_q, ref newloc);
+ }
+ //printf("call petal intersection for q\n");
+
+ // make sure the relocated point is a free vertex
+ if (newLocFound)
+ {
+ possibilities[2] = newloc[0];// something found
+ possibilities[3] = newloc[1];
+ num_pos++;// increase the number of possibilities
+ flag2 = 2;
+ }
+ }
+
+
+ //********************* TRY TO RELOCATE POINT "q" ***************
+ // get the surrounding points of r, so this gives us the triangles
+ numpoints_r = GetStarPoints(m, badotri, torg, tdest, tapex, 3, ref points_r);
+ // check if the points in counterclockwise order
+ // v1[0] = points_r[0]; v1[1] = points_r[1];
+ // v2[0] = points_r[2]; v2[1] = points_r[3];
+ // v3[0] = points_r[4]; v3[1] = points_r[5];
+ // if(counterclockwise(m,b,v1,v2,v3) < 0){
+ // // reverse the order to ccw
+ // for(i = 0; i < numpoints_r/2; i++){
+ // temp[0] = points_r[2*i];
+ // temp[1] = points_r[2*i+1];
+ // points_r[2*i] = points_r[2*(numpoints_r-1)-2*i];
+ // points_r[2*i+1] = points_r[2*(numpoints_r-1)+1-2*i];
+ // points_r[2*(numpoints_r-1)-2*i] = temp[0];
+ // points_r[2*(numpoints_r-1)+1-2*i] = temp[1];
+ // }
+ // }
+ // m.counterclockcount--;
+ // INTERSECTION OF PETALS
+ // first check whether the star angles are appropriate for relocation
+ if (tapex.type == VertexType.FreeVertex && numpoints_r != 0 && ValidPolygonAngles(numpoints_r, points_r))
+ {
+ //newLocFound = getPetalIntersection(m, b,numpoints_r, points_r, newloc);
+ //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_r, points_r, newloc,tdest[0],tdest[1]);
+ if (Behavior.MaxAngle == 0.0)
+ {
+ newLocFound = GetWedgeIntersectionWithoutMaxAngle(m, numpoints_r, points_r, ref newloc);
+ }
+ else
+ {
+ newLocFound = GetWedgeIntersection(m, numpoints_r, points_r, ref newloc);
+ }
+
+ //printf("call petal intersection for r\n");
+
+
+ // make sure the relocated point is a free vertex
+ if (newLocFound)
+ {
+ possibilities[4] = newloc[0];// something found
+ possibilities[5] = newloc[1];
+ num_pos++;// increase the number of possibilities
+ flag3 = 3;
+ }
+ }
+ //printf("numpossibilities %d\n",num_pos);
+ //////////// AFTER FINISH CHECKING EVERY POSSIBILITY, CHOOSE ANY OF THE AVAILABLE ONE //////////////////////
+ if (num_pos > 0)
+ {
+ if (flag1 > 0)
+ { // suggest to relocate origin
+ newloc[0] = possibilities[0];
+ newloc[1] = possibilities[1];
+ return flag1;
+
+ }
+ else
+ {
+ if (flag2 > 0)
+ { // suggest to relocate apex
+ newloc[0] = possibilities[2];
+ newloc[1] = possibilities[3];
+ return flag2;
+
+ }
+ else
+ {// suggest to relocate destination
+ if (flag3 > 0)
+ {
+ newloc[0] = possibilities[4];
+ newloc[1] = possibilities[5];
+ return flag3;
+
+ }
+ }
+ }
+ }
+
+ return 0;// could not find any good relocation
+ }
+
+ ///
+ /// Finds the star of a given point.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// List of points on the star of the given point.
+ /// Number of points on the star of the given point.
+ static int GetStarPoints(Mesh m, Otri badotri,
+ Vertex p,
+ Vertex q,
+ Vertex r,
+ int whichPoint,
+ ref double[] points)
+ {
+
+ Otri neighotri = default(Otri); // for return value of the function
+ Otri tempotri; // for temporary usage
+ double first_x = 0, first_y = 0; // keeps the first point to be considered
+ double second_x = 0, second_y = 0; // for determining the edge we will begin
+ double third_x = 0, third_y = 0; // termination
+ double[] returnPoint = new double[2]; // for keeping the returned point
+ int numvertices = 0; // for keeping number of surrounding vertices
+
+ // first determine which point to be used to find its neighbor triangles
+ switch (whichPoint)
+ {
+ case 1:
+ first_x = p.pt.X; // point at the center
+ first_y = p.pt.Y;
+ second_x = r.pt.X; // second vertex of first edge to consider
+ second_y = r.pt.Y;
+ third_x = q.pt.X; // for terminating the search
+ third_y = q.pt.Y;
+ break;
+ case 2:
+ first_x = q.pt.X; // point at the center
+ first_y = q.pt.Y;
+ second_x = p.pt.X; // second vertex of first edge to consider
+ second_y = p.pt.Y;
+ third_x = r.pt.X; // for terminating the search
+ third_y = r.pt.Y;
+ break;
+ case 3:
+ first_x = r.pt.X; // point at the center
+ first_y = r.pt.Y;
+ second_x = q.pt.X; // second vertex of first edge to consider
+ second_y = q.pt.Y;
+ third_x = p.pt.X; // for terminating the search
+ third_y = p.pt.Y;
+ break;
+ }
+ tempotri = badotri;
+ // add first point as the end of first edge
+ points[numvertices] = second_x;
+ numvertices++;
+ points[numvertices] = second_y;
+ numvertices++;
+ // assign as dummy value
+ returnPoint[0] = second_x; returnPoint[1] = second_y;
+ // until we reach the third point of the beginning triangle
+ do
+ {
+ // find the neighbor's third point where it is incident to given edge
+ if (!GetNeighborsVertex(m, tempotri, first_x, first_y, second_x, second_y, ref returnPoint, ref neighotri))
+ {
+ // go to next triangle
+ tempotri = neighotri;
+ // now the second point is the neighbor's third vertex
+ second_x = returnPoint[0];
+ second_y = returnPoint[1];
+ // add a new point to the list of surrounding points
+ points[numvertices] = returnPoint[0];
+ numvertices++;
+ points[numvertices] = returnPoint[1];
+ numvertices++;
+ }
+ else
+ {
+ numvertices = 0;
+ break;
+ }
+
+ } while (!((Math.Abs(returnPoint[0] - third_x) <= EPS) &&
+ (Math.Abs(returnPoint[1] - third_y) <= EPS)));
+ return numvertices / 2;
+
+ }
+
+ ///
+ /// Gets a neighbours vertex.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Neighbor's third vertex incident to given edge.
+ /// Pointer for the neighbor triangle.
+ /// Returns true if vertex was found.
+ static bool GetNeighborsVertex(Mesh m, Otri badotri,
+ double first_x, double first_y,
+ double second_x, double second_y,
+ ref double[] thirdpoint, ref Otri neighotri)
+ {
+
+ Otri neighbor = default(Otri); // keeps the neighbor triangles
+ bool notFound = false; // boolean variable if we can find that neighbor or not
+
+ // for keeping the vertices of the neighbor triangle
+ Vertex neighborvertex_1 = null;
+ Vertex neighborvertex_2 = null;
+ Vertex neighborvertex_3 = null;
+
+ // used for finding neighbor triangle
+ int firstVertexMatched = 0, secondVertexMatched = 0; // to find the correct neighbor
+ //triangle ptr; // Temporary variable used by sym()
+ //int i; // index variable
+ // find neighbors
+ // Check each of the triangle's three neighbors to find the correct one
+ for (badotri.orient = 0; badotri.orient < 3; badotri.orient++)
+ {
+ // Find the neighbor.
+ badotri.Sym(ref neighbor);
+ // check if it is the one we are looking for by checking the corners
+ // first check if the neighbor is nonexistent, since it can be on the border
+ if ((neighbor.triangle != Mesh.dummytri))
+ {
+ // then check if two wanted corners are also in this triangle
+ // take the vertices of the candidate neighbor
+ neighborvertex_1 = neighbor.Org();
+ neighborvertex_2 = neighbor.Dest();
+ neighborvertex_3 = neighbor.Apex();
+
+ // check if it is really a triangle
+ if ((neighborvertex_1.pt.X == neighborvertex_2.pt.X && neighborvertex_1.pt.Y == neighborvertex_2.pt.Y)
+ || (neighborvertex_2.pt.X == neighborvertex_3.pt.X && neighborvertex_2.pt.Y == neighborvertex_3.pt.Y)
+ || (neighborvertex_1.pt.X == neighborvertex_3.pt.X && neighborvertex_1.pt.Y == neighborvertex_3.pt.Y))
+ {
+ //printf("Two vertices are the same!!!!!!!\n");
+ }
+ else
+ {
+ // begin searching for the correct neighbor triangle
+ firstVertexMatched = 0;
+ if ((Math.Abs(first_x - neighborvertex_1.pt.X) < EPS) &&
+ (Math.Abs(first_y - neighborvertex_1.pt.Y) < EPS))
+ {
+ firstVertexMatched = 11; // neighbor's 1st vertex is matched to first vertex
+
+ }
+ else if ((Math.Abs(first_x - neighborvertex_2.pt.X) < EPS) &&
+ (Math.Abs(first_y - neighborvertex_2.pt.Y) < EPS))
+ {
+ firstVertexMatched = 12; // neighbor's 2nd vertex is matched to first vertex
+
+ }
+ else if ((Math.Abs(first_x - neighborvertex_3.pt.X) < EPS) &&
+ (Math.Abs(first_y - neighborvertex_3.pt.Y) < EPS))
+ {
+ firstVertexMatched = 13; // neighbor's 3rd vertex is matched to first vertex
+
+ }/*else{
+ // none of them matched
+ } // end of first vertex matching */
+
+ secondVertexMatched = 0;
+ if ((Math.Abs(second_x - neighborvertex_1.pt.X) < EPS) &&
+ (Math.Abs(second_y - neighborvertex_1.pt.Y) < EPS))
+ {
+ secondVertexMatched = 21; // neighbor's 1st vertex is matched to second vertex
+ }
+ else if ((Math.Abs(second_x - neighborvertex_2.pt.X) < EPS) &&
+ (Math.Abs(second_y - neighborvertex_2.pt.Y) < EPS))
+ {
+ secondVertexMatched = 22; // neighbor's 2nd vertex is matched to second vertex
+ }
+ else if ((Math.Abs(second_x - neighborvertex_3.pt.X) < EPS) &&
+ (Math.Abs(second_y - neighborvertex_3.pt.Y) < EPS))
+ {
+ secondVertexMatched = 23; // neighbor's 3rd vertex is matched to second vertex
+ }/*else{
+ // none of them matched
+ } // end of second vertex matching*/
+
+ }
+
+ }// if neighbor exists or not
+
+ if (((firstVertexMatched == 11) && (secondVertexMatched == 22 || secondVertexMatched == 23))
+ || ((firstVertexMatched == 12) && (secondVertexMatched == 21 || secondVertexMatched == 23))
+ || ((firstVertexMatched == 13) && (secondVertexMatched == 21 || secondVertexMatched == 22)))
+ break;
+ }// end of for loop over all orientations
+
+ switch (firstVertexMatched)
+ {
+ case 0:
+ notFound = true;
+ break;
+ case 11:
+ if (secondVertexMatched == 22)
+ {
+ thirdpoint[0] = neighborvertex_3.pt.X;
+ thirdpoint[1] = neighborvertex_3.pt.Y;
+ }
+ else if (secondVertexMatched == 23)
+ {
+ thirdpoint[0] = neighborvertex_2.pt.X;
+ thirdpoint[1] = neighborvertex_2.pt.Y;
+ }
+ else { notFound = true; }
+ break;
+ case 12:
+ if (secondVertexMatched == 21)
+ {
+ thirdpoint[0] = neighborvertex_3.pt.X;
+ thirdpoint[1] = neighborvertex_3.pt.Y;
+ }
+ else if (secondVertexMatched == 23)
+ {
+ thirdpoint[0] = neighborvertex_1.pt.X;
+ thirdpoint[1] = neighborvertex_1.pt.Y;
+ }
+ else { notFound = true; }
+ break;
+ case 13:
+ if (secondVertexMatched == 21)
+ {
+ thirdpoint[0] = neighborvertex_2.pt.X;
+ thirdpoint[1] = neighborvertex_2.pt.Y;
+ }
+ else if (secondVertexMatched == 22)
+ {
+ thirdpoint[0] = neighborvertex_1.pt.X;
+ thirdpoint[1] = neighborvertex_1.pt.Y;
+ }
+ else { notFound = true; }
+ break;
+ default:
+ if (secondVertexMatched == 0) { notFound = true; }
+ break;
+ }
+ // pointer of the neighbor triangle
+ neighotri = neighbor;
+ return notFound;
+
+ }
+
+ ///
+ /// Find a new point location by wedge intersection.
+ ///
+ ///
+ ///
+ ///
+ /// A new location for the point according to surrounding points.
+ /// Returns true if new location found
+ static bool GetWedgeIntersectionWithoutMaxAngle(Mesh m,
+ int numpoints, double[] points, ref double[] newloc)
+ {
+ //double total_x = 0;
+ //double total_y = 0;
+ double x0, y0, x1, y1, x2, y2;
+ //double compConst = 0.01; // for comparing real numbers
+
+ double x01, y01;
+ //double x12, y12;
+
+ //double ax, ay, bx, by; //two intersections of two petals disks
+
+ double d01;//, d12
+
+ //double petalx0, petaly0, petalr0, petalx1, petaly1, petalr1;
+
+ //double p[5];
+
+ double[] petalx = new double[24];
+ double[] petaly = new double[24];
+ double[] petalr = new double[24];
+
+ double[] wedges = new double[2000];
+ double xmid, ymid, dist, x3, y3;
+ double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy;
+ double ux, uy;
+ double alpha;
+ double[] p1 = new double[3];
+ double[] initialConvexPoly = new double[500];
+ //double poly_points;
+ int numpolypoints = 0;
+
+ //int numBadTriangle;
+
+ int i, j;
+
+ int s, flag, count, num;
+
+ double petalcenterconstant, petalradiusconstant;
+
+ x0 = points[2 * numpoints - 4];
+ y0 = points[2 * numpoints - 3];
+ x1 = points[2 * numpoints - 2];
+ y1 = points[2 * numpoints - 1];
+
+ // minimum angle
+ alpha = Behavior.MinAngle * Math.PI / 180.0;
+ // initialize the constants
+ if (Behavior.GoodAngle == 1.0)
+ {
+ petalcenterconstant = 0;
+ petalradiusconstant = 0;
+ }
+ else
+ {
+ petalcenterconstant = 0.5 / Math.Tan(alpha);
+ petalradiusconstant = 0.5 / Math.Sin(alpha);
+ }
+
+ for (i = 0; i < numpoints * 2; i = i + 2)
+ {
+ x2 = points[i];
+ y2 = points[i + 1];
+
+ //printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1);
+
+ x01 = x1 - x0;
+ y01 = y1 - y0;
+ d01 = Math.Sqrt(x01 * x01 + y01 * y01);
+ // find the petal of each edge 01;
+
+ // printf("PETAL CONSTANT (%.12f, %.12f)\n",
+ // b.petalcenterconstant, b.petalradiusconstant );
+ // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01);
+
+ petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01;
+ petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01;
+ petalr[i / 2] = petalradiusconstant * d01;
+ petalx[numpoints + i / 2] = petalx[i / 2];
+ petaly[numpoints + i / 2] = petaly[i / 2];
+ petalr[numpoints + i / 2] = petalr[i / 2];
+ //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]);
+
+ /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL
+ xmid = (x0 + x1) / 2.0; // mid point of pq
+ ymid = (y0 + y1) / 2.0;
+
+ // distance between xmid and petal center
+ dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid));
+ // find the unit vector goes from mid point to petal center
+ ux = (petalx[i / 2] - xmid) / dist;
+ uy = (petaly[i / 2] - ymid) / dist;
+ // find the third point other than p and q
+ x3 = petalx[i / 2] + ux * petalr[i / 2];
+ y3 = petaly[i / 2] + uy * petalr[i / 2];
+ /// FIND THE LINE POINTS BY THE ROTATION MATRIX
+ // cw rotation matrix [cosX sinX; -sinX cosX]
+ // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX]
+ // ccw rotation matrix [cosX -sinX; sinX cosX]
+ // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX]
+ /// LINE #1: (x1,y1) & (x_1,y_1)
+ // vector from p to q
+ ux = x1 - x0;
+ uy = y1 - y0;
+ // rotate the vector around p = (x0,y0) in ccw by alpha degrees
+ x_1 = x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha) + x0 - x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha);
+ y_1 = x1 * Math.Sin(alpha) + y1 * Math.Cos(alpha) + y0 - x0 * Math.Sin(alpha) - y0 * Math.Cos(alpha);
+ // add these to wedges list as lines in order
+ wedges[i * 16] = x0; wedges[i * 16 + 1] = y0;
+ wedges[i * 16 + 2] = x_1; wedges[i * 16 + 3] = y_1;
+ //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1);
+ /// LINE #2: (x2,y2) & (x_2,y_2)
+ // vector from p to q
+ ux = x0 - x1;
+ uy = y0 - y1;
+ // rotate the vector around q = (x1,y1) in cw by alpha degrees
+ x_2 = x0 * Math.Cos(alpha) + y0 * Math.Sin(alpha) + x1 - x1 * Math.Cos(alpha) - y1 * Math.Sin(alpha);
+ y_2 = -x0 * Math.Sin(alpha) + y0 * Math.Cos(alpha) + y1 + x1 * Math.Sin(alpha) - y1 * Math.Cos(alpha);
+ // add these to wedges list as lines in order
+ wedges[i * 16 + 4] = x_2; wedges[i * 16 + 5] = y_2;
+ wedges[i * 16 + 6] = x1; wedges[i * 16 + 7] = y1;
+ //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1);
+ // vector from (petalx, petaly) to (x3,y3)
+ ux = x3 - petalx[i / 2];
+ uy = y3 - petaly[i / 2];
+ tempx = x3; tempy = y3;
+ /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3)
+ for (j = 1; j < 4; j++)
+ {
+ // rotate the vector around (petalx,petaly) in cw by (60 - alpha)*j degrees
+ x_3 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j);
+ y_3 = -x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] + petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j);
+ // add these to wedges list as lines in order
+ wedges[i * 16 + 8 + 4 * (j - 1)] = x_3; wedges[i * 16 + 9 + 4 * (j - 1)] = y_3;
+ wedges[i * 16 + 10 + 4 * (j - 1)] = tempx; wedges[i * 16 + 11 + 4 * (j - 1)] = tempy;
+ tempx = x_3; tempy = y_3;
+ }
+ tempx = x3; tempy = y3;
+ /// LINE #6, #7, #8: (x3,y3) & (x_4,y_4)
+ for (j = 1; j < 4; j++)
+ {
+ // rotate the vector around (petalx,petaly) in ccw by (60 - alpha)*j degrees
+ x_4 = x3 * Math.Cos((Math.PI / 3.0 - alpha) * j) - y3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j);
+ y_4 = x3 * Math.Sin((Math.PI / 3.0 - alpha) * j) + y3 * Math.Cos((Math.PI / 3.0 - alpha) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((Math.PI / 3.0 - alpha) * j) - petaly[i / 2] * Math.Cos((Math.PI / 3.0 - alpha) * j);
+
+ // add these to wedges list as lines in order
+ wedges[i * 16 + 20 + 4 * (j - 1)] = tempx; wedges[i * 16 + 21 + 4 * (j - 1)] = tempy;
+ wedges[i * 16 + 22 + 4 * (j - 1)] = x_4; wedges[i * 16 + 23 + 4 * (j - 1)] = y_4;
+ tempx = x_4; tempy = y_4;
+ }
+ //printf("LINE #3 (%.12f, %.12f) (%.12f, %.12f)\n", x_3,y_3,x3,y3);
+ //printf("LINE #4 (%.12f, %.12f) (%.12f, %.12f)\n", x3,y3,x_4,y_4);
+
+ /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON
+ if (i == 0)
+ {
+ // line1 & line2: p1
+ LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1);
+ if ((p1[0] == 1.0))
+ {
+ // #0
+ initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2];
+ // #1
+ initialConvexPoly[2] = wedges[i * 16 + 16]; initialConvexPoly[3] = wedges[i * 16 + 17];
+ // #2
+ initialConvexPoly[4] = wedges[i * 16 + 12]; initialConvexPoly[5] = wedges[i * 16 + 13];
+ // #3
+ initialConvexPoly[6] = wedges[i * 16 + 8]; initialConvexPoly[7] = wedges[i * 16 + 9];
+ // #4
+ initialConvexPoly[8] = x3; initialConvexPoly[9] = y3;
+ // #5
+ initialConvexPoly[10] = wedges[i * 16 + 22]; initialConvexPoly[11] = wedges[i * 16 + 23];
+ // #6
+ initialConvexPoly[12] = wedges[i * 16 + 26]; initialConvexPoly[13] = wedges[i * 16 + 27];
+ // #7
+ initialConvexPoly[14] = wedges[i * 16 + 30]; initialConvexPoly[15] = wedges[i * 16 + 31];
+ //printf("INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15]);
+ }
+ }
+
+ x0 = x1; y0 = y1;
+ x1 = x2; y1 = y2;
+ }
+
+ /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION
+ if (numpoints != 0)
+ {
+ // first intersect the opposite located ones
+ s = (numpoints - 1) / 2 + 1;
+ flag = 0;
+ count = 0;
+ i = 1;
+ num = 8;
+ for (j = 0; j < 32; j = j + 4)
+ {
+ numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * s + j], wedges[32 * s + 1 + j], wedges[32 * s + 2 + j], wedges[32 * s + 3 + j]);
+ if (numpolypoints == 0)
+ return false;
+ else
+ num = numpolypoints;
+ }
+ count++;
+ while (count < numpoints - 1)
+ {
+ for (j = 0; j < 32; j = j + 4)
+ {
+ numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[32 * (i + s * flag) + j], wedges[32 * (i + s * flag) + 1 + j], wedges[32 * (i + s * flag) + 2 + j], wedges[32 * (i + s * flag) + 3 + j]);
+ if (numpolypoints == 0)
+ return false;
+ else
+ num = numpolypoints;
+ }
+ i = i + flag;
+ flag = (flag + 1) % 2;
+ count++;
+ }
+ /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION
+ FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc);
+
+ if (Behavior.FixedArea)
+ {
+ // numBadTriangle = 0;
+ // for(j= 0; j < numpoints *2-2; j = j+2){
+ // if(testTriangleAngleArea(m,b,&newloc[0],&newloc[1], &points[j], &points[j+1], &points[j+2], &points[j+3] )){
+ // numBadTriangle++;
+ // }
+ // }
+ // if(testTriangleAngleArea(m,b, &newloc[0],&newloc[1], &points[0], &points[1], &points[numpoints*2-2], &points[numpoints*2-1] )){
+ // numBadTriangle++;
+ // }
+ //
+ // if (numBadTriangle == 0) {
+ //
+ // return 1;
+ // }
+ }
+ else
+ {
+ //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]);
+ // for(i = 0; i < 2*numpolypoints; i = i+2){
+ // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]);
+ // }
+ // printf("numpoints %d\n",numpoints);
+ return true;
+ }
+ }
+
+
+ return false;
+ }
+
+ ///
+ /// Find a new point location by wedge intersection.
+ ///
+ ///
+ ///
+ ///
+ /// A new location for the point according to surrounding points.
+ /// Returns true if new location found
+ static bool GetWedgeIntersection(Mesh m, int numpoints, double[] points, ref double[] newloc)
+ {
+ //double total_x = 0;
+ //double total_y = 0;
+ double x0, y0, x1, y1, x2, y2;
+ //double compConst = 0.01; // for comparing real numbers
+
+ double x01, y01;
+ //double x12, y12;
+
+ //double ax, ay, bx, by; //two intersections of two petals disks
+
+ double d01;//, d12
+
+ //double petalx0, petaly1, petaly0, petalr0, petalx1, petalr1;
+
+ //double p[5];
+
+ double[] petalx = new double[100];
+ double[] petaly = new double[100];
+ double[] petalr = new double[100];
+
+ double[] wedges = new double[2000];
+ double xmid, ymid, dist, x3, y3;
+ double x_1, y_1, x_2, y_2, x_3, y_3, x_4, y_4, tempx, tempy, x_5, y_5, x_6, y_6;
+ double ux, uy;
+ double[] p1 = new double[3], p2 = new double[3], p3 = new double[3], p4 = new double[3];
+ double[] initialConvexPoly = new double[500];
+ //double poly_points;
+ int numpolypoints = 0;
+ int howManyPoints = 0; // keeps the number of points used for representing the wedge
+ double line345 = 4.0, line789 = 4.0; // flag keeping which line to skip or construct
+
+ int numBadTriangle;
+
+ int i, j, k;
+
+ int s, flag, count, num;
+
+ int n, e;
+
+ double weight;
+
+ double petalcenterconstant, petalradiusconstant;
+
+ x0 = points[2 * numpoints - 4];
+ y0 = points[2 * numpoints - 3];
+ x1 = points[2 * numpoints - 2];
+ y1 = points[2 * numpoints - 1];
+
+ // minimum / maximum angle
+ double alpha, sinAlpha, cosAlpha, beta, sinBeta, cosBeta;
+ alpha = Behavior.MinAngle * Math.PI / 180.0;
+ sinAlpha = Math.Sin(alpha);
+ cosAlpha = Math.Cos(alpha);
+ beta = Behavior.MaxAngle * Math.PI / 180.0;
+ sinBeta = Math.Sin(beta);
+ cosBeta = Math.Cos(beta);
+
+ // initialize the constants
+ if (Behavior.GoodAngle == 1.0)
+ {
+ petalcenterconstant = 0;
+ petalradiusconstant = 0;
+ }
+ else
+ {
+ petalcenterconstant = 0.5 / Math.Tan(alpha);
+ petalradiusconstant = 0.5 / Math.Sin(alpha);
+ }
+
+ for (i = 0; i < numpoints * 2; i = i + 2)
+ {
+ // go to the next point
+ x2 = points[i];
+ y2 = points[i + 1];
+
+ // printf("POLYGON POINTS (p,q) #%d (%.12f, %.12f) (%.12f, %.12f)\n", i/2, x0, y0,x1, y1);
+
+ x01 = x1 - x0;
+ y01 = y1 - y0;
+ d01 = Math.Sqrt(x01 * x01 + y01 * y01);
+ // find the petal of each edge 01;
+
+ // printf("PETAL CONSTANT (%.12f, %.12f)\n",
+ // b.petalcenterconstant, b.petalradiusconstant );
+ // printf("PETAL DIFFS (%.6f, %.6f, %.4f)\n", x01, y01, d01);
+ //printf("i:%d numpoints:%d\n", i, numpoints);
+ petalx[i / 2] = x0 + 0.5 * x01 - petalcenterconstant * y01;
+ petaly[i / 2] = y0 + 0.5 * y01 + petalcenterconstant * x01;
+ petalr[i / 2] = petalradiusconstant * d01;
+ petalx[numpoints + i / 2] = petalx[i / 2];
+ petaly[numpoints + i / 2] = petaly[i / 2];
+ petalr[numpoints + i / 2] = petalr[i / 2];
+ //printf("PETAL POINTS #%d (%.12f, %.12f) R= %.12f\n", i/2, petalx[i/2],petaly[i/2], petalr[i/2]);
+
+ /// FIRST FIND THE HALF-PLANE POINTS FOR EACH PETAL
+ xmid = (x0 + x1) / 2.0; // mid point of pq
+ ymid = (y0 + y1) / 2.0;
+
+ // distance between xmid and petal center
+ dist = Math.Sqrt((petalx[i / 2] - xmid) * (petalx[i / 2] - xmid) + (petaly[i / 2] - ymid) * (petaly[i / 2] - ymid));
+ // find the unit vector goes from mid point to petal center
+ ux = (petalx[i / 2] - xmid) / dist;
+ uy = (petaly[i / 2] - ymid) / dist;
+ // find the third point other than p and q
+ x3 = petalx[i / 2] + ux * petalr[i / 2];
+ y3 = petaly[i / 2] + uy * petalr[i / 2];
+ /// FIND THE LINE POINTS BY THE ROTATION MATRIX
+ // cw rotation matrix [cosX sinX; -sinX cosX]
+ // cw rotation about (x,y) [ux*cosX + uy*sinX + x - x*cosX - y*sinX; -ux*sinX + uy*cosX + y + x*sinX - y*cosX]
+ // ccw rotation matrix [cosX -sinX; sinX cosX]
+ // ccw rotation about (x,y) [ux*cosX - uy*sinX + x - x*cosX + y*sinX; ux*sinX + uy*cosX + y - x*sinX - y*cosX]
+ /// LINE #1: (x1,y1) & (x_1,y_1)
+ // vector from p to q
+ ux = x1 - x0;
+ uy = y1 - y0;
+ // rotate the vector around p = (x0,y0) in ccw by alpha degrees
+ x_1 = x1 * cosAlpha - y1 * sinAlpha + x0 - x0 * cosAlpha + y0 * sinAlpha;
+ y_1 = x1 * sinAlpha + y1 * cosAlpha + y0 - x0 * sinAlpha - y0 * cosAlpha;
+ // add these to wedges list as lines in order
+ wedges[i * 20] = x0; wedges[i * 20 + 1] = y0;
+ wedges[i * 20 + 2] = x_1; wedges[i * 20 + 3] = y_1;
+ //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1);
+ /// LINE #2: (x2,y2) & (x_2,y_2)
+ // vector from q to p
+ ux = x0 - x1;
+ uy = y0 - y1;
+ // rotate the vector around q = (x1,y1) in cw by alpha degrees
+ x_2 = x0 * cosAlpha + y0 * sinAlpha + x1 - x1 * cosAlpha - y1 * sinAlpha;
+ y_2 = -x0 * sinAlpha + y0 * cosAlpha + y1 + x1 * sinAlpha - y1 * cosAlpha;
+ // add these to wedges list as lines in order
+ wedges[i * 20 + 4] = x_2; wedges[i * 20 + 5] = y_2;
+ wedges[i * 20 + 6] = x1; wedges[i * 20 + 7] = y1;
+ //printf("LINE #2 (%.12f, %.12f) (%.12f, %.12f)\n", x_2,y_2,x1,y1);
+ // vector from (petalx, petaly) to (x3,y3)
+ ux = x3 - petalx[i / 2];
+ uy = y3 - petaly[i / 2];
+ tempx = x3; tempy = y3;
+
+ /// DETERMINE HOW MANY POINTS TO USE ACCORDING TO THE MINANGLE-MAXANGLE COMBINATION
+ // petal center angle
+ alpha = (2.0 * Behavior.MaxAngle + Behavior.MinAngle - 180.0);
+ if (alpha <= 0.0)
+ {// when only angle lines needed
+ // 4 point case
+ howManyPoints = 4;
+ //printf("4 point case\n");
+ line345 = 1.0;
+ line789 = 1.0;
+ }
+ else if (alpha <= 5.0)
+ {// when only angle lines plus two other lines are needed
+ // 6 point case
+ howManyPoints = 6;
+ //printf("6 point case\n");
+ line345 = 2.0;
+ line789 = 2.0;
+ }
+ else if (alpha <= 10.0)
+ {// when we need more lines
+ // 8 point case
+ howManyPoints = 8;
+ line345 = 3.0;
+ line789 = 3.0;
+ //printf("8 point case\n");
+ }
+ else
+ {// when we have a big wedge
+ // 10 point case
+ howManyPoints = 10;
+ //printf("10 point case\n");
+ line345 = 4.0;
+ line789 = 4.0;
+ }
+ alpha = alpha * Math.PI / 180.0;
+ /// LINE #3, #4, #5: (x3,y3) & (x_3,y_3)
+ for (j = 1; j < line345; j++)
+ {
+ if (line345 == 1)
+ continue;
+ // rotate the vector around (petalx,petaly) in cw by (alpha/3.0)*j degrees
+ x_3 = x3 * Math.Cos((alpha / (line345 - 1.0)) * j) + y3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + petalx[i / 2] - petalx[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j));
+ y_3 = -x3 * Math.Sin(((alpha / (line345 - 1.0)) * j)) + y3 * Math.Cos(((alpha / (line345 - 1.0)) * j)) + petaly[i / 2] + petalx[i / 2] * Math.Sin(((alpha / (line345 - 1.0)) * j)) - petaly[i / 2] * Math.Cos(((alpha / (line345 - 1.0)) * j));
+ // add these to wedges list as lines in order
+ wedges[i * 20 + 8 + 4 * (j - 1)] = x_3; wedges[i * 20 + 9 + 4 * (j - 1)] = y_3;
+ wedges[i * 20 + 10 + 4 * (j - 1)] = tempx; wedges[i * 20 + 11 + 4 * (j - 1)] = tempy;
+ tempx = x_3; tempy = y_3;
+ }
+ /// LINE #6: (x2,y2) & (x_3,y_3)
+ // vector from q to p
+ ux = x0 - x1;
+ uy = y0 - y1;
+ // rotate the vector around q = (x1,y1) in cw by alpha degrees
+ x_5 = x0 * cosBeta + y0 * sinBeta + x1 - x1 * cosBeta - y1 * sinBeta;
+ y_5 = -x0 * sinBeta + y0 * cosBeta + y1 + x1 * sinBeta - y1 * cosBeta;
+ wedges[i * 20 + 20] = x1; wedges[i * 20 + 21] = y1;
+ wedges[i * 20 + 22] = x_5; wedges[i * 20 + 23] = y_5;
+
+ tempx = x3; tempy = y3;
+ /// LINE #7, #8, #9: (x3,y3) & (x_4,y_4)
+ for (j = 1; j < line789; j++)
+ {
+ if (line789 == 1)
+ continue;
+ // rotate the vector around (petalx,petaly) in ccw by (alpha/3.0)*j degrees
+ x_4 = x3 * Math.Cos((alpha / (line789 - 1.0)) * j) - y3 * Math.Sin((alpha / (line789 - 1.0)) * j) + petalx[i / 2] - petalx[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j);
+ y_4 = x3 * Math.Sin((alpha / (line789 - 1.0)) * j) + y3 * Math.Cos((alpha / (line789 - 1.0)) * j) + petaly[i / 2] - petalx[i / 2] * Math.Sin((alpha / (line789 - 1.0)) * j) - petaly[i / 2] * Math.Cos((alpha / (line789 - 1.0)) * j);
+
+ // add these to wedges list as lines in order
+ wedges[i * 20 + 24 + 4 * (j - 1)] = tempx; wedges[i * 20 + 25 + 4 * (j - 1)] = tempy;
+ wedges[i * 20 + 26 + 4 * (j - 1)] = x_4; wedges[i * 20 + 27 + 4 * (j - 1)] = y_4;
+ tempx = x_4; tempy = y_4;
+ }
+ /// LINE #10: (x1,y1) & (x_3,y_3)
+ // vector from p to q
+ ux = x1 - x0;
+ uy = y1 - y0;
+ // rotate the vector around p = (x0,y0) in ccw by alpha degrees
+ x_6 = x1 * cosBeta - y1 * sinBeta + x0 - x0 * cosBeta + y0 * sinBeta;
+ y_6 = x1 * sinBeta + y1 * cosBeta + y0 - x0 * sinBeta - y0 * cosBeta;
+ wedges[i * 20 + 36] = x_6; wedges[i * 20 + 37] = y_6;
+ wedges[i * 20 + 38] = x0; wedges[i * 20 + 39] = y0;
+
+ //printf("LINE #1 (%.12f, %.12f) (%.12f, %.12f)\n", x0,y0,x_1,y_1);
+ /// IF IT IS THE FIRST ONE, FIND THE CONVEX POLYGON
+ if (i == 0)
+ {
+ switch (howManyPoints)
+ {
+ case 4:
+ // line1 & line2 & line3 & line4
+ LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1);
+ LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2);
+ LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_5, y_5, ref p3);
+ LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p4);
+ if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0) && (p4[0] == 1.0))
+ {
+ // #0
+ initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2];
+ // #1
+ initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2];
+ // #2
+ initialConvexPoly[4] = p3[1]; initialConvexPoly[5] = p3[2];
+ // #3
+ initialConvexPoly[6] = p4[1]; initialConvexPoly[7] = p4[2];
+ }
+ break;
+ case 6:
+ // line1 & line2 & line3
+ LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1);
+ LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2);
+ LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3);
+ if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0))
+ {
+ // #0
+ initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2];
+ // #1
+ initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2];
+ // #2
+ initialConvexPoly[4] = wedges[i * 20 + 8]; initialConvexPoly[5] = wedges[i * 20 + 9];
+ // #3
+ initialConvexPoly[6] = x3; initialConvexPoly[7] = y3;
+ // #4
+ initialConvexPoly[8] = wedges[i * 20 + 26]; initialConvexPoly[9] = wedges[i * 20 + 27];
+ // #5
+ initialConvexPoly[10] = p3[1]; initialConvexPoly[11] = p3[2];
+ }
+ break;
+ case 8:
+ // line1 & line2: p1
+ LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1);
+ LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2);
+ LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3);
+ if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0))
+ {
+ // #0
+ initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2];
+ // #1
+ initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2];
+ // #2
+ initialConvexPoly[4] = wedges[i * 20 + 12]; initialConvexPoly[5] = wedges[i * 20 + 13];
+ // #3
+ initialConvexPoly[6] = wedges[i * 20 + 8]; initialConvexPoly[7] = wedges[i * 20 + 9];
+ // #4
+ initialConvexPoly[8] = x3; initialConvexPoly[9] = y3;
+ // #5
+ initialConvexPoly[10] = wedges[i * 20 + 26]; initialConvexPoly[11] = wedges[i * 20 + 27];
+ // #6
+ initialConvexPoly[12] = wedges[i * 20 + 30]; initialConvexPoly[13] = wedges[i * 20 + 31];
+ // #7
+ initialConvexPoly[14] = p3[1]; initialConvexPoly[15] = p3[2];
+ }
+ break;
+ case 10:
+ // line1 & line2: p1
+ LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_2, y_2, ref p1);
+ LineLineIntersection(x0, y0, x_1, y_1, x1, y1, x_5, y_5, ref p2);
+ LineLineIntersection(x0, y0, x_6, y_6, x1, y1, x_2, y_2, ref p3);
+ //printf("p3 %f %f %f (%f %f) (%f %f) (%f %f) (%f %f)\n",p3[0],p3[1],p3[2], x0, y0, x_6, x_6, x1, y1, x_2, y_2);
+ if ((p1[0] == 1.0) && (p2[0] == 1.0) && (p3[0] == 1.0))
+ {
+ // #0
+ initialConvexPoly[0] = p1[1]; initialConvexPoly[1] = p1[2];
+ // #1
+ initialConvexPoly[2] = p2[1]; initialConvexPoly[3] = p2[2];
+ // #2
+ initialConvexPoly[4] = wedges[i * 20 + 16]; initialConvexPoly[5] = wedges[i * 20 + 17];
+ // #3
+ initialConvexPoly[6] = wedges[i * 20 + 12]; initialConvexPoly[7] = wedges[i * 20 + 13];
+ // #4
+ initialConvexPoly[8] = wedges[i * 20 + 8]; initialConvexPoly[9] = wedges[i * 20 + 9];
+ // #5
+ initialConvexPoly[10] = x3; initialConvexPoly[11] = y3;
+ // #6
+ initialConvexPoly[12] = wedges[i * 20 + 28]; initialConvexPoly[13] = wedges[i * 20 + 29];
+ // #7
+ initialConvexPoly[14] = wedges[i * 20 + 32]; initialConvexPoly[15] = wedges[i * 20 + 33];
+ // #8
+ initialConvexPoly[16] = wedges[i * 20 + 34]; initialConvexPoly[17] = wedges[i * 20 + 35];
+ // #9
+ initialConvexPoly[18] = p3[1]; initialConvexPoly[19] = p3[2];
+ }
+ break;
+ }
+ // printf("smallest edge (%f,%f) (%f,%f)\n", x0,y0, x1,y1);
+ // printf("real INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n", initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]);
+ }
+
+ x0 = x1; y0 = y1;
+ x1 = x2; y1 = y2;
+ }
+ /// HALF PLANE INTERSECTION: START SPLITTING THE INITIAL POLYGON TO FIND FEASIBLE REGION
+ if (numpoints != 0)
+ {
+ // first intersect the opposite located ones
+ s = (numpoints - 1) / 2 + 1;
+ flag = 0;
+ count = 0;
+ i = 1;
+ num = howManyPoints;
+ for (j = 0; j < 40; j = j + 4)
+ {
+ // in order to skip non-existent lines
+ if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32))
+ {
+ continue;
+ }
+ else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32))
+ {
+ continue;
+ }
+ else if (howManyPoints == 8 && (j == 16 || j == 32))
+ {
+ continue;
+ }
+ // printf("%d 1 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",num, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]);
+ // printf("line (%f, %f) (%f, %f)\n",wedges[40*s+j],wedges[40*s+1+j], wedges[40*s+2+j], wedges[40*s+3+j]);
+ numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * s + j], wedges[40 * s + 1 + j], wedges[40 * s + 2 + j], wedges[40 * s + 3 + j]);
+
+ if (numpolypoints == 0)
+ return false;
+ else
+ num = numpolypoints;
+ }
+ count++;
+ //printf("yes here\n");
+ while (count < numpoints - 1)
+ {
+ for (j = 0; j < 40; j = j + 4)
+ {
+ // in order to skip non-existent lines
+ if (howManyPoints == 4 && (j == 8 || j == 12 || j == 16 || j == 24 || j == 28 || j == 32))
+ {
+ continue;
+ }
+ else if (howManyPoints == 6 && (j == 12 || j == 16 || j == 28 || j == 32))
+ {
+ continue;
+ }
+ else if (howManyPoints == 8 && (j == 16 || j == 32))
+ {
+ continue;
+ }
+ ////printf("%d 2 INITIAL POLY [%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;%.12f, %.12f;]\n",numpolypoints, initialConvexPoly[0],initialConvexPoly[1],initialConvexPoly[2],initialConvexPoly[3],initialConvexPoly[4],initialConvexPoly[5],initialConvexPoly[6],initialConvexPoly[7],initialConvexPoly[8],initialConvexPoly[9],initialConvexPoly[10],initialConvexPoly[11],initialConvexPoly[12],initialConvexPoly[13],initialConvexPoly[14],initialConvexPoly[15],initialConvexPoly[16],initialConvexPoly[17],initialConvexPoly[18],initialConvexPoly[19]);
+ //printf("line (%.20f, %.20f) (%.20f, %.20f)\n", wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]);
+ numpolypoints = HalfPlaneIntersection(num, ref initialConvexPoly, wedges[40 * (i + s * flag) + j], wedges[40 * (i + s * flag) + 1 + j], wedges[40 * (i + s * flag) + 2 + j], wedges[40 * (i + s * flag) + 3 + j]);
+
+ if (numpolypoints == 0)
+ return false;
+ else
+ num = numpolypoints;
+ }
+ i = i + flag;
+ flag = (flag + 1) % 2;
+ count++;
+ }
+ /// IF THERE IS A FEASIBLE INTERSECTION POLYGON, FIND ITS CENTROID AS THE NEW LOCATION
+ FindPolyCentroid(numpolypoints, initialConvexPoly, ref newloc);
+
+ if (Behavior.MaxAngle != 0.0)
+ {
+ numBadTriangle = 0;
+ for (j = 0; j < numpoints * 2 - 2; j = j + 2)
+ {
+ if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3]))
+ {
+ numBadTriangle++;
+ }
+ }
+ if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1]))
+ {
+ numBadTriangle++;
+ }
+
+ if (numBadTriangle == 0)
+ {
+
+ return true;
+ }
+ n = (numpoints <= 2) ? 20 : 30;
+ // try points other than centroid
+ for (k = 0; k < 2 * numpoints; k = k + 2)
+ {
+ for (e = 1; e < n; e = e + 1)
+ {
+ newloc[0] = 0.0; newloc[1] = 0.0;
+ for (i = 0; i < 2 * numpoints; i = i + 2)
+ {
+ weight = 1.0 / numpoints;
+ if (i == k)
+ {
+ newloc[0] = newloc[0] + 0.1 * e * weight * points[i];
+ newloc[1] = newloc[1] + 0.1 * e * weight * points[i + 1];
+ }
+ else
+ {
+ weight = (1.0 - 0.1 * e * weight) / (double)(numpoints - 1.0);
+ newloc[0] = newloc[0] + weight * points[i];
+ newloc[1] = newloc[1] + weight * points[i + 1];
+ }
+
+ }
+ numBadTriangle = 0;
+ for (j = 0; j < numpoints * 2 - 2; j = j + 2)
+ {
+ if (IsBadTriangleAngle(newloc[0], newloc[1], points[j], points[j + 1], points[j + 2], points[j + 3]))
+ {
+ numBadTriangle++;
+ }
+ }
+ if (IsBadTriangleAngle(newloc[0], newloc[1], points[0], points[1], points[numpoints * 2 - 2], points[numpoints * 2 - 1]))
+ {
+ numBadTriangle++;
+ }
+
+ if (numBadTriangle == 0)
+ {
+
+ return true;
+ }
+ }
+ }
+ }
+ else
+ {
+ //printf("yes, we found a feasible region num: %d newloc (%.12f,%.12f)\n", numpolypoints, newloc[0], newloc[1]);
+ // for(i = 0; i < 2*numpolypoints; i = i+2){
+ // printf("point %d) (%.12f,%.12f)\n", i/2, initialConvexPoly[i], initialConvexPoly[i+1]);
+ // }
+ // printf("numpoints %d\n",numpoints);
+ return true;
+ }
+ }
+
+
+ return false;
+ }
+
+ ///
+ /// Check polygon for min angle.
+ ///
+ ///
+ ///
+ /// Returns true if the polygon has angles greater than 2*minangle.
+ static bool ValidPolygonAngles(int numpoints, double[] points)
+ {
+ int i;//,j
+ for (i = 0; i < numpoints; i++)
+ {
+ if (i == numpoints - 1)
+ {
+ if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[0], points[1], points[2], points[3]))
+ {
+ return false; // one of the inner angles is less than required
+ }
+ }
+ else if (i == numpoints - 2)
+ {
+ if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[0], points[1]))
+ {
+ return false; // one of the inner angles is less than required
+ }
+ }
+ else
+ {
+ if (IsBadPolygonAngle(points[i * 2], points[i * 2 + 1], points[(i + 1) * 2], points[(i + 1) * 2 + 1], points[(i + 2) * 2], points[(i + 2) * 2 + 1]))
+ {
+ return false; // one of the inner angles is less than required
+ }
+ }
+ }
+ return true; // all angles are valid
+ }
+
+ ///
+ /// Given three coordinates of a polygon, tests to see if it satisfies the minimum
+ /// angle condition for relocation.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Returns true, if it is a BAD polygon corner, returns false if it is a GOOD
+ /// polygon corner
+ static bool IsBadPolygonAngle(double x1, double y1,
+ double x2, double y2, double x3, double y3)
+ {
+ // variables keeping the distance values for the edges
+ double dx12, dy12, dx23, dy23, dx31, dy31;
+ double dist12, dist23, dist31;
+
+ double cosAngle; // in order to check minimum angle condition
+
+ // calculate the side lengths
+
+ dx12 = x1 - x2;
+ dy12 = y1 - y2;
+ dx23 = x2 - x3;
+ dy23 = y2 - y3;
+ dx31 = x3 - x1;
+ dy31 = y3 - y1;
+ // calculate the squares of the side lentghs
+ dist12 = dx12 * dx12 + dy12 * dy12;
+ dist23 = dx23 * dx23 + dy23 * dy23;
+ dist31 = dx31 * dx31 + dy31 * dy31;
+
+ /// calculate cosine of largest angle ///
+ cosAngle = (dist12 + dist23 - dist31) / (2 * Math.Sqrt(dist12) * Math.Sqrt(dist23));
+ // Check whether the angle is smaller than permitted which is 2*minangle!!!
+ //printf("angle: %f 2*minangle = %f\n",acos(cosAngle)*180/PI, 2*acos(Math.Sqrt(b.goodangle))*180/PI);
+ if (Math.Acos(cosAngle) < 2 * Math.Acos(Math.Sqrt(Behavior.GoodAngle)))
+ {
+ return true;// it is a BAD triangle
+ }
+ return false;// it is a GOOD triangle
+
+ }
+
+ ///
+ /// Given four points representing two lines, returns the intersection point.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The intersection point.
+ ///
+ // referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/
+ ///
+ static void LineLineIntersection(
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3,
+ double x4, double y4, ref double[] p)
+ {
+ // x1,y1 P1 coordinates (point of line 1)
+ // x2,y2 P2 coordinates (point of line 1)
+ // x3,y3 P3 coordinates (point of line 2)
+ // x4,y4 P4 coordinates (point of line 2)
+ // p[1],p[2] intersection coordinates
+ //
+ // This function returns a pointer array which first index indicates
+ // weather they intersect on one point or not, followed by coordinate pairs.
+
+ double u_a, u_b, denom;
+
+ // calculate denominator first
+ denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
+ u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
+ u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
+ // if denominator and numerator equal to zero, lines are coincident
+ if (Math.Abs(denom - 0.0) < EPS && (Math.Abs(u_b - 0.0) < EPS && Math.Abs(u_a - 0.0) < EPS))
+ {
+ p[0] = 0.0;
+ }
+ // if denominator equals to zero, lines are parallel
+ else if (Math.Abs(denom - 0.0) < EPS)
+ {
+ p[0] = 0.0;
+ }
+ else
+ {
+ p[0] = 1.0;
+ u_a = u_a / denom;
+ u_b = u_b / denom;
+ p[1] = x1 + u_a * (x2 - x1); // not the intersection point
+ p[2] = y1 + u_a * (y2 - y1);
+ }
+ }
+
+ ///
+ /// Returns the convex polygon which is the intersection of the given convex
+ /// polygon with the halfplane on the left side (regarding the directional vector)
+ /// of the given line.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps
+ ///
+ static int HalfPlaneIntersection(int numvertices, ref double[] convexPoly, double x1, double y1, double x2, double y2)
+ {
+ double dx, dy; // direction of the line
+ double z, min, max;
+ int i, j;
+
+ double[][] polys = new double[3][];
+ polys[0] = new double[2];
+ polys[1] = new double[2];
+ polys[2] = new double[2];
+
+ int numpolys;
+ double[] res = null;
+ int count = 0;
+ int intFound = 0;
+ dx = x2 - x1;
+ dy = y2 - y1;
+ numpolys = SplitConvexPolygon(numvertices, convexPoly, x1, y1, x2, y2, ref polys);
+
+ if (numpolys == 3)
+ {
+ count = numvertices;
+ }
+ else
+ {
+ for (i = 0; i < numpolys; i++)
+ {
+ min = 99999999999999999;
+ max = -99999999999999999;
+ // compute the minimum and maximum of the
+ // third coordinate of the cross product
+ for (j = 1; j <= 2 * polys[i][0] - 1; j = j + 2)
+ {
+ z = dx * (polys[i][j + 1] - y1) - dy * (polys[i][j] - x1);
+ min = (z < min ? z : min);
+ max = (z > max ? z : max);
+ }
+ // ... and choose the (absolute) greater of both
+ z = (Math.Abs(min) > Math.Abs(max) ? min : max);
+ // and if it is positive, the polygon polys[i]
+ // is on the left side of line
+ if (z > 0.0)
+ {
+ res = polys[i];
+ intFound = 1;
+ break;
+ }
+ }
+ if (intFound == 1)
+ {
+ while (count < res[0])
+ {
+ convexPoly[2 * count] = res[2 * count + 1];
+ convexPoly[2 * count + 1] = res[2 * count + 2];
+ count++;
+
+ }
+ }
+ }
+ // update convexPoly
+ return count;
+ }
+
+ ///
+ /// Splits a convex polygons into one or two polygons through the intersection
+ /// with the given line (regarding the directional vector of the given line).
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps
+ ///
+ static int SplitConvexPolygon(int numvertices, double[] convexPoly, double x1, double y1, double x2, double y2, ref double[][] polys)
+ {
+ // state = 0: before the first intersection (with the line)
+ // state = 1: after the first intersection (with the line)
+ // state = 2: after the second intersection (with the line)
+
+ int state = 0;
+ double[] p = new double[3];
+ // poly1 is constructed in states 0 and 2
+ double[] poly1 = new double[100];
+ int poly1counter = 0;
+ // poly2 is constructed in state 1
+ double[] poly2 = new double[100];
+ int poly2counter = 0;
+ int numpolys;
+ int i;
+ double compConst = 0.000000000001;
+ // for debugging
+ int case1 = 0, case2 = 0, case3 = 0, case31 = 0, case32 = 0, case33 = 0, case311 = 0, case3111 = 0;
+ // intersect all edges of poly with line
+ for (i = 0; i < 2 * numvertices; i = i + 2)
+ {
+ int j = (i + 2 >= 2 * numvertices) ? 0 : i + 2;
+ LineLineSegmentIntersection(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1], convexPoly[j], convexPoly[j + 1], ref p);
+ // if this edge does not intersect with line
+ if (Math.Abs(p[0] - 0.0) <= compConst)
+ {
+ //System.out.println("null");
+ // add p[j] to the proper polygon
+ if (state == 1)
+ {
+ poly2counter++;
+ poly2[2 * poly2counter - 1] = convexPoly[j];
+ poly2[2 * poly2counter] = convexPoly[j + 1];
+ }
+ else
+ {
+ poly1counter++;
+ poly1[2 * poly1counter - 1] = convexPoly[j];
+ poly1[2 * poly1counter] = convexPoly[j + 1];
+ }
+ // debug
+ case1++;
+ }
+ // ... or if the intersection is the whole edge
+ else if (Math.Abs(p[0] - 2.0) <= compConst)
+ {
+ //System.out.println(o);
+ // then we can not reach state 1 and 2
+ poly1counter++;
+ poly1[2 * poly1counter - 1] = convexPoly[j];
+ poly1[2 * poly1counter] = convexPoly[j + 1];
+ // debug
+ case2++;
+ }
+ // ... or if the intersection is a point
+ else
+ {
+ // debug
+ case3++;
+ // if the point is the second vertex of the edge
+ if (Math.Abs(p[1] - convexPoly[j]) <= compConst && Math.Abs(p[2] - convexPoly[j + 1]) <= compConst)
+ {
+ // debug
+ case31++;
+ if (state == 1)
+ {
+ poly2counter++;
+ poly2[2 * poly2counter - 1] = convexPoly[j];
+ poly2[2 * poly2counter] = convexPoly[j + 1];
+ poly1counter++;
+ poly1[2 * poly1counter - 1] = convexPoly[j];
+ poly1[2 * poly1counter] = convexPoly[j + 1];
+ state++;
+ }
+ else if (state == 0)
+ {
+ // debug
+ case311++;
+ poly1counter++;
+ poly1[2 * poly1counter - 1] = convexPoly[j];
+ poly1[2 * poly1counter] = convexPoly[j + 1];
+ // test whether the polygon is splitted
+ // or the line only touches the polygon
+ if (i + 4 < 2 * numvertices)
+ {
+ int s1 = LinePointLocation(x1, y1, x2, y2, convexPoly[i], convexPoly[i + 1]);
+ int s2 = LinePointLocation(x1, y1, x2, y2, convexPoly[i + 4], convexPoly[i + 5]);
+ // the line only splits the polygon
+ // when the previous and next vertex lie
+ // on different sides of the line
+ if (s1 != s2 && s1 != 0 && s2 != 0)
+ {
+ // debug
+ case3111++;
+ poly2counter++;
+ poly2[2 * poly2counter - 1] = convexPoly[j];
+ poly2[2 * poly2counter] = convexPoly[j + 1];
+ state++;
+ }
+ }
+ }
+ }
+ // ... if the point is not the other vertex of the edge
+ else if (!(Math.Abs(p[1] - convexPoly[i]) <= compConst && Math.Abs(p[2] - convexPoly[i + 1]) <= compConst))
+ {
+ // debug
+ case32++;
+ poly1counter++;
+ poly1[2 * poly1counter - 1] = p[1];
+ poly1[2 * poly1counter] = p[2];
+ poly2counter++;
+ poly2[2 * poly2counter - 1] = p[1];
+ poly2[2 * poly2counter] = p[2];
+ if (state == 1)
+ {
+ poly1counter++;
+ poly1[2 * poly1counter - 1] = convexPoly[j];
+ poly1[2 * poly1counter] = convexPoly[j + 1];
+ }
+ else if (state == 0)
+ {
+ poly2counter++;
+ poly2[2 * poly2counter - 1] = convexPoly[j];
+ poly2[2 * poly2counter] = convexPoly[j + 1];
+ }
+ state++;
+ }
+ // ... else if the point is the second vertex of the edge
+ else
+ {
+ // debug
+ case33++;
+ if (state == 1)
+ {
+ poly2counter++;
+ poly2[2 * poly2counter - 1] = convexPoly[j];
+ poly2[2 * poly2counter] = convexPoly[j + 1];
+ }
+ else
+ {
+ poly1counter++;
+ poly1[2 * poly1counter - 1] = convexPoly[j];
+ poly1[2 * poly1counter] = convexPoly[j + 1];
+ }
+ }
+ }
+ }
+ // after splitting the state must be 0 or 2
+ // (depending whether the polygon was splitted or not)
+ if (state != 0 && state != 2)
+ {
+ // printf("there is something wrong state: %d\n", state);
+ // printf("polygon might not be convex!!\n");
+ // printf("case1: %d\ncase2: %d\ncase3: %d\ncase31: %d case311: %d case3111: %d\ncase32: %d\ncase33: %d\n", case1, case2, case3, case31, case311, case3111, case32, case33);
+ // printf("numvertices %d\n=============\n", numvertices);
+ // if there is something wrong with the intersection, just ignore this one
+ numpolys = 3;
+ }
+ else
+ {
+ // finally convert the vertex lists into convex polygons
+ numpolys = (state == 0) ? 1 : 2;
+ poly1[0] = poly1counter;
+ poly2[0] = poly2counter;
+ // convert the first convex polygon
+ polys[0] = poly1;
+ // convert the second convex polygon
+ if (state == 2)
+ {
+ polys[1] = poly2;
+ }
+ }
+ return numpolys;
+ }
+
+ ///
+ /// Determines on which side (relative to the direction) of the given line and the
+ /// point lies (regarding the directional vector) of the given line.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// http://www.mathematik.uni-ulm.de/stochastik/lehre/ws03_04/rt/Geometry2D.ps
+ ///
+ static int LinePointLocation(double x1, double y1, double x2, double y2, double x, double y)
+ {
+ double z;
+ if (Math.Atan((y2 - y1) / (x2 - x1)) * 180.0 / Math.PI == 90.0)
+ {
+ if (Math.Abs(x1 - x) <= 0.00000000001)
+ return 0;
+ }
+ else
+ {
+ if (Math.Abs(y1 + (((y2 - y1) * (x - x1)) / (x2 - x1)) - y) <= EPS)
+ return 0;
+ }
+ // third component of the 3 dimensional product
+ z = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1);
+ if (Math.Abs(z - 0.0) <= 0.00000000001)
+ {
+ return 0;
+ }
+ else if (z > 0)
+ {
+ return 1;
+ }
+ else
+ {
+ return 2;
+ }
+ }
+
+ ///
+ /// Given four points representing one line and a line segment, returns the intersection point
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/
+ ///
+ static void LineLineSegmentIntersection(
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3,
+ double x4, double y4, ref double[] p)
+ {
+ // x1,y1 P1 coordinates (point of line)
+ // x2,y2 P2 coordinates (point of line)
+ // x3,y3 P3 coordinates (point of line segment)
+ // x4,y4 P4 coordinates (point of line segment)
+ // p[1],p[2] intersection coordinates
+ //
+ // This function returns a pointer array which first index indicates
+ // weather they intersect on one point or not, followed by coordinate pairs.
+
+ double u_a, u_b, denom;
+ double compConst = 0.0000000000001;
+ // calculate denominator first
+ denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
+ u_a = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
+ u_b = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
+
+
+ //if(fabs(denom-0.0) < compConst && (fabs(u_b-0.0) < compConst && fabs(u_a-0.0) < compConst)){
+ //printf("denom %.20f u_b %.20f u_a %.20f\n",denom, u_b, u_a);
+ if (Math.Abs(denom - 0.0) < compConst)
+ {
+ if (Math.Abs(u_b - 0.0) < compConst && Math.Abs(u_a - 0.0) < compConst)
+ {
+ p[0] = 2.0; // if denominator and numerator equal to zero, lines are coincident
+ }
+ else
+ {
+ p[0] = 0.0;// if denominator equals to zero, lines are parallel
+ }
+
+ }
+ else
+ {
+ u_b = u_b / denom;
+ u_a = u_a / denom;
+ // printf("u_b %.20f\n", u_b);
+ if (u_b < -compConst || u_b > 1.0 + compConst)
+ { // check if it is on the line segment
+ // printf("line (%.20f, %.20f) (%.20f, %.20f) line seg (%.20f, %.20f) (%.20f, %.20f) \n",x1, y1 ,x2, y2 ,x3, y3 , x4, y4);
+ p[0] = 0.0;
+ }
+ else
+ {
+ p[0] = 1.0;
+ p[1] = x1 + u_a * (x2 - x1); // intersection point
+ p[2] = y1 + u_a * (y2 - y1);
+ }
+ }
+
+ }
+
+ ///
+ /// Returns the centroid of a given polygon
+ ///
+ ///
+ ///
+ /// Centroid of a given polygon
+ static void FindPolyCentroid(int numpoints, double[] points, ref double[] centroid)
+ {
+ int i;
+ //double area = 0.0;//, temp
+ centroid[0] = 0.0; centroid[1] = 0.0;
+
+ for (i = 0; i < 2 * numpoints; i = i + 2)
+ {
+
+ centroid[0] = centroid[0] + points[i];
+ centroid[1] = centroid[1] + points[i + 1];
+
+ }
+ centroid[0] = centroid[0] / numpoints;
+ centroid[1] = centroid[1] / numpoints;
+ }
+
+ ///
+ /// Given two points representing a line and a radius together with a center point
+ /// representing a circle, returns the intersection points.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Pointer to list of intersection points
+ ///
+ /// referenced to: http://local.wasp.uwa.edu.au/~pbourke/geometry/sphereline/
+ ///
+ static void CircleLineIntersection(
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3, double r, ref double[] p)
+ {
+ // x1,y1 P1 coordinates [point of line]
+ // x2,y2 P2 coordinates [point of line]
+ // x3,y3, r P3 coordinates(circle center) and radius [circle]
+ // p[1],p[2]; p[3],p[4] intersection coordinates
+ //
+ // This function returns a pointer array which first index indicates
+ // the number of intersection points, followed by coordinate pairs.
+
+ //double x , y ;
+ double a, b, c, mu, i;
+
+ a = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
+ b = 2 * ((x2 - x1) * (x1 - x3) + (y2 - y1) * (y1 - y3));
+ c = x3 * x3 + y3 * y3 + x1 * x1 + y1 * y1 - 2 * (x3 * x1 + y3 * y1) - r * r;
+ i = b * b - 4 * a * c;
+
+ if (i < 0.0)
+ {
+ // no intersection
+ p[0] = 0.0;
+ }
+ else if (Math.Abs(i - 0.0) < EPS)
+ {
+ // one intersection
+ p[0] = 1.0;
+
+ mu = -b / (2 * a);
+ p[1] = x1 + mu * (x2 - x1);
+ p[2] = y1 + mu * (y2 - y1);
+
+ }
+ else if (i > 0.0 && !(Math.Abs(a - 0.0) < EPS))
+ {
+ // two intersections
+ p[0] = 2.0;
+ // first intersection
+ mu = (-b + Math.Sqrt(i)) / (2 * a);
+ p[1] = x1 + mu * (x2 - x1);
+ p[2] = y1 + mu * (y2 - y1);
+ // second intersection
+ mu = (-b - Math.Sqrt(i)) / (2 * a);
+ p[3] = x1 + mu * (x2 - x1);
+ p[4] = y1 + mu * (y2 - y1);
+
+
+ }
+ else
+ {
+ p[0] = 0.0;
+ }
+ }
+
+ ///
+ /// Given three points, check if the point is the correct point that we are looking for.
+ ///
+ /// P1 coordinates (bisector point of dual edge on triangle)
+ /// P1 coordinates (bisector point of dual edge on triangle)
+ /// P2 coordinates (intersection point)
+ /// P2 coordinates (intersection point)
+ /// P3 coordinates (circumcenter point)
+ /// P3 coordinates (circumcenter point)
+ ///
+ /// Returns true, if given point is the correct one otherwise return false.
+ static bool ChooseCorrectPoint(
+ double x1, double y1,
+ double x2, double y2,
+ double x3, double y3, bool isObtuse)
+ {
+ double d1, d2;
+ bool p;
+
+ // squared distance between circumcenter and intersection point
+ d1 = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3);
+ // squared distance between bisector point and intersection point
+ d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
+
+ if (isObtuse)
+ {
+ // obtuse case
+ if (d2 >= d1)
+ {
+ p = true; // means we have found the right point
+ }
+ else
+ {
+ p = false; // means take the other point
+ }
+ }
+ else
+ {
+ // non-obtuse case
+ if (d2 < d1)
+ {
+ p = true; // means we have found the right point
+ }
+ else
+ {
+ p = false; // means take the other point
+ }
+ }
+ /// HANDLE RIGHT TRIANGLE CASE!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ return p;
+
+ }
+
+ ///
+ /// This function returns a pointer array which first index indicates the whether
+ /// the point is in between the other points, followed by coordinate pairs.
+ ///
+ /// P1 coordinates [point of line] (point on Voronoi edge - intersection)
+ /// P1 coordinates [point of line] (point on Voronoi edge - intersection)
+ /// P2 coordinates [point of line] (circumcenter)
+ /// P2 coordinates [point of line] (circumcenter)
+ /// P3 coordinates [point to be compared] (neighbor's circumcenter)
+ /// P3 coordinates [point to be compared] (neighbor's circumcenter)
+ ///
+ static void PointBetweenPoints(double x1, double y1, double x2, double y2, double x, double y, ref double[] p)
+ {
+ // now check whether the point is close to circumcenter than intersection point
+ // BETWEEN THE POINTS
+ if ((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y) < (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
+ {
+ p[0] = 1.0;
+ // calculate the squared distance to circumcenter
+ p[1] = (x - x2) * (x - x2) + (y - y2) * (y - y2);
+ p[2] = x;
+ p[3] = y;
+ }// *NOT* BETWEEN THE POINTS
+ else
+ {
+ p[0] = 0.0;
+ p[1] = 0.0;
+ p[2] = 0.0;
+ p[3] = 0.0;
+ }
+ }
+
+ ///
+ /// Given three coordinates of a triangle, tests a triangle to see if it satisfies
+ /// the minimum and/or maximum angle condition.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Returns true, if it is a BAD triangle, returns false if it is a GOOD triangle.
+ static bool IsBadTriangleAngle(double x1, double y1, double x2, double y2, double x3, double y3)
+ {
+ // variables keeping the distance values for the edges
+ double dxod, dyod, dxda, dyda, dxao, dyao;
+ double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2;
+
+ double apexlen, orglen, destlen, minedge;
+ double angle; // in order to check minimum angle condition
+
+ double maxangle, maxedge; // in order to check minimum angle condition
+ // calculate the side lengths
+
+ dxod = x1 - x2;
+ dyod = y1 - y2;
+ dxda = x2 - x3;
+ dyda = y2 - y3;
+ dxao = x3 - x1;
+ dyao = y3 - y1;
+ // calculate the squares of the side lentghs
+ dxod2 = dxod * dxod;
+ dyod2 = dyod * dyod;
+ dxda2 = dxda * dxda;
+ dyda2 = dyda * dyda;
+ dxao2 = dxao * dxao;
+ dyao2 = dyao * dyao;
+
+ // Find the lengths of the triangle's three edges.
+ apexlen = dxod2 + dyod2;
+ orglen = dxda2 + dyda2;
+ destlen = dxao2 + dyao2;
+
+ // try to find the minimum edge and accordingly the pqr orientation
+ if ((apexlen < orglen) && (apexlen < destlen))
+ {
+ // The edge opposite the apex is shortest.
+ minedge = apexlen;
+ // Find the square of the cosine of the angle at the apex.
+ angle = dxda * dxao + dyda * dyao;
+ angle = angle * angle / (orglen * destlen);
+
+
+ }
+ else if (orglen < destlen)
+ {
+ // The edge opposite the origin is shortest.
+ minedge = orglen;
+ // Find the square of the cosine of the angle at the origin.
+ angle = dxod * dxao + dyod * dyao;
+ angle = angle * angle / (apexlen * destlen);
+
+
+ }
+ else
+ {
+ // The edge opposite the destination is shortest.
+ minedge = destlen;
+ // Find the square of the cosine of the angle at the destination.
+ angle = dxod * dxda + dyod * dyda;
+ angle = angle * angle / (apexlen * orglen);
+
+ }
+ // try to find the maximum edge and accordingly the pqr orientation
+ if ((apexlen > orglen) && (apexlen > destlen))
+ {
+ // The edge opposite the apex is longest.
+ maxedge = apexlen;
+ // Find the cosine of the angle at the apex.
+ maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen) * Math.Sqrt(destlen));
+ }
+ else if (orglen > destlen)
+ {
+ // The edge opposite the origin is longest.
+ maxedge = orglen;
+ // Find the cosine of the angle at the origin.
+ maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen) * Math.Sqrt(destlen));
+ }
+ else
+ {
+ // The edge opposite the destination is longest.
+ maxedge = destlen;
+ // Find the cosine of the angle at the destination.
+ maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen) * Math.Sqrt(orglen));
+ }
+
+
+ // Check whether the angle is smaller than permitted.
+ if ((angle > Behavior.GoodAngle) || (Behavior.MaxAngle != 0.00 && maxangle < Behavior.MaxGoodAngle))
+ {
+ return true;// it is a bad triangle
+ }
+ return false;// it is a good triangle
+
+ }
+
+ ///
+ /// Given the triangulation, and a vertex returns the minimum distance to the
+ /// vertices of the triangle where the given vertex located.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ static double MinDistanceToNeighbor(Mesh m, double newlocX, double newlocY, ref Otri searchtri)
+ {
+ Otri horiz = default(Otri); // for search operation
+ LocateResult intersect = LocateResult.Outside;
+ Vertex v1, v2, v3, torg, tdest;
+ double d1, d2, d3, ahead;
+ //triangle ptr; // Temporary variable used by sym().
+
+ Point2 newvertex = new Point2(newlocX, newlocY);
+
+ // printf("newvertex %f,%f\n", newvertex[0], newvertex[1]);
+ // Find the location of the vertex to be inserted. Check if a good
+ // starting triangle has already been provided by the caller.
+ // Find a boundary triangle.
+ //horiz.tri = m.dummytri;
+ //horiz.orient = 0;
+ //horiz.symself();
+ // Search for a triangle containing 'newvertex'.
+ // Start searching from the triangle provided by the caller.
+ // Where are we?
+ torg = searchtri.Org();
+ tdest = searchtri.Dest();
+ // Check the starting triangle's vertices.
+ if ((torg.pt.X == newvertex.X) && (torg.pt.Y == newvertex.Y))
+ {
+ intersect = LocateResult.OnVertex;
+ searchtri.Copy(ref horiz);
+
+ }
+ else if ((tdest.pt.X == newvertex.X) && (tdest.pt.Y == newvertex.Y))
+ {
+ searchtri.LnextSelf();
+ intersect = LocateResult.OnVertex;
+ searchtri.Copy(ref horiz);
+ }
+ else
+ {
+ // Orient 'searchtri' to fit the preconditions of calling preciselocate().
+ ahead = Primitives.CounterClockwise(torg.pt, tdest.pt, newvertex);
+ if (ahead < 0.0)
+ {
+ // Turn around so that 'searchpoint' is to the left of the
+ // edge specified by 'searchtri'.
+ searchtri.SymSelf();
+ searchtri.Copy(ref horiz);
+ intersect = m.PreciseLocate(newvertex, ref horiz, false);
+ }
+ else if (ahead == 0.0)
+ {
+ // Check if 'searchpoint' is between 'torg' and 'tdest'.
+ if (((torg.pt.X < newvertex.X) == (newvertex.X < tdest.pt.X)) &&
+ ((torg.pt.Y < newvertex.Y) == (newvertex.Y < tdest.pt.Y)))
+ {
+ intersect = LocateResult.OnEdge;
+ searchtri.Copy(ref horiz);
+
+ }
+ }
+ else
+ {
+ searchtri.Copy(ref horiz);
+ intersect = m.PreciseLocate(newvertex, ref horiz, false);
+ }
+ }
+ if (intersect == LocateResult.OnVertex || intersect == LocateResult.Outside)
+ {
+ // set distance to 0
+ //m.VertexDealloc(newvertex);
+ return 0.0;
+ }
+ else
+ { // intersect == ONEDGE || intersect == INTRIANGLE
+ // find the triangle vertices
+ v1 = horiz.Org();
+ v2 = horiz.Dest();
+ v3 = horiz.Apex();
+ d1 = (v1.pt.X - newvertex.X) * (v1.pt.X - newvertex.X) + (v1.pt.Y - newvertex.Y) * (v1.pt.Y - newvertex.Y);
+ d2 = (v2.pt.X - newvertex.X) * (v2.pt.X - newvertex.X) + (v2.pt.Y - newvertex.Y) * (v2.pt.Y - newvertex.Y);
+ d3 = (v3.pt.X - newvertex.X) * (v3.pt.X - newvertex.X) + (v3.pt.Y - newvertex.Y) * (v3.pt.Y - newvertex.Y);
+ //m.VertexDealloc(newvertex);
+ // find minimum of the distance
+ if (d1 <= d2 && d1 <= d3)
+ {
+ return d1;
+ }
+ else if (d2 <= d3)
+ {
+ return d2;
+ }
+ else
+ {
+ return d3;
+ }
+ }
+ }
+
+ /*
+ ///
+ /// Finds min angle of a triangle for .part file.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ static double ReturnMinAngle(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y)
+ {
+ double angle_p1, angle_p2, angle_p3, a2, b2, c2;
+ a2 = (p1x - p2x) * (p1x - p2x) + (p1y - p2y) * (p1y - p2y);
+ b2 = (p1x - p3x) * (p1x - p3x) + (p1y - p3y) * (p1y - p3y);
+ c2 = (p2x - p3x) * (p2x - p3x) + (p2y - p3y) * (p2y - p3y);
+ angle_p1 = Math.Acos((a2 + b2 - c2) / (2.0 * Math.Sqrt(a2) * Math.Sqrt(b2))) * 180.0 / Math.PI;
+ angle_p2 = Math.Acos((a2 + c2 - b2) / (2.0 * Math.Sqrt(a2) * Math.Sqrt(c2))) * 180.0 / Math.PI;
+ angle_p3 = Math.Acos((c2 + b2 - a2) / (2.0 * Math.Sqrt(c2) * Math.Sqrt(b2))) * 180.0 / Math.PI;
+ if (angle_p1 <= angle_p2 && angle_p1 <= angle_p3)
+ {
+ return angle_p1;
+ }
+ else if (angle_p2 <= angle_p3)
+ {
+ return angle_p2;
+ }
+ else
+ {
+ return angle_p3;
+ }
+ }
+
+ ///
+ /// Finds max angle of a triangle for .part file.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ static double returnMaxAngle(double p1x, double p1y, double p2x, double p2y, double p3x, double p3y)
+ {
+ double angle_p1, angle_p2, angle_p3, a2, b2, c2;
+ a2 = (p1x - p2x) * (p1x - p2x) + (p1y - p2y) * (p1y - p2y);
+ b2 = (p1x - p3x) * (p1x - p3x) + (p1y - p3y) * (p1y - p3y);
+ c2 = (p2x - p3x) * (p2x - p3x) + (p2y - p3y) * (p2y - p3y);
+ angle_p1 = Math.Acos((a2 + b2 - c2) / (2.0 * Math.Sqrt(a2) * Math.Sqrt(b2))) * 180.0 / Math.PI;
+ angle_p2 = Math.Acos((a2 + c2 - b2) / (2.0 * Math.Sqrt(a2) * Math.Sqrt(c2))) * 180.0 / Math.PI;
+ angle_p3 = Math.Acos((c2 + b2 - a2) / (2.0 * Math.Sqrt(c2) * Math.Sqrt(b2))) * 180.0 / Math.PI;
+ if (angle_p1 >= angle_p2 && angle_p1 >= angle_p3)
+ {
+ return angle_p1;
+ }
+ else if (angle_p2 >= angle_p3)
+ {
+ return angle_p2;
+ }
+ else
+ {
+ return angle_p3;
+ }
+ }
+
+ //*****************************************************************
+ //
+ // writeparts() Write the triangles to a .part file with the information
+ // that it is bad or good
+ //
+ //*****************************************************************
+ #ifndef TRILIBRARY
+ void writeparts(mesh m, int argc, char **argv)
+ {
+ FILE *outfile;
+ otri triangleloop;
+ vertex p1, p2, p3;
+ long elementnumber;
+ int i;
+ int bad;
+ double minAngleTri, maxAngleTri;
+ char partfilename[FILENAMESIZE];
+ strcpy(partfilename, behavior.innodefilename);
+ partfilename[strlen(partfilename) - 5] = '\0';
+ strcat(partfilename, ".1.part");
+ if (!behavior.quiet) {
+ printf("Writing %s.\n", partfilename);
+ }
+ outfile = fopen(partfilename, "w");
+ if (outfile == (FILE *) NULL) {
+ printf(" Error: Cannot create file %s.\n", partfilename);
+ triexit(1);
+ }
+ // Number of triangles, vertices per triangle, attributes per triangle.
+ fprintf(outfile, "%ld 103\n", m.triangles.items);
+
+ traversalinit(&m.triangles);
+ triangleloop.tri = triangletraverse(m);
+ triangleloop.orient = 0;
+ elementnumber = behavior.firstnumber;
+ while (triangleloop.tri != (triangle *) NULL) {
+ org(triangleloop, p1);
+ dest(triangleloop, p2);
+ apex(triangleloop, p3);
+ if (behavior.order == 1) {
+ // we need to check if this triangle is good or not
+ //bad = testTriangleAngleArea(m, b, &p1[0], &p1[1],&p2[0], &p2[1],&p3[0], &p3[1]);
+ bad = testTriangleAngle(m, b, &p1[0], &p1[1],&p2[0], &p2[1],&p3[0], &p3[1]);
+ minAngleTri = returnMinAngle(p1[0], p1[1],p2[0], p2[1],p3[0], p3[1]);
+ maxAngleTri = returnMaxAngle(p1[0], p1[1],p2[0], p2[1],p3[0], p3[1]);
+ if(bad){
+ //printf("!!!!!!!!bad >> p1[0]=%f,p1[1]=%f,p2[0]=%f,p2[1]=%f,p3[0]=%f,p3[1]=%f\n", p1[0], p1[1],p2[0], p2[1],p3[0], p3[1]);
+ // Triangle number, indices for three vertices.
+ if(behavior.maxangle != 0.00000){
+ fprintf(outfile, "%4ld %d ", elementnumber,(int)((180.0-maxAngleTri)*0.35+2.0));
+ }else{
+ fprintf(outfile, "%4ld %d ", elementnumber, 1);
+ }
+ }else{
+ if(behavior.maxangle != 0.00000){
+ fprintf(outfile, "%4ld %d ", elementnumber,(int)((180.0-maxAngleTri)*0.35+54.0+2.0));
+ }else{
+ fprintf(outfile, "%4ld %d ", elementnumber, 103);
+ }
+ }
+ }
+
+ fprintf(outfile, "\n");
+ triangleloop.tri = triangletraverse(m);
+ elementnumber++;
+ }
+ finishfile(outfile, argc, argv);
+ }
+ */
+ }
+}
\ No newline at end of file
diff --git a/Triangle.NET/Triangle/Primitives.cs b/Triangle.NET/Triangle/Primitives.cs
new file mode 100644
index 0000000..3e61aed
--- /dev/null
+++ b/Triangle.NET/Triangle/Primitives.cs
@@ -0,0 +1,463 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet
+{
+ using System;
+ using TriangleNet.Data;
+
+ ///
+ /// Provides some primitives regularly used in computational geometry.
+ ///
+ public static class Primitives
+ {
+ static double splitter; // Used to split double factors for exact multiplication.
+ static double epsilon; // Floating-point machine epsilon.
+ static double resulterrbound;
+ static double ccwerrboundA, ccwerrboundB, ccwerrboundC;
+ static double iccerrboundA, iccerrboundB, iccerrboundC;
+ static double o3derrboundA, o3derrboundB, o3derrboundC;
+
+ ///
+ /// Initialize the variables used for exact arithmetic.
+ ///
+ ///
+ /// 'epsilon' is the largest power of two such that 1.0 + epsilon = 1.0 in
+ /// floating-point arithmetic. 'epsilon' bounds the relative roundoff
+ /// error. It is used for floating-point error analysis.
+ ///
+ /// 'splitter' is used to split floating-point numbers into two half-
+ /// length significands for exact multiplication.
+ ///
+ /// I imagine that a highly optimizing compiler might be too smart for its
+ /// own good, and somehow cause this routine to fail, if it pretends that
+ /// floating-point arithmetic is too much like real arithmetic.
+ ///
+ /// Don't change this routine unless you fully understand it.
+ ///
+ public static void ExactInit()
+ {
+ double half;
+ double check, lastcheck;
+ bool every_other;
+
+ every_other = true;
+ half = 0.5;
+ epsilon = 1.0;
+ splitter = 1.0;
+ check = 1.0;
+ // Repeatedly divide 'epsilon' by two until it is too small to add to
+ // one without causing roundoff. (Also check if the sum is equal to
+ // the previous sum, for machines that round up instead of using exact
+ // rounding. Not that these routines will work on such machines.)
+ do
+ {
+ lastcheck = check;
+ epsilon *= half;
+ if (every_other)
+ {
+ splitter *= 2.0;
+ }
+ every_other = !every_other;
+ check = 1.0 + epsilon;
+ } while ((check != 1.0) && (check != lastcheck));
+ splitter += 1.0;
+ // Error bounds for orientation and incircle tests.
+ resulterrbound = (3.0 + 8.0 * epsilon) * epsilon;
+ ccwerrboundA = (3.0 + 16.0 * epsilon) * epsilon;
+ ccwerrboundB = (2.0 + 12.0 * epsilon) * epsilon;
+ ccwerrboundC = (9.0 + 64.0 * epsilon) * epsilon * epsilon;
+ iccerrboundA = (10.0 + 96.0 * epsilon) * epsilon;
+ iccerrboundB = (4.0 + 48.0 * epsilon) * epsilon;
+ iccerrboundC = (44.0 + 576.0 * epsilon) * epsilon * epsilon;
+ o3derrboundA = (7.0 + 56.0 * epsilon) * epsilon;
+ o3derrboundB = (3.0 + 28.0 * epsilon) * epsilon;
+ o3derrboundC = (26.0 + 288.0 * epsilon) * epsilon * epsilon;
+ }
+
+ ///
+ /// Check, if the three points appear in counterclockwise order. The result is
+ /// also a rough approximation of twice the signed area of the triangle defined
+ /// by the three points.
+ ///
+ ///
+ ///
+ ///
+ /// Return a positive value if the points pa, pb, and pc occur in
+ /// counterclockwise order; a negative value if they occur in clockwise order;
+ /// and zero if they are collinear.
+ ///
+ /// Uses exact arithmetic if necessary to ensure a correct answer. The
+ /// result returned is the determinant of a matrix. This determinant is
+ /// computed adaptively, in the sense that exact arithmetic is used only to
+ /// the degree it is needed to ensure that the returned value has the
+ /// correct sign. Hence, this function is usually quite fast, but will run
+ /// more slowly when the input points are collinear or nearly so.
+ ///
+ /// See Robust Predicates paper for details.
+ ///
+ public static double CounterClockwise(Point2 pa, Point2 pb, Point2 pc)
+ {
+ double detleft, detright, det;
+ double detsum, errbound;
+
+ Statistic.CounterClockwiseCount++;
+
+ detleft = (pa.X - pc.X) * (pb.Y - pc.Y);
+ detright = (pa.Y - pc.Y) * (pb.X - pc.X);
+ det = detleft - detright;
+
+ if (Behavior.NoExact)
+ {
+ return det;
+ }
+
+ if (detleft > 0.0)
+ {
+ if (detright <= 0.0)
+ {
+ return det;
+ }
+ else
+ {
+ detsum = detleft + detright;
+ }
+ }
+ else if (detleft < 0.0)
+ {
+ if (detright >= 0.0)
+ {
+ return det;
+ }
+ else
+ {
+ detsum = -detleft - detright;
+ }
+ }
+ else
+ {
+ return det;
+ }
+
+ errbound = ccwerrboundA * detsum;
+ if ((det >= errbound) || (-det >= errbound))
+ {
+ return det;
+ }
+
+ return det;
+
+ // TODO: throw new Exception();
+ // return counterclockwiseadapt(pa, pb, pc, detsum);
+ }
+
+
+ ///
+ /// Check if the point pd lies inside the circle passing through pa, pb, and pc. The
+ /// points pa, pb, and pc must be in counterclockwise order, or the sign of the result
+ /// will be reversed.
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Return a positive value if the point pd lies inside the circle passing through
+ /// pa, pb, and pc; a negative value if it lies outside; and zero if the four points
+ /// are cocircular.
+ ///
+ /// Uses exact arithmetic if necessary to ensure a correct answer. The
+ /// result returned is the determinant of a matrix. This determinant is
+ /// computed adaptively, in the sense that exact arithmetic is used only to
+ /// the degree it is needed to ensure that the returned value has the
+ /// correct sign. Hence, this function is usually quite fast, but will run
+ /// more slowly when the input points are cocircular or nearly so.
+ ///
+ /// See Robust Predicates paper for details.
+ ///
+ public static double InCircle(Point2 pa, Point2 pb, Point2 pc, Point2 pd)
+ {
+ double adx, bdx, cdx, ady, bdy, cdy;
+ double bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady;
+ double alift, blift, clift;
+ double det;
+ double permanent, errbound;
+
+ Statistic.InCircleCount++;
+
+ adx = pa.X - pd.X;
+ bdx = pb.X - pd.X;
+ cdx = pc.X - pd.X;
+ ady = pa.Y - pd.Y;
+ bdy = pb.Y - pd.Y;
+ cdy = pc.Y - pd.Y;
+
+ bdxcdy = bdx * cdy;
+ cdxbdy = cdx * bdy;
+ alift = adx * adx + ady * ady;
+
+ cdxady = cdx * ady;
+ adxcdy = adx * cdy;
+ blift = bdx * bdx + bdy * bdy;
+
+ adxbdy = adx * bdy;
+ bdxady = bdx * ady;
+ clift = cdx * cdx + cdy * cdy;
+
+ det = alift * (bdxcdy - cdxbdy)
+ + blift * (cdxady - adxcdy)
+ + clift * (adxbdy - bdxady);
+
+ if (Behavior.NoExact)
+ {
+ return det;
+ }
+
+ permanent = (Math.Abs(bdxcdy) + Math.Abs(cdxbdy)) * alift
+ + (Math.Abs(cdxady) + Math.Abs(adxcdy)) * blift
+ + (Math.Abs(adxbdy) + Math.Abs(bdxady)) * clift;
+ errbound = iccerrboundA * permanent;
+ if ((det > errbound) || (-det > errbound))
+ {
+ return det;
+ }
+
+ return det;
+
+ // TODO: throw new Exception();
+ //return incircleadapt(pa, pb, pc, pd, permanent);
+ }
+
+ ///
+ /// Return a positive value if the point pd is incompatible with the circle
+ /// or plane passing through pa, pb, and pc (meaning that pd is inside the
+ /// circle or below the plane); a negative value if it is compatible; and
+ /// zero if the four points are cocircular/coplanar. The points pa, pb, and
+ /// pc must be in counterclockwise order, or the sign of the result will be
+ /// reversed.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// If the -w switch is used, the points are lifted onto the parabolic
+ /// lifting map, then they are dropped according to their weights, then the
+ /// 3D orientation test is applied. If the -W switch is used, the points'
+ /// heights are already provided, so the 3D orientation test is applied
+ /// directly. If neither switch is used, the incircle test is applied.
+ ///
+ public static double NonRegular(Point2 pa, Point2 pb, Point2 pc, Point2 pd)
+ {
+ return InCircle(pa, pb, pc, pd);
+ }
+
+ ///
+ /// Find the circumcenter of a triangle.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The result is returned both in terms of x-y coordinates and xi-eta
+ /// (barycentric) coordinates. The xi-eta coordinate system is defined in
+ /// terms of the triangle: the origin of the triangle is the origin of the
+ /// coordinate system; the destination of the triangle is one unit along the
+ /// xi axis; and the apex of the triangle is one unit along the eta axis.
+ /// This procedure also returns the square of the length of the triangle's
+ /// shortest edge.
+ ///
+ public static Point2 FindCircumcenter(Point2 torg, Point2 tdest, Point2 tapex,
+ ref double xi, ref double eta, bool offcenter)
+ {
+ double xdo, ydo, xao, yao;
+ double dodist, aodist, dadist;
+ double denominator;
+ double dx, dy, dxoff, dyoff;
+
+ Statistic.CircumcenterCount++;
+
+ // Compute the circumcenter of the triangle.
+ xdo = tdest.X - torg.X;
+ ydo = tdest.Y - torg.Y;
+ xao = tapex.X - torg.X;
+ yao = tapex.Y - torg.Y;
+ dodist = xdo * xdo + ydo * ydo;
+ aodist = xao * xao + yao * yao;
+ dadist = (tdest.X - tapex.X) * (tdest.X - tapex.X) +
+ (tdest.Y - tapex.Y) * (tdest.Y - tapex.Y);
+ if (Behavior.NoExact)
+ {
+ denominator = 0.5 / (xdo * yao - xao * ydo);
+ }
+ else
+ {
+ // Use the counterclockwise() routine to ensure a positive (and
+ // reasonably accurate) result, avoiding any possibility of
+ // division by zero.
+ denominator = 0.5 / CounterClockwise(tdest, tapex, torg);
+ // Don't count the above as an orientation test.
+ Statistic.CounterClockwiseCount--;
+ }
+ dx = (yao * dodist - ydo * aodist) * denominator;
+ dy = (xdo * aodist - xao * dodist) * denominator;
+
+ // Find the (squared) length of the triangle's shortest edge. This
+ // serves as a conservative estimate of the insertion radius of the
+ // circumcenter's parent. The estimate is used to ensure that
+ // the algorithm terminates even if very small angles appear in
+ // the input PSLG.
+ if ((dodist < aodist) && (dodist < dadist))
+ {
+ if (offcenter && (Behavior.Offconstant > 0.0))
+ {
+ // Find the position of the off-center, as described by Alper Ungor.
+ dxoff = 0.5 * xdo - Behavior.Offconstant * ydo;
+ dyoff = 0.5 * ydo + Behavior.Offconstant * xdo;
+ // If the off-center is closer to the origin than the
+ // circumcenter, use the off-center instead.
+ if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy)
+ {
+ dx = dxoff;
+ dy = dyoff;
+ }
+ }
+ }
+ else if (aodist < dadist)
+ {
+ if (offcenter && (Behavior.Offconstant > 0.0))
+ {
+ dxoff = 0.5 * xao + Behavior.Offconstant * yao;
+ dyoff = 0.5 * yao - Behavior.Offconstant * xao;
+ // If the off-center is closer to the origin than the
+ // circumcenter, use the off-center instead.
+ if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy)
+ {
+ dx = dxoff;
+ dy = dyoff;
+ }
+ }
+ }
+ else
+ {
+ if (offcenter && (Behavior.Offconstant > 0.0))
+ {
+ dxoff = 0.5 * (tapex.X - tdest.X) -
+ Behavior.Offconstant * (tapex.Y - tdest.Y);
+ dyoff = 0.5 * (tapex.Y - tdest.Y) +
+ Behavior.Offconstant * (tapex.X - tdest.X);
+ // If the off-center is closer to the destination than the
+ // circumcenter, use the off-center instead.
+ if (dxoff * dxoff + dyoff * dyoff <
+ (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo))
+ {
+ dx = xdo + dxoff;
+ dy = ydo + dyoff;
+ }
+ }
+ }
+
+ Point2 circumcenter = new Point2(torg.X + dx, torg.Y + dy);
+
+ // To interpolate vertex attributes for the new vertex inserted at
+ // the circumcenter, define a coordinate system with a xi-axis,
+ // directed from the triangle's origin to its destination, and
+ // an eta-axis, directed from its origin to its apex.
+ // Calculate the xi and eta coordinates of the circumcenter.
+ xi = (yao * dx - xao * dy) * (2.0 * denominator);
+ eta = (xdo * dy - ydo * dx) * (2.0 * denominator);
+
+ return circumcenter;
+ }
+
+
+ /*
+ ///
+ /// Return a positive value if the point pd lies below the plane passing
+ /// through pa, pb, and pc; "below" is defined so that pa, pb, and pc appear
+ /// in counterclockwise order when viewed from above the plane. The result is
+ /// also a rough approximation of six times the signed volume of the
+ /// tetrahedron defined by the four points.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Return a positive value if the point pd lies below the plane
+ /// passing through pa, pb, and pc. Returns a negative value if pd lies above
+ /// the plane. Returns zero if the points are coplanar.
+ ///
+ /// Uses exact arithmetic if necessary to ensure a correct answer. The
+ /// result returned is the determinant of a matrix. This determinant is
+ /// computed adaptively, in the sense that exact arithmetic is used only to
+ /// the degree it is needed to ensure that the returned value has the
+ /// correct sign. Hence, this function is usually quite fast, but will run
+ /// more slowly when the input points are coplanar or nearly so.
+ ///
+ /// See my Robust Predicates paper for details.
+ ///
+ public static double Orient3d2(Point2 pa, Point2 pb, Point2 pc, Point2 pd,
+ double aheight, double bheight, double cheight, double dheight)
+ {
+ double adx, bdx, cdx, ady, bdy, cdy, adheight, bdheight, cdheight;
+ double bdxcdy, cdxbdy, cdxady, adxcdy, adxbdy, bdxady;
+ double det;
+ double permanent, errbound;
+
+ Statistic.Orient3dCount++;
+
+ adx = pa.X - pd.X;
+ bdx = pb.X - pd.X;
+ cdx = pc.X - pd.X;
+ ady = pa.Y - pd.Y;
+ bdy = pb.Y - pd.Y;
+ cdy = pc.Y - pd.Y;
+ adheight = aheight - dheight;
+ bdheight = bheight - dheight;
+ cdheight = cheight - dheight;
+
+ bdxcdy = bdx * cdy;
+ cdxbdy = cdx * bdy;
+
+ cdxady = cdx * ady;
+ adxcdy = adx * cdy;
+
+ adxbdy = adx * bdy;
+ bdxady = bdx * ady;
+
+ det = adheight * (bdxcdy - cdxbdy)
+ + bdheight * (cdxady - adxcdy)
+ + cdheight * (adxbdy - bdxady);
+
+ if (Behavior.NoExact)
+ {
+ return det;
+ }
+
+ permanent = (Math.Abs(bdxcdy) + Math.Abs(cdxbdy)) * Math.Abs(adheight)
+ + (Math.Abs(cdxady) + Math.Abs(adxcdy)) * Math.Abs(bdheight)
+ + (Math.Abs(adxbdy) + Math.Abs(bdxady)) * Math.Abs(cdheight);
+ errbound = o3derrboundA * permanent;
+ if ((det > errbound) || (-det > errbound))
+ {
+ return det;
+ }
+
+ throw new Exception();
+ //return orient3dadapt(pa, pb, pc, pd, aheight, bheight, cheight, dheight, permanent);
+ }
+ */
+ }
+}
diff --git a/Triangle.NET/Triangle/Properties/AssemblyInfo.cs b/Triangle.NET/Triangle/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..330e970
--- /dev/null
+++ b/Triangle.NET/Triangle/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")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Triangle")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("96a540d0-1772-4bed-8d25-ef5fa23cd1bc")]
+
+// 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/Quality.cs b/Triangle.NET/Triangle/Quality.cs
new file mode 100644
index 0000000..44beb91
--- /dev/null
+++ b/Triangle.NET/Triangle/Quality.cs
@@ -0,0 +1,998 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet
+{
+ using System;
+ using System.Collections.Generic;
+ using TriangleNet.Data;
+ using TriangleNet.Log;
+
+ ///
+ /// Provides methods for mesh quality enforcement and testing.
+ ///
+ class Quality
+ {
+ Queue badsubsegs;
+ BadTriQueue queue;
+ Mesh mesh;
+ Func userTest;
+
+ ILog logger;
+
+ public Quality(Mesh m)
+ {
+ logger = SimpleLogger.Instance;
+
+ badsubsegs = new Queue();
+ queue = new BadTriQueue();
+ mesh = m;
+ }
+
+ ///
+ /// Deallocate space for a bad subsegment, marking it dead.
+ ///
+ ///
+ public void AddBadSubseg(BadSubseg badseg)
+ {
+ badsubsegs.Enqueue(badseg);
+ }
+
+ #region Check
+
+ ///
+ /// Test the mesh for topological consistency.
+ ///
+ public bool CheckMesh()
+ {
+ Otri tri = default(Otri);
+ Otri oppotri = default(Otri), oppooppotri = default(Otri);
+ Vertex triorg, tridest, triapex;
+ Vertex oppoorg, oppodest;
+ int horrors;
+ bool saveexact;
+
+ // Temporarily turn on exact arithmetic if it's off.
+ saveexact = Behavior.NoExact;
+ Behavior.NoExact = false;
+ horrors = 0;
+
+ // Run through the list of triangles, checking each one.
+ foreach (var t in mesh.triangles.Values)
+ {
+ tri.triangle = t;
+
+ // Check all three edges of the triangle.
+ for (tri.orient = 0; tri.orient < 3; tri.orient++)
+ {
+ triorg = tri.Org();
+ tridest = tri.Dest();
+ if (tri.orient == 0)
+ { // Only test for inversion once.
+ // Test if the triangle is flat or inverted.
+ triapex = tri.Apex();
+ if (Primitives.CounterClockwise(triorg.pt, tridest.pt, triapex.pt) <= 0.0)
+ {
+ horrors++;
+ }
+ }
+ // Find the neighboring triangle on this edge.
+ tri.Sym(ref oppotri);
+ if (oppotri.triangle != Mesh.dummytri)
+ {
+ // Check that the triangle's neighbor knows it's a neighbor.
+ oppotri.Sym(ref oppooppotri);
+ if ((tri.triangle != oppooppotri.triangle) || (tri.orient != oppooppotri.orient))
+ {
+ if (tri.triangle == oppooppotri.triangle)
+ {
+ logger.Warning("Asymmetric triangle-triangle bond: (Right triangle, wrong orientation)",
+ "Quality.CheckMesh()");
+ }
+
+ horrors++;
+ }
+ // Check that both triangles agree on the identities
+ // of their shared vertices.
+ oppoorg = oppotri.Org();
+ oppodest = oppotri.Dest();
+ if ((triorg != oppodest) || (tridest != oppoorg))
+ {
+ logger.Warning("Mismatched edge coordinates between two triangles.",
+ "Quality.CheckMesh()");
+
+ horrors++;
+ }
+ }
+ }
+ }
+
+ if (horrors == 0)
+ {
+ if (Behavior.Verbose)
+ {
+ logger.Info("In my studied opinion, the mesh appears to be consistent.");
+ }
+ }
+ else if (horrors == 1)
+ {
+ logger.Info("Precisely one festering wound discovered.");
+ }
+ else
+ {
+ logger.Info(horrors + " abominations witnessed.");
+ }
+ // Restore the status of exact arithmetic.
+ Behavior.NoExact = saveexact;
+
+ return (horrors == 0);
+ }
+
+ ///
+ /// Ensure that the mesh is (constrained) Delaunay.
+ ///
+ public bool CheckDelaunay()
+ {
+ Otri triangleloop = default(Otri);
+ Otri oppotri = default(Otri);
+ Osub opposubseg = default(Osub);
+ Vertex triorg, tridest, triapex;
+ Vertex oppoapex;
+ bool shouldbedelaunay;
+ int horrors;
+ bool saveexact;
+
+ // Temporarily turn on exact arithmetic if it's off.
+ saveexact = Behavior.NoExact;
+ Behavior.NoExact = false;
+ horrors = 0;
+
+ // Run through the list of triangles, checking each one.
+ foreach (var t in mesh.triangles.Values)
+ {
+ triangleloop.triangle = t;
+
+ // Check all three edges of the triangle.
+ for (triangleloop.orient = 0; triangleloop.orient < 3;
+ triangleloop.orient++)
+ {
+ triorg = triangleloop.Org();
+ tridest = triangleloop.Dest();
+ triapex = triangleloop.Apex();
+ triangleloop.Sym(ref oppotri);
+ oppoapex = oppotri.Apex();
+ // Only test that the edge is locally Delaunay if there is an
+ // adjoining triangle whose pointer is larger (to ensure that
+ // each pair isn't tested twice).
+ shouldbedelaunay = (oppotri.triangle != Mesh.dummytri) &&
+ !Otri.IsDead(oppotri.triangle) && //(triangleloop.tri < oppotri.tri) &&
+ (triorg != mesh.infvertex1) && (triorg != mesh.infvertex2) &&
+ (triorg != mesh.infvertex3) &&
+ (tridest != mesh.infvertex1) && (tridest != mesh.infvertex2) &&
+ (tridest != mesh.infvertex3) &&
+ (triapex != mesh.infvertex1) && (triapex != mesh.infvertex2) &&
+ (triapex != mesh.infvertex3) &&
+ (oppoapex != mesh.infvertex1) && (oppoapex != mesh.infvertex2) &&
+ (oppoapex != mesh.infvertex3);
+ if (mesh.checksegments && shouldbedelaunay)
+ {
+ // If a subsegment separates the triangles, then the edge is
+ // constrained, so no local Delaunay test should be done.
+ triangleloop.SegPivot(ref opposubseg);
+ if (opposubseg.ss != Mesh.dummysub)
+ {
+ shouldbedelaunay = false;
+ }
+ }
+ if (shouldbedelaunay)
+ {
+ if (Primitives.NonRegular(triorg.pt, tridest.pt, triapex.pt, oppoapex.pt) > 0.0)
+ {
+ logger.Warning("Non-regular pair of triangles found.", "Quality.CheckDelaunay()");
+ horrors++;
+ }
+ }
+ }
+
+ }
+ if (horrors == 0)
+ {
+ if (Behavior.Verbose)
+ {
+ logger.Info("By virtue of my perceptive intelligence, I declare the mesh Delaunay.");
+ }
+ }
+ else if (horrors == 1)
+ {
+ logger.Info("Precisely one terrifying transgression identified.");
+ }
+ else
+ {
+ logger.Info(horrors + " obscenities viewed with horror.");
+ }
+ // Restore the status of exact arithmetic.
+ Behavior.NoExact = saveexact;
+
+ return (horrors == 0);
+ }
+
+ ///
+ /// Check a subsegment to see if it is encroached; add it to the list if it is.
+ ///
+ ///
+ /// Returns a nonzero value if the subsegment is encroached.
+ ///
+ /// A subsegment is encroached if there is a vertex in its diametral lens.
+ /// For Ruppert's algorithm (-D switch), the "diametral lens" is the
+ /// diametral circle. For Chew's algorithm (default), the diametral lens is
+ /// just big enough to enclose two isosceles triangles whose bases are the
+ /// subsegment. Each of the two isosceles triangles has two angles equal
+ /// to 'b.minangle'.
+ ///
+ /// Chew's algorithm does not require diametral lenses at all--but they save
+ /// time. Any vertex inside a subsegment's diametral lens implies that the
+ /// triangle adjoining the subsegment will be too skinny, so it's only a
+ /// matter of time before the encroaching vertex is deleted by Chew's
+ /// algorithm. It's faster to simply not insert the doomed vertex in the
+ /// first place, which is why I use diametral lenses with Chew's algorithm.
+ ///
+ public int CheckSeg4Encroach(ref Osub testsubseg)
+ {
+ Otri neighbortri = default(Otri);
+ Osub testsym = default(Osub);
+ BadSubseg encroachedseg;
+ double dotproduct;
+ int encroached;
+ int sides;
+ Vertex eorg, edest, eapex;
+
+ encroached = 0;
+ sides = 0;
+
+ eorg = testsubseg.Org();
+ edest = testsubseg.Dest();
+ // Check one neighbor of the subsegment.
+ testsubseg.TriPivot(ref neighbortri);
+ // Does the neighbor exist, or is this a boundary edge?
+ if (neighbortri.triangle != Mesh.dummytri)
+ {
+ sides++;
+ // Find a vertex opposite this subsegment.
+ eapex = neighbortri.Apex();
+ // Check whether the apex is in the diametral lens of the subsegment
+ // (the diametral circle if 'conformdel' is set). A dot product
+ // of two sides of the triangle is used to check whether the angle
+ // at the apex is greater than (180 - 2 'minangle') degrees (for
+ // lenses; 90 degrees for diametral circles).
+ dotproduct = (eorg.pt.X - eapex.pt.X) * (edest.pt.X - eapex.pt.X) +
+ (eorg.pt.Y - eapex.pt.Y) * (edest.pt.Y - eapex.pt.Y);
+ if (dotproduct < 0.0)
+ {
+ if (Behavior.ConformDel ||
+ (dotproduct * dotproduct >=
+ (2.0 * Behavior.GoodAngle - 1.0) * (2.0 * Behavior.GoodAngle - 1.0) *
+ ((eorg.pt.X - eapex.pt.X) * (eorg.pt.X - eapex.pt.X) +
+ (eorg.pt.Y - eapex.pt.Y) * (eorg.pt.Y - eapex.pt.Y)) *
+ ((edest.pt.X - eapex.pt.X) * (edest.pt.X - eapex.pt.X) +
+ (edest.pt.Y - eapex.pt.Y) * (edest.pt.Y - eapex.pt.Y))))
+ {
+ encroached = 1;
+ }
+ }
+ }
+ // Check the other neighbor of the subsegment.
+ testsubseg.Sym(ref testsym);
+ testsym.TriPivot(ref neighbortri);
+ // Does the neighbor exist, or is this a boundary edge?
+ if (neighbortri.triangle != Mesh.dummytri)
+ {
+ sides++;
+ // Find the other vertex opposite this subsegment.
+ eapex = neighbortri.Apex();
+ // Check whether the apex is in the diametral lens of the subsegment
+ // (or the diametral circle, if 'conformdel' is set).
+ dotproduct = (eorg.pt.X - eapex.pt.X) * (edest.pt.X - eapex.pt.X) +
+ (eorg.pt.Y - eapex.pt.Y) * (edest.pt.Y - eapex.pt.Y);
+ if (dotproduct < 0.0)
+ {
+ if (Behavior.ConformDel ||
+ (dotproduct * dotproduct >=
+ (2.0 * Behavior.GoodAngle - 1.0) * (2.0 * Behavior.GoodAngle - 1.0) *
+ ((eorg.pt.X - eapex.pt.X) * (eorg.pt.X - eapex.pt.X) +
+ (eorg.pt.Y - eapex.pt.Y) * (eorg.pt.Y - eapex.pt.Y)) *
+ ((edest.pt.X - eapex.pt.X) * (edest.pt.X - eapex.pt.X) +
+ (edest.pt.Y - eapex.pt.Y) * (edest.pt.Y - eapex.pt.Y))))
+ {
+ encroached += 2;
+ }
+ }
+ }
+
+ if (encroached > 0 && (Behavior.NoBisect == 0 || ((Behavior.NoBisect == 1) && (sides == 2))))
+ {
+ // Add the subsegment to the list of encroached subsegments.
+ // Be sure to get the orientation right.
+ encroachedseg = new BadSubseg();
+ if (encroached == 1)
+ {
+ encroachedseg.encsubseg = testsubseg;
+ encroachedseg.subsegorg = eorg;
+ encroachedseg.subsegdest = edest;
+ }
+ else
+ {
+ encroachedseg.encsubseg = testsym;
+ encroachedseg.subsegorg = edest;
+ encroachedseg.subsegdest = eorg;
+ }
+
+ badsubsegs.Enqueue(encroachedseg);
+ }
+
+ return encroached;
+ }
+
+ ///
+ /// Test a triangle for quality and size.
+ ///
+ /// Triangle to check.
+ ///
+ /// Tests a triangle to see if it satisfies the minimum angle condition and
+ /// the maximum area condition. Triangles that aren't up to spec are added
+ /// to the bad triangle queue.
+ ///
+ public void TestTriangle(ref Otri testtri)
+ {
+ Otri tri1 = default(Otri), tri2 = default(Otri);
+ Osub testsub = default(Osub);
+ Vertex torg, tdest, tapex;
+ Vertex base1, base2;
+ Vertex org1, dest1, org2, dest2;
+ Vertex joinvertex;
+ double dxod, dyod, dxda, dyda, dxao, dyao;
+ double dxod2, dyod2, dxda2, dyda2, dxao2, dyao2;
+ double apexlen, orglen, destlen, minedge;
+ double angle;
+ double area;
+ double dist1, dist2;
+
+ double maxedge, maxangle;
+
+ torg = testtri.Org();
+ tdest = testtri.Dest();
+ tapex = testtri.Apex();
+ dxod = torg.pt.X - tdest.pt.X;
+ dyod = torg.pt.Y - tdest.pt.Y;
+ dxda = tdest.pt.X - tapex.pt.X;
+ dyda = tdest.pt.Y - tapex.pt.Y;
+ dxao = tapex.pt.X - torg.pt.X;
+ dyao = tapex.pt.Y - torg.pt.Y;
+ dxod2 = dxod * dxod;
+ dyod2 = dyod * dyod;
+ dxda2 = dxda * dxda;
+ dyda2 = dyda * dyda;
+ dxao2 = dxao * dxao;
+ dyao2 = dyao * dyao;
+ // Find the lengths of the triangle's three edges.
+ apexlen = dxod2 + dyod2;
+ orglen = dxda2 + dyda2;
+ destlen = dxao2 + dyao2;
+
+ if ((apexlen < orglen) && (apexlen < destlen))
+ {
+ // The edge opposite the apex is shortest.
+ minedge = apexlen;
+ // Find the square of the cosine of the angle at the apex.
+ angle = dxda * dxao + dyda * dyao;
+ angle = angle * angle / (orglen * destlen);
+ base1 = torg;
+ base2 = tdest;
+ testtri.Copy(ref tri1);
+ }
+ else if (orglen < destlen)
+ {
+ // The edge opposite the origin is shortest.
+ minedge = orglen;
+ // Find the square of the cosine of the angle at the origin.
+ angle = dxod * dxao + dyod * dyao;
+ angle = angle * angle / (apexlen * destlen);
+ base1 = tdest;
+ base2 = tapex;
+ testtri.Lnext(ref tri1);
+ }
+ else
+ {
+ // The edge opposite the destination is shortest.
+ minedge = destlen;
+ // Find the square of the cosine of the angle at the destination.
+ angle = dxod * dxda + dyod * dyda;
+ angle = angle * angle / (apexlen * orglen);
+ base1 = tapex;
+ base2 = torg;
+ testtri.Lprev(ref tri1);
+ }
+
+ if (Behavior.VarArea || Behavior.FixedArea || Behavior.Usertest)
+ {
+ // Check whether the area is larger than permitted.
+ area = 0.5 * (dxod * dyda - dyod * dxda);
+ if (Behavior.FixedArea && (area > Behavior.MaxArea))
+ {
+ // Add this triangle to the list of bad triangles.
+ queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
+ return;
+ }
+
+ // Nonpositive area constraints are treated as unconstrained.
+ if ((Behavior.VarArea) && (area > testtri.triangle.area) && (testtri.triangle.area > 0.0))
+ {
+ // Add this triangle to the list of bad triangles.
+ queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
+ return;
+ }
+
+ // Check whether the user thinks this triangle is too large.
+ if (Behavior.Usertest && userTest != null)
+ {
+ if (userTest(torg, tdest, tapex, area))
+ {
+ queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
+ return;
+ }
+ }
+ }
+
+ // find the maximum edge and accordingly the pqr orientation
+ if ((apexlen > orglen) && (apexlen > destlen))
+ {
+ // The edge opposite the apex is longest.
+ maxedge = apexlen;
+ // Find the cosine of the angle at the apex.
+ maxangle = (orglen + destlen - apexlen) / (2 * Math.Sqrt(orglen) * Math.Sqrt(destlen));
+ }
+ else if (orglen > destlen)
+ {
+ // The edge opposite the origin is longest.
+ maxedge = orglen;
+ // Find the cosine of the angle at the origin.
+ maxangle = (apexlen + destlen - orglen) / (2 * Math.Sqrt(apexlen) * Math.Sqrt(destlen));
+ }
+ else
+ {
+ // The edge opposite the destination is longest.
+ maxedge = destlen;
+ // Find the cosine of the angle at the destination.
+ maxangle = (apexlen + orglen - destlen) / (2 * Math.Sqrt(apexlen) * Math.Sqrt(orglen));
+ }
+
+ // Check whether the angle is smaller than permitted.
+ if ((angle > Behavior.GoodAngle) || (maxangle < Behavior.MaxGoodAngle && Behavior.MaxAngle != 0.0))
+ {
+ // Use the rules of Miller, Pav, and Walkington to decide that certain
+ // triangles should not be split, even if they have bad angles.
+ // A skinny triangle is not split if its shortest edge subtends a
+ // small input angle, and both endpoints of the edge lie on a
+ // concentric circular shell. For convenience, I make a small
+ // adjustment to that rule: I check if the endpoints of the edge
+ // both lie in segment interiors, equidistant from the apex where
+ // the two segments meet.
+ // First, check if both points lie in segment interiors.
+ if ((base1.type == VertexType.SegmentVertex) &&
+ (base2.type == VertexType.SegmentVertex))
+ {
+ // Check if both points lie in a common segment. If they do, the
+ // skinny triangle is enqueued to be split as usual.
+ tri1.SegPivot(ref testsub);
+ if (testsub.ss == Mesh.dummysub)
+ {
+ // No common segment. Find a subsegment that contains 'torg'.
+ tri1.Copy(ref tri2);
+ do
+ {
+ tri1.OprevSelf();
+ tri1.SegPivot(ref testsub);
+ } while (testsub.ss == Mesh.dummysub);
+ // Find the endpoints of the containing segment.
+ org1 = testsub.SegOrg();
+ dest1 = testsub.SegDest();
+ // Find a subsegment that contains 'tdest'.
+ do
+ {
+ tri2.DnextSelf();
+ tri2.SegPivot(ref testsub);
+ } while (testsub.ss == Mesh.dummysub);
+ // Find the endpoints of the containing segment.
+ org2 = testsub.SegOrg();
+ dest2 = testsub.SegDest();
+ // Check if the two containing segments have an endpoint in common.
+ joinvertex = null;
+ if ((dest1.pt.X == org2.pt.X) && (dest1.pt.Y == org2.pt.Y))
+ {
+ joinvertex = dest1;
+ }
+ else if ((org1.pt.X == dest2.pt.X) && (org1.pt.Y == dest2.pt.Y))
+ {
+ joinvertex = org1;
+ }
+ if (joinvertex != null)
+ {
+ // Compute the distance from the common endpoint (of the two
+ // segments) to each of the endpoints of the shortest edge.
+ dist1 = ((base1.pt.X - joinvertex.pt.X) * (base1.pt.X - joinvertex.pt.X) +
+ (base1.pt.Y - joinvertex.pt.Y) * (base1.pt.Y - joinvertex.pt.Y));
+ dist2 = ((base2.pt.X - joinvertex.pt.X) * (base2.pt.X - joinvertex.pt.X) +
+ (base2.pt.Y - joinvertex.pt.Y) * (base2.pt.Y - joinvertex.pt.Y));
+ // If the two distances are equal, don't split the triangle.
+ if ((dist1 < 1.001 * dist2) && (dist1 > 0.999 * dist2))
+ {
+ // Return now to avoid enqueueing the bad triangle.
+ return;
+ }
+ }
+ }
+ }
+
+ // Add this triangle to the list of bad triangles.
+ queue.Enqueue(ref testtri, minedge, tapex, torg, tdest);
+ }
+ }
+
+ #endregion
+
+ #region Maintanance
+
+ ///
+ /// Traverse the entire list of subsegments, and check each to see if it
+ /// is encroached. If so, add it to the list.
+ ///
+ void TallyEncs()
+ {
+ Osub subsegloop = default(Osub);
+ int dummy;
+
+ subsegloop.ssorient = 0;
+
+ foreach (var s in mesh.subsegs.Values)
+ {
+ subsegloop.ss = s;
+ // If the segment is encroached, add it to the list.
+ dummy = CheckSeg4Encroach(ref subsegloop);
+ //subsegloop.ss = subsegtraverse(m);
+ }
+ }
+
+ ///
+ /// Split all the encroached subsegments.
+ ///
+ /// A flag that specifies whether one should take
+ /// note of new bad triangles that result from inserting vertices to repair
+ /// encroached subsegments.
+ ///
+ /// Each encroached subsegment is repaired by splitting it - inserting a
+ /// vertex at or near its midpoint. Newly inserted vertices may encroach
+ /// upon other subsegments; these are also repaired.
+ ///
+ void SplitEncSegs(bool triflaws)
+ {
+ Otri enctri = default(Otri);
+ Otri testtri = default(Otri);
+ Osub testsh = default(Osub);
+ Osub currentenc = default(Osub);
+ BadSubseg seg;
+ Vertex eorg, edest, eapex;
+ Vertex newvertex;
+ InsertVertexResult success;
+ double segmentlength, nearestpoweroftwo;
+ double split;
+ double multiplier, divisor;
+ bool acuteorg, acuteorg2, acutedest, acutedest2;
+ int dummy;
+
+ // Note that steinerleft == -1 if an unlimited number
+ // of Steiner points is allowed.
+ while (badsubsegs.Count > 0)
+ {
+ if (mesh.steinerleft == 0)
+ {
+ break;
+ }
+
+ seg = badsubsegs.Dequeue();
+
+ currentenc = seg.encsubseg;
+ eorg = currentenc.Org();
+ edest = currentenc.Dest();
+ // Make sure that this segment is still the same segment it was
+ // when it was determined to be encroached. If the segment was
+ // enqueued multiple times (because several newly inserted
+ // vertices encroached it), it may have already been split.
+ if (!Osub.IsDead(currentenc.ss) && (eorg == seg.subsegorg) && (edest == seg.subsegdest))
+ {
+ // To decide where to split a segment, we need to know if the
+ // segment shares an endpoint with an adjacent segment.
+ // The concern is that, if we simply split every encroached
+ // segment in its center, two adjacent segments with a small
+ // angle between them might lead to an infinite loop; each
+ // vertex added to split one segment will encroach upon the
+ // other segment, which must then be split with a vertex that
+ // will encroach upon the first segment, and so on forever.
+ // To avoid this, imagine a set of concentric circles, whose
+ // radii are powers of two, about each segment endpoint.
+ // These concentric circles determine where the segment is
+ // split. (If both endpoints are shared with adjacent
+ // segments, split the segment in the middle, and apply the
+ // concentric circles for later splittings.)
+
+ // Is the origin shared with another segment?
+ currentenc.TriPivot(ref enctri);
+ enctri.Lnext(ref testtri);
+ testtri.SegPivot(ref testsh);
+ acuteorg = testsh.ss != Mesh.dummysub;
+ // Is the destination shared with another segment?
+ testtri.LnextSelf();
+ testtri.SegPivot(ref testsh);
+ acutedest = testsh.ss != Mesh.dummysub;
+
+ // If we're using Chew's algorithm (rather than Ruppert's)
+ // to define encroachment, delete free vertices from the
+ // subsegment's diametral circle.
+ if (!Behavior.ConformDel && !acuteorg && !acutedest)
+ {
+ eapex = enctri.Apex();
+ while ((eapex.type == VertexType.FreeVertex) &&
+ ((eorg.pt.X - eapex.pt.X) * (edest.pt.X - eapex.pt.X) +
+ (eorg.pt.Y - eapex.pt.Y) * (edest.pt.Y - eapex.pt.Y) < 0.0))
+ {
+ mesh.DeleteVertex(ref testtri);
+ currentenc.TriPivot(ref enctri);
+ eapex = enctri.Apex();
+ enctri.Lprev(ref testtri);
+ }
+ }
+
+ // Now, check the other side of the segment, if there's a triangle there.
+ enctri.Sym(ref testtri);
+ if (testtri.triangle != Mesh.dummytri)
+ {
+ // Is the destination shared with another segment?
+ testtri.LnextSelf();
+ testtri.SegPivot(ref testsh);
+ acutedest2 = testsh.ss != Mesh.dummysub;
+ acutedest = acutedest || acutedest2;
+ // Is the origin shared with another segment?
+ testtri.LnextSelf();
+ testtri.SegPivot(ref testsh);
+ acuteorg2 = testsh.ss != Mesh.dummysub;
+ acuteorg = acuteorg || acuteorg2;
+
+ // Delete free vertices from the subsegment's diametral circle.
+ if (!Behavior.ConformDel && !acuteorg2 && !acutedest2)
+ {
+ eapex = testtri.Org();
+ while ((eapex.type == VertexType.FreeVertex) &&
+ ((eorg.pt.X - eapex.pt.X) * (edest.pt.X - eapex.pt.X) +
+ (eorg.pt.Y - eapex.pt.Y) * (edest.pt.Y - eapex.pt.Y) < 0.0))
+ {
+ mesh.DeleteVertex(ref testtri);
+ enctri.Sym(ref testtri);
+ eapex = testtri.Apex();
+ testtri.LprevSelf();
+ }
+ }
+ }
+
+ // Use the concentric circles if exactly one endpoint is shared
+ // with another adjacent segment.
+ if (acuteorg || acutedest)
+ {
+ segmentlength = Math.Sqrt((edest.pt.X - eorg.pt.X) * (edest.pt.X - eorg.pt.X) +
+ (edest.pt.Y - eorg.pt.Y) * (edest.pt.Y - eorg.pt.Y));
+ // Find the power of two that most evenly splits the segment.
+ // The worst case is a 2:1 ratio between subsegment lengths.
+ nearestpoweroftwo = 1.0;
+ while (segmentlength > 3.0 * nearestpoweroftwo)
+ {
+ nearestpoweroftwo *= 2.0;
+ }
+ while (segmentlength < 1.5 * nearestpoweroftwo)
+ {
+ nearestpoweroftwo *= 0.5;
+ }
+ // Where do we split the segment?
+ split = nearestpoweroftwo / segmentlength;
+ if (acutedest)
+ {
+ split = 1.0 - split;
+ }
+ }
+ else
+ {
+ // If we're not worried about adjacent segments, split
+ // this segment in the middle.
+ split = 0.5;
+ }
+
+ // Create the new vertex.
+ newvertex = new Vertex(mesh.nextras);
+ mesh.vertices.Add(newvertex.Hash, newvertex);
+
+ // Interpolate its coordinate and attributes.
+ for (int i = 0; i < mesh.nextras; i++)
+ {
+ newvertex.attributes[i] = eorg.attributes[i]
+ + split * (edest.attributes[i] - eorg.attributes[i]);
+ }
+
+ newvertex.pt.X = eorg.pt.X + split * (edest.pt.X - eorg.pt.X);
+ newvertex.pt.Y = eorg.pt.Y + split * (edest.pt.Y - eorg.pt.Y);
+
+ if (!Behavior.NoExact)
+ {
+ // Roundoff in the above calculation may yield a 'newvertex'
+ // that is not precisely collinear with 'eorg' and 'edest'.
+ // Improve collinearity by one step of iterative refinement.
+ multiplier = Primitives.CounterClockwise(eorg.pt, edest.pt, newvertex.pt);
+ divisor = ((eorg.pt.X - edest.pt.X) * (eorg.pt.X - edest.pt.X) +
+ (eorg.pt.Y - edest.pt.Y) * (eorg.pt.Y - edest.pt.Y));
+ if ((multiplier != 0.0) && (divisor != 0.0))
+ {
+ multiplier = multiplier / divisor;
+ // Watch out for NANs.
+ if (!double.IsNaN(multiplier))
+ {
+ newvertex.pt.X += multiplier * (edest.pt.Y - eorg.pt.Y);
+ newvertex.pt.Y += multiplier * (eorg.pt.X - edest.pt.X);
+ }
+ }
+ }
+
+ newvertex.mark = currentenc.Mark();
+ newvertex.type = VertexType.SegmentVertex;
+
+ // Check whether the new vertex lies on an endpoint.
+ if (((newvertex.pt.X == eorg.pt.X) && (newvertex.pt.Y == eorg.pt.Y)) ||
+ ((newvertex.pt.X == edest.pt.X) && (newvertex.pt.Y == edest.pt.Y)))
+ {
+
+ logger.Error("Ran out of precision: I attempted to split a"
+ + " segment to a smaller size than can be accommodated by"
+ + " the finite precision of floating point arithmetic.",
+ "Quality.SplitEncSegs()");
+
+ throw new Exception("Ran out of precision");
+ }
+ // Insert the splitting vertex. This should always succeed.
+ success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws);
+ if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching))
+ {
+ logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()");
+ throw new Exception("Failure to split a segment.");
+ }
+ if (mesh.steinerleft > 0)
+ {
+ mesh.steinerleft--;
+ }
+ // Check the two new subsegments to see if they're encroached.
+ dummy = CheckSeg4Encroach(ref currentenc);
+ currentenc.NextSelf();
+ dummy = CheckSeg4Encroach(ref currentenc);
+ }
+
+ // Set subsegment's origin to NULL. This makes it possible to detect dead
+ // badsubsegs when traversing the list of all badsubsegs.
+ seg.subsegorg = null;
+ }
+ }
+
+ ///
+ /// Test every triangle in the mesh for quality measures.
+ ///
+ void TallyFaces()
+ {
+ Otri triangleloop = default(Otri);
+
+ triangleloop.orient = 0;
+
+ foreach (var t in mesh.triangles.Values)
+ {
+ triangleloop.triangle = t;
+
+ // If the triangle is bad, enqueue it.
+ TestTriangle(ref triangleloop);
+ }
+ }
+
+ ///
+ /// Inserts a vertex at the circumcenter of a triangle. Deletes
+ /// the newly inserted vertex if it encroaches upon a segment.
+ ///
+ ///
+ void SplitTriangle(BadTriangle badtri)
+ {
+ Otri badotri = default(Otri);
+ Vertex borg, bdest, bapex;
+ Vertex newvertex;
+ double xi = 0, eta = 0;
+ InsertVertexResult success;
+ bool errorflag;
+ int i;
+
+ badotri = badtri.poortri;
+ borg = badotri.Org();
+ bdest = badotri.Dest();
+ bapex = badotri.Apex();
+
+ // Make sure that this triangle is still the same triangle it was
+ // when it was tested and determined to be of bad quality.
+ // Subsequent transformations may have made it a different triangle.
+ if (!Otri.IsDead(badotri.triangle) && (borg == badtri.triangorg) &&
+ (bdest == badtri.triangdest) && (bapex == badtri.triangapex))
+ {
+ errorflag = false;
+ // Create a new vertex at the triangle's circumcenter.
+ newvertex = new Vertex(mesh.nextras);
+
+ // Using the original (simpler) Steiner point location method
+ // for mesh refinement.
+ // TODO: NewLocation doesn't work for refinement. Why? Maybe
+ // reset VertexType?
+ if (Behavior.FixedArea || Behavior.VarArea)
+ {
+ newvertex.pt = Primitives.FindCircumcenter(borg.pt, bdest.pt, bapex.pt, ref xi, ref eta, true);
+ }
+ else
+ {
+ NewLocation.FindLocation(mesh, borg, bdest, bapex, newvertex, ref xi, ref eta, true, badotri);
+ }
+
+ // Check whether the new vertex lies on a triangle vertex.
+ if (((newvertex.pt.X == borg.pt.X) && (newvertex.pt.Y == borg.pt.Y)) ||
+ ((newvertex.pt.X == bdest.pt.X) && (newvertex.pt.Y == bdest.pt.Y)) ||
+ ((newvertex.pt.X == bapex.pt.X) && (newvertex.pt.Y == bapex.pt.Y)))
+ {
+ if (Behavior.Verbose)
+ {
+ logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
+ errorflag = true;
+ }
+ }
+ else
+ {
+ for (i = 0; i < mesh.nextras; i++)
+ {
+ // Interpolate the vertex attributes at the circumcenter.
+ newvertex.attributes[i] = borg.attributes[i]
+ + xi * (bdest.attributes[i] - borg.attributes[i])
+ + eta * (bapex.attributes[i] - borg.attributes[i]);
+ }
+ // The new vertex must be in the interior, and therefore is a
+ // free vertex with a marker of zero.
+ newvertex.mark = 0;
+ newvertex.type = VertexType.FreeVertex;
+
+ // Ensure that the handle 'badotri' does not represent the longest
+ // edge of the triangle. This ensures that the circumcenter must
+ // fall to the left of this edge, so point location will work.
+ // (If the angle org-apex-dest exceeds 90 degrees, then the
+ // circumcenter lies outside the org-dest edge, and eta is
+ // negative. Roundoff error might prevent eta from being
+ // negative when it should be, so I test eta against xi.)
+ if (eta < xi)
+ {
+ badotri.LprevSelf();
+ }
+
+ // Insert the circumcenter, searching from the edge of the triangle,
+ // and maintain the Delaunay property of the triangulation.
+ Osub tmp = default(Osub);
+ success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true);
+
+ if (success == InsertVertexResult.Successful)
+ {
+ mesh.vertices.Add(newvertex.Hash, newvertex);
+
+ if (mesh.steinerleft > 0)
+ {
+ mesh.steinerleft--;
+ }
+ }
+ else if (success == InsertVertexResult.Encroaching)
+ {
+ // If the newly inserted vertex encroaches upon a subsegment,
+ // delete the new vertex.
+ mesh.UndoVertex();
+ }
+ else if (success == InsertVertexResult.Violating)
+ {
+ // Failed to insert the new vertex, but some subsegment was
+ // marked as being encroached.
+ }
+ else
+ { // success == DUPLICATEVERTEX
+ // Couldn't insert the new vertex because a vertex is already there.
+ if (Behavior.Verbose)
+ {
+ logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()");
+ errorflag = true;
+ }
+ }
+ }
+ if (errorflag)
+ {
+ logger.Error("The new vertex is at the circumcenter of triangle: This probably "
+ + "means that I am trying to refine triangles to a smaller size than can be "
+ + "accommodated by the finite precision of floating point arithmetic.",
+ "Quality.SplitTriangle()");
+
+ throw new Exception("The new vertex is at the circumcenter of triangle.");
+ }
+ }
+ }
+
+ ///
+ /// Remove all the encroached subsegments and bad triangles from the triangulation.
+ ///
+ public void EnforceQuality()
+ {
+ BadTriangle badtri;
+
+ // Test all segments to see if they're encroached.
+ TallyEncs();
+
+ // Fix encroached subsegments without noting bad triangles.
+ SplitEncSegs(false);
+ // At this point, if we haven't run out of Steiner points, the
+ // triangulation should be (conforming) Delaunay.
+
+ // Next, we worry about enforcing triangle quality.
+ if ((Behavior.MinAngle > 0.0) || Behavior.VarArea || Behavior.FixedArea || Behavior.Usertest)
+ {
+ // TODO: Reset queue? (Or is it always empty at this point)
+
+ // Test all triangles to see if they're bad.
+ TallyFaces();
+
+ mesh.checkquality = true;
+ while ((queue.badtriangles.Count > 0) && (mesh.steinerleft != 0))
+ {
+ // Fix one bad triangle by inserting a vertex at its circumcenter.
+ badtri = queue.Dequeue();
+ SplitTriangle(badtri);
+
+ if (badsubsegs.Count > 0)
+ {
+ // Put bad triangle back in queue for another try later.
+ queue.Enqueue(badtri);
+ // Fix any encroached subsegments that resulted.
+ // Record any new bad triangles that result.
+ SplitEncSegs(true);
+ }
+ else
+ {
+ // Return the bad triangle to the pool.
+ queue.badtriangles.Remove(badtri);
+ }
+ }
+ }
+
+ // At this point, if the "-D" switch was selected and we haven't run out
+ // of Steiner points, the triangulation should be (conforming) Delaunay
+ // and have no low-quality triangles.
+
+ // Might we have run out of Steiner points too soon?
+ if (Behavior.Verbose && Behavior.ConformDel && (badsubsegs.Count > 0) && (mesh.steinerleft == 0))
+ {
+
+ logger.Warning("I ran out of Steiner points, but the mesh has encroached subsegments, "
+ + "and therefore might not be truly Delaunay. If the Delaunay property is important "
+ + "to you, try increasing the number of Steiner points",
+ "Quality.EnforceQuality()");
+ }
+ }
+ #endregion
+ }
+}
diff --git a/Triangle.NET/Triangle/Sampler.cs b/Triangle.NET/Triangle/Sampler.cs
new file mode 100644
index 0000000..5b12004
--- /dev/null
+++ b/Triangle.NET/Triangle/Sampler.cs
@@ -0,0 +1,80 @@
+// -----------------------------------------------------------------------
+//
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+
+ ///
+ /// Used for triangle sampling in the Mesh.Locate method.
+ ///
+ class Sampler
+ {
+ static Random rand = new Random(DateTime.Now.Millisecond);
+
+ // Number of random samples for point location (at least 1).
+ int samples = 1;
+
+ // Number of triangles in mesh.
+ int triangleCount = 0;
+
+ // Empirically chosen factor.
+ static int samplefactor = 11;
+
+ // Keys of the triangle dictionary.
+ int[] keys;
+
+ ///
+ /// Update sampling parameters if mesh changed.
+ ///
+ /// Current mesh.
+ public void Update(Mesh mesh)
+ {
+ int count = mesh.triangles.Count;
+
+ // TODO: Is checking the triangle count a good way to monitor mesh changes?
+ if (triangleCount != count)
+ {
+ triangleCount = count;
+
+ // The number of random samples taken is proportional to the cube root of
+ // the number of triangles in the mesh. The next bit of code assumes
+ // that the number of triangles increases monotonically (or at least
+ // doesn't decrease enough to matter).
+ while (samplefactor * samples * samples * samples < count)
+ {
+ samples++;
+ }
+
+ // TODO: Is there a way not calling ToArray()?
+ keys = mesh.triangles.Keys.ToArray();
+ }
+ }
+
+ ///
+ /// Get a random sample set of triangle keys.
+ ///
+ /// Array of triangle keys.
+ public int[] GetSamples()
+ {
+ int[] randSamples = new int[samples];
+
+ int range = triangleCount / samples;
+
+ for (int i = 0; i < samples; i++)
+ {
+ // Yeah, rand should be equally distributed, but just to make
+ // sure, use a range variable...
+ randSamples[i] = keys[rand.Next(i * range, (i + 1) * range - 1)];
+ }
+
+ return randSamples;
+ }
+ }
+}
diff --git a/Triangle.NET/Triangle/Statistic.cs b/Triangle.NET/Triangle/Statistic.cs
new file mode 100644
index 0000000..f98bb60
--- /dev/null
+++ b/Triangle.NET/Triangle/Statistic.cs
@@ -0,0 +1,641 @@
+// -----------------------------------------------------------------------
+//
+// Original Triangle code by Jonathan Richard Shewchuk, http://www.cs.cmu.edu/~quake/triangle.html
+// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
+//
+// -----------------------------------------------------------------------
+
+namespace TriangleNet
+{
+ using System;
+ using System.Text;
+ using TriangleNet.Data;
+
+ ///
+ /// Gather mesh statistics.
+ ///
+ public class Statistic
+ {
+ #region Static members
+
+ ///
+ /// Number of incircle tests performed.
+ ///
+ public static long InCircleCount = 0;
+
+ ///
+ /// Number of counterclockwise tests performed.
+ ///
+ public static long CounterClockwiseCount = 0;
+
+ ///
+ /// Number of 3D orientation tests performed.
+ ///
+ public static long Orient3dCount = 0;
+
+ ///
+ /// Number of right-of-hyperbola tests performed.
+ ///
+ public static long HyperbolaCount = 0;
+
+ ///
+ /// // Number of circumcenter calculations performed.
+ ///
+ public static long CircumcenterCount = 0;
+
+ ///
+ /// Number of circle top calculations performed.
+ ///
+ public static long CircleTopCount = 0;
+
+ ///
+ /// Number of vertex relocation.
+ ///
+ public static long RelocationCount = 0;
+
+ #endregion
+
+ #region Properties
+
+ double minEdge = 0;
+ ///
+ /// Shortest edge
+ ///
+ public double ShortestEdge { get { return minEdge; } }
+
+ double maxEdge = 0;
+ ///
+ /// Longest edge
+ ///
+ public double LongestEdge { get { return maxEdge; } }
+
+ //
+ double minAspect = 0;
+ ///
+ /// Shortest altitude
+ ///
+ public double ShortestAltitude { get { return minAspect; } }
+
+ double maxAspect = 0;
+ ///
+ /// Largest aspect ratio
+ ///
+ public double LargestAspectRatio { get { return maxAspect; } }
+
+ double minArea = 0;
+ ///
+ /// Smallest area
+ ///
+ public double SmallestArea { get { return minArea; } }
+
+ double maxArea = 0;
+ ///
+ /// Largest area
+ ///
+ public double LargestArea { get { return maxArea; } }
+
+ double minAngle = 0;
+ ///
+ /// Smallest angle
+ ///
+ public double SmallestAngle { get { return minAngle; } }
+
+ double maxAngle = 0;
+ ///
+ /// Largest angle
+ ///
+ public double LargestAngle { get { return maxAngle; } }
+
+ int inVetrices = 0;
+ ///
+ /// Input vertices
+ ///
+ public int InputVertices { get { return inVetrices; } }
+
+ int inTriangles = 0;
+ ///
+ /// Input triangles
+ ///
+ public int InputTriangles { get { return inTriangles; } }
+
+ int inSegments = 0;
+ ///
+ /// Input segments
+ ///
+ public int InputSegments { get { return inSegments; } }
+
+ int inHoles = 0;
+ ///
+ /// Input holes
+ ///
+ public int InputHoles { get { return inHoles; } }
+
+ int outVertices = 0;
+ ///
+ /// Mesh vertices
+ ///
+ public int Vertices { get { return outVertices; } }
+
+ int outTriangles = 0;
+ ///
+ /// Mesh triangles
+ ///
+ public int Triangles { get { return outTriangles; } }
+
+ int outEdges = 0;
+ ///
+ /// Mesh edges
+ ///
+ public int Edges { get { return outEdges; } }
+
+ int boundaryEdges = 0;
+ ///
+ /// Exterior boundary edges
+ ///
+ public int BoundaryEdges { get { return boundaryEdges; } }
+
+ int intBoundaryEdges = 0;
+ ///
+ /// Interior boundary edges
+ ///
+ public int InteriorBoundaryEdges { get { return intBoundaryEdges; } }
+
+ int constrainedEdges = 0;
+ ///
+ /// Constrained edges
+ ///
+ public int ConstrainedEdges { get { return constrainedEdges; } }
+
+ int[] angleTable;
+ ///
+ /// Angle histogram
+ ///
+ public int[] AngleHistogram { get { return angleTable; } }
+
+ #endregion
+
+ ///
+ /// detailedHistogram()
+ ///
+ ///
+ void detailedHistogram(Mesh m)
+ {
+ Vertex[] p = new Vertex[3];
+ double[] cosSquareTable = new double[8];
+ double[] dx = new double[3], dy = new double[3];
+ double[] edgelength = new double[3];
+ double dotproduct;
+ double cosSquare;
+
+ int i, ii, j, k;
+ double[] cossquaretableHist = new double[89];
+ double radconstHist;
+ int onedegree;
+ int[] angletableHist = new int[180];
+
+ radconstHist = Math.PI / 180.0;
+ for (i = 0; i < 89; i++)
+ {
+ cossquaretableHist[i] = Math.Cos(radconstHist * (i + 1));
+ cossquaretableHist[i] = cossquaretableHist[i] * cossquaretableHist[i];
+ }
+ for (i = 0; i < 180; i++)
+ {
+ angletableHist[i] = 0;
+ }
+
+ foreach (var tri in m.triangles.Values)
+ {
+ p[0] = tri.vertices[0];
+ p[1] = tri.vertices[1];
+ p[2] = tri.vertices[2];
+
+ for (i = 0; i < 3; i++)
+ {
+ j = plus1Mod3[i];
+ k = minus1Mod3[i];
+ dx[i] = p[j][0] - p[k][0];
+ dy[i] = p[j][1] - p[k][1];
+ edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i];
+ }
+ for (i = 0; i < 3; i++)
+ {
+ j = plus1Mod3[i];
+ k = minus1Mod3[i];
+ dotproduct = dx[j] * dx[k] + dy[j] * dy[k];
+ cosSquare = dotproduct * dotproduct / (edgelength[j] * edgelength[k]);
+ onedegree = 89;
+ for (ii = 88; ii >= 0; ii--)
+ {
+ if (cosSquare > cossquaretableHist[ii])
+ {
+ onedegree = ii;
+ }
+ }
+ if (dotproduct <= 0.0)
+ {
+ angletableHist[onedegree]++;
+ }
+ else
+ {
+ angletableHist[179 - onedegree]++;
+ }
+ }
+ }
+ }
+
+ static readonly int[] plus1Mod3 = { 1, 2, 0 };
+ static readonly int[] minus1Mod3 = { 2, 0, 1 };
+
+ ///
+ /// Update statistics about the quality of the mesh.
+ ///
+ ///
+ public void Update(Mesh mesh)
+ {
+ inVetrices = mesh.invertices;
+ inTriangles = mesh.inelements;
+ inSegments = mesh.insegments;
+ inHoles = mesh.holes.Count;
+ outVertices = mesh.vertices.Count - mesh.undeads;
+ outTriangles = mesh.triangles.Count;
+ outEdges = (int)mesh.edges;
+ boundaryEdges = (int)mesh.hullsize;
+ intBoundaryEdges = mesh.subsegs.Count - (int)mesh.hullsize;
+ constrainedEdges = mesh.subsegs.Count;
+
+ Point2[] p = new Point2[3];
+
+ int k1, k2;
+ int tendegree;
+
+ double[] cosSquareTable = new double[8];
+ double[] dx = new double[3];
+ double[] dy = new double[3];
+ double[] edgelength = new double[3];
+ double dotproduct;
+ double cossquare;
+ double triarea;
+ double trilongest2;
+ double triminaltitude2;
+ double triaspect2;
+
+ double radconst = Math.PI / 18.0;
+ double degconst = 180.0 / Math.PI;
+
+ // New angle table
+ angleTable = new int[18];
+
+ for (int i = 0; i < 8; i++)
+ {
+ cosSquareTable[i] = Math.Cos(radconst * (i + 1));
+ cosSquareTable[i] = cosSquareTable[i] * cosSquareTable[i];
+ }
+ for (int i = 0; i < 18; i++)
+ {
+ angleTable[i] = 0;
+ }
+
+ minAspect = mesh.xmax - mesh.xmin + mesh.ymax - mesh.ymin;
+ minAspect = minAspect * minAspect;
+ maxAspect = 0.0;
+ minEdge = minAspect;
+ maxEdge = 0.0;
+ minArea = minAspect;
+ maxArea = 0.0;
+ minAngle = 0.0;
+ maxAngle = 2.0;
+
+ bool acuteBiggest = true;
+
+ foreach (var tri in mesh.triangles.Values)
+ {
+ p[0] = tri.vertices[0].pt;
+ p[1] = tri.vertices[1].pt;
+ p[2] = tri.vertices[2].pt;
+
+ trilongest2 = 0.0;
+
+ for (int i = 0; i < 3; i++)
+ {
+ k1 = plus1Mod3[i];
+ k2 = minus1Mod3[i];
+
+ dx[i] = p[k1].X - p[k2].X;
+ dy[i] = p[k1].Y - p[k2].Y;
+
+ edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i];
+
+ if (edgelength[i] > trilongest2)
+ {
+ trilongest2 = edgelength[i];
+ }
+
+ if (edgelength[i] > maxEdge)
+ {
+ maxEdge = edgelength[i];
+ }
+
+ if (edgelength[i] < minEdge)
+ {
+ minEdge = edgelength[i];
+ }
+ }
+
+ //triarea = Primitives.CounterClockwise(p[0], p[1], p[2]);
+ triarea = Math.Abs((p[2].X - p[0].X) * (p[1].Y - p[0].Y) -
+ (p[1].X - p[0].X) * (p[2].Y - p[0].Y));
+ if (triarea < minArea)
+ {
+ minArea = triarea;
+ }
+ if (triarea > maxArea)
+ {
+ maxArea = triarea;
+ }
+ triminaltitude2 = triarea * triarea / trilongest2;
+ if (triminaltitude2 < minAspect)
+ {
+ minAspect = triminaltitude2;
+ }
+ triaspect2 = trilongest2 / triminaltitude2;
+ if (triaspect2 > maxAspect)
+ {
+ maxAspect = triaspect2;
+ }
+
+ for (int i = 0; i < 3; i++)
+ {
+ k1 = plus1Mod3[i];
+ k2 = minus1Mod3[i];
+
+ dotproduct = dx[k1] * dx[k2] + dy[k1] * dy[k2];
+ cossquare = dotproduct * dotproduct / (edgelength[k1] * edgelength[k2]);
+ tendegree = 8;
+
+ for (int j = 7; j >= 0; j--)
+ {
+ if (cossquare > cosSquareTable[j])
+ {
+ tendegree = j;
+ }
+ }
+ if (dotproduct <= 0.0)
+ {
+ angleTable[tendegree]++;
+ if (cossquare > minAngle)
+ {
+ minAngle = cossquare;
+ }
+ if (acuteBiggest && (cossquare < maxAngle))
+ {
+ maxAngle = cossquare;
+ }
+ }
+ else
+ {
+ angleTable[17 - tendegree]++;
+ if (acuteBiggest || (cossquare > maxAngle))
+ {
+ maxAngle = cossquare;
+ acuteBiggest = false;
+ }
+ }
+ }
+ }
+
+ minEdge = Math.Sqrt(minEdge);
+ maxEdge = Math.Sqrt(maxEdge);
+ minAspect = Math.Sqrt(minAspect);
+ maxAspect = Math.Sqrt(maxAspect);
+ minArea *= 0.5;
+ maxArea *= 0.5;
+ if (minAngle >= 1.0)
+ {
+ minAngle = 0.0;
+ }
+ else
+ {
+ minAngle = degconst * Math.Acos(Math.Sqrt(minAngle));
+ }
+ if (maxAngle >= 1.0)
+ {
+ maxAngle = 180.0;
+ }
+ else
+ {
+ if (acuteBiggest)
+ {
+ maxAngle = degconst * Math.Acos(Math.Sqrt(maxAngle));
+ }
+ else
+ {
+ maxAngle = 180.0 - degconst * Math.Acos(Math.Sqrt(maxAngle));
+ }
+ }
+ }
+
+ /* Original code with aspect ratio
+
+ int[] aspecttable;
+ double[] ratiotable;
+
+ public Statistic()
+ {
+ angletable = new int[18];
+ aspecttable = new int[16];
+ ratiotable = new double[] {
+ 1.5, 2.0, 2.5, 3.0, 4.0, 6.0, 10.0, 15.0, 25.0, 50.0,
+ 100.0, 300.0, 1000.0, 10000.0, 100000.0, 0.0 };
+ }
+
+ public void Update(Mesh mesh)
+ {
+ inVetrices = mesh.invertices;
+ inTriangles = mesh.inelements;
+ inSegments = mesh.insegments;
+ inHoles = mesh.holes;
+ outVertices = mesh.vertices.Count - mesh.undeads;
+ outTriangles = mesh.triangles.Count;
+ outEdges = (int)mesh.edges;
+ boundaryEdges = (int)mesh.hullsize;
+ intBoundaryEdges = mesh.subsegs.Count - (int)mesh.hullsize;
+ constrainedEdges = mesh.subsegs.Count;
+
+ Otri triangleloop = default(Otri);
+ Vertex[] p = new Vertex[3];
+ double[] cossquaretable = new double[8];
+ double[] dx = new double[3], dy = new double[3];
+ double[] edgelength = new double[3];
+ double dotproduct;
+ double cossquare;
+ double triarea;
+ double trilongest2;
+ double triminaltitude2;
+ double triaspect2;
+ double radconst, degconst;
+
+ int aspectindex;
+ int tendegree;
+ bool acutebiggest;
+ int i, ii, j, k;
+
+ radconst = Math.PI / 18.0;
+ degconst = 180.0 / Math.PI;
+ for (i = 0; i < 8; i++)
+ {
+ cossquaretable[i] = Math.Cos(radconst * (double)(i + 1));
+ cossquaretable[i] = cossquaretable[i] * cossquaretable[i];
+ }
+ for (i = 0; i < 18; i++)
+ {
+ angletable[i] = 0;
+ }
+
+ for (i = 0; i < 16; i++)
+ {
+ aspecttable[i] = 0;
+ }
+
+ worstaspect = 0.0;
+ minaltitude = mesh.xmax - mesh.xmin + mesh.ymax - mesh.ymin;
+ minaltitude = minaltitude * minaltitude;
+ shortest = minaltitude;
+ longest = 0.0;
+ smallestarea = minaltitude;
+ biggestarea = 0.0;
+ worstaspect = 0.0;
+ smallestangle = 0.0;
+ biggestangle = 2.0;
+ acutebiggest = true;
+
+ triangleloop.orient = 0;
+ foreach (var t in mesh.triangles)
+ {
+ triangleloop.triangle = t;
+ p[0] = triangleloop.Org();
+ p[1] = triangleloop.Dest();
+ p[2] = triangleloop.Apex();
+ trilongest2 = 0.0;
+
+ for (i = 0; i < 3; i++)
+ {
+ j = plus1Mod3[i];
+ k = minus1Mod3[i];
+ dx[i] = p[j].pt.X - p[k].pt.X;
+ dy[i] = p[j].pt.Y - p[k].pt.Y;
+ edgelength[i] = dx[i] * dx[i] + dy[i] * dy[i];
+ if (edgelength[i] > trilongest2)
+ {
+ trilongest2 = edgelength[i];
+ }
+ if (edgelength[i] > longest)
+ {
+ longest = edgelength[i];
+ }
+ if (edgelength[i] < shortest)
+ {
+ shortest = edgelength[i];
+ }
+ }
+
+ //triarea = Primitives.CounterClockwise(p[0], p[1], p[2]);
+ triarea = Math.Abs((p[2].pt.X - p[0].pt.X) * (p[1].pt.Y - p[0].pt.Y) -
+ (p[1].pt.X - p[0].pt.X) * (p[2].pt.Y - p[0].pt.Y)) / 2.0;
+ if (triarea < smallestarea)
+ {
+ smallestarea = triarea;
+ }
+ if (triarea > biggestarea)
+ {
+ biggestarea = triarea;
+ }
+ triminaltitude2 = triarea * triarea / trilongest2;
+ if (triminaltitude2 < minaltitude)
+ {
+ minaltitude = triminaltitude2;
+ }
+ triaspect2 = trilongest2 / triminaltitude2;
+ if (triaspect2 > worstaspect)
+ {
+ worstaspect = triaspect2;
+ }
+ aspectindex = 0;
+ while ((triaspect2 > ratiotable[aspectindex] * ratiotable[aspectindex]) && (aspectindex < 15))
+ {
+ aspectindex++;
+ }
+ aspecttable[aspectindex]++;
+
+ for (i = 0; i < 3; i++)
+ {
+ j = plus1Mod3[i];
+ k = minus1Mod3[i];
+ dotproduct = dx[j] * dx[k] + dy[j] * dy[k];
+ cossquare = dotproduct * dotproduct / (edgelength[j] * edgelength[k]);
+ tendegree = 8;
+ for (ii = 7; ii >= 0; ii--)
+ {
+ if (cossquare > cossquaretable[ii])
+ {
+ tendegree = ii;
+ }
+ }
+ if (dotproduct <= 0.0)
+ {
+ angletable[tendegree]++;
+ if (cossquare > smallestangle)
+ {
+ smallestangle = cossquare;
+ }
+ if (acutebiggest && (cossquare < biggestangle))
+ {
+ biggestangle = cossquare;
+ }
+ }
+ else
+ {
+ angletable[17 - tendegree]++;
+ if (acutebiggest || (cossquare > biggestangle))
+ {
+ biggestangle = cossquare;
+ acutebiggest = false;
+ }
+ }
+ }
+ }
+
+ shortest = Math.Sqrt(shortest);
+ longest = Math.Sqrt(longest);
+ minaltitude = Math.Sqrt(minaltitude);
+ worstaspect = Math.Sqrt(worstaspect);
+ smallestarea *= 0.5;
+ biggestarea *= 0.5;
+ if (smallestangle >= 1.0)
+ {
+ smallestangle = 0.0;
+ }
+ else
+ {
+ smallestangle = degconst * Math.Acos(Math.Sqrt(smallestangle));
+ }
+ if (biggestangle >= 1.0)
+ {
+ biggestangle = 180.0;
+ }
+ else
+ {
+ if (acutebiggest)
+ {
+ biggestangle = degconst * Math.Acos(Math.Sqrt(biggestangle));
+ }
+ else
+ {
+ biggestangle = 180.0 - degconst * Math.Acos(Math.Sqrt(biggestangle));
+ }
+ }
+ }
+
+ */
+ }
+}
diff --git a/Triangle.NET/Triangle/Triangle.csproj b/Triangle.NET/Triangle/Triangle.csproj
new file mode 100644
index 0000000..0ef6e1c
--- /dev/null
+++ b/Triangle.NET/Triangle/Triangle.csproj
@@ -0,0 +1,89 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}
+ Library
+ Properties
+ TriangleNet
+ Triangle
+ v4.0
+ 512
+ Client
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file