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