Initial check in.

git-svn-id: https://triangle.svn.codeplex.com/svn@66283 0e2699bc-83d4-4a8f-98e7-55e24ab8c7a5
This commit is contained in:
SND\wo80_cp
2012-03-21 18:19:44 +00:00
parent 8f190fa4fe
commit f31ec1ad02
59 changed files with 19436 additions and 0 deletions
+185
View File
@@ -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
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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);
}
}
}
@@ -0,0 +1,206 @@
// -----------------------------------------------------------------------
// <copyright file="CheckBoxDark.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------
namespace TestApp.Controls
{
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
/// <summary>
/// TODO: Update summary.
/// </summary>
public class CheckBoxDark : ButtonBase
{
#region Designer
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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);
}
}
}
+154
View File
@@ -0,0 +1,154 @@
// -----------------------------------------------------------------------
// <copyright file="CheckBoxDark.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------
namespace TestApp.Controls
{
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
/// <summary>
/// TODO: Update summary.
/// </summary>
public class Histogram : Control
{
#region Designer
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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);
}
}
}
}
@@ -0,0 +1,304 @@
// -----------------------------------------------------------------------
// <copyright file="MeshRenderer.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------
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;
/// <summary>
/// Renders a mesh using GDI.
/// </summary>
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
}
}
@@ -0,0 +1,195 @@
// -----------------------------------------------------------------------
// <copyright file="CheckBoxDark.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------
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
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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
}
}
+120
View File
@@ -0,0 +1,120 @@
// -----------------------------------------------------------------------
// <copyright file="Examples.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------
namespace TestApp
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TriangleNet;
using TriangleNet.IO;
/// <summary>
/// Code of the online examples.
///
/// </summary>
public static class Examples
{
// Make sure this path points to the polygon sample data.
static readonly string pathToData = @"..\..\..\Data\";
/// <summary>
/// Generating Delaunay triangulations
/// </summary>
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);
}
/// <summary>
/// Quality meshing: angle and size constraints
/// </summary>
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);
}
/// <summary>
/// Refining preexisting meshes
/// </summary>
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);
}
}
}
+176
View File
@@ -0,0 +1,176 @@
namespace TestApp
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}
+281
View File
@@ -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<double[]> points = new List<double[]>();
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();
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+489
View File
@@ -0,0 +1,489 @@
namespace TestApp
{
partial class Form2
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}
+82
View File
@@ -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();
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+297
View File
@@ -0,0 +1,297 @@
// -----------------------------------------------------------------------
// <copyright file="ImageWriter.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------
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;
/// <summary>
/// Writes an image of the mesh to disk.
/// </summary>
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);
/// <summary>
/// Sets the color scheme.
/// </summary>
/// <param name="background">Background color.</param>
/// <param name="points">Points color.</param>
/// <param name="steiner">Steiner points color.</param>
/// <param name="lines">Line color.</param>
/// <param name="segments">Segment color.</param>
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;
}
/// <summary>
/// Set a color scheme with white background.
/// </summary>
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);
}
/// <summary>
/// Set a color scheme with black background.
/// </summary>
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);
}
/// <summary>
/// Draws the mesh and writes the image file.
/// </summary>
/// <param name="mesh">The mesh to visualize.</param>
public static void WritePng(Mesh mesh)
{
WritePng(mesh, "", 1000);
}
/// <summary>
/// Draws the mesh and writes the image file.
/// </summary>
/// <param name="mesh">The mesh to visualize.</param>
/// <param name="filename">The filename (only PNG supported).</param>
public static void WritePng(Mesh mesh, string filename)
{
WritePng(mesh, filename, 1000);
}
/// <summary>
/// Draws the mesh and writes the image file.
/// </summary>
/// <param name="mesh">The mesh to visualize.</param>
/// <param name="width">The target width of the image (pixel).</param>
public static void WritePng(Mesh mesh, int width)
{
WritePng(mesh, "", width);
}
/// <summary>
/// Draws the mesh and writes the image file.
/// </summary>
/// <param name="mesh">The mesh to visualize.</param>
/// <param name="filename">The filename (only PNG supported).</param>
/// <param name="width">The target width of the image (pixel).</param>
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);
}
/// <summary>
/// Draw mesh to the graphics object.
/// </summary>
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();
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace TestApp
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
@@ -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")]
+63
View File
@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace TestApp.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// 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() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[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;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+26
View File
@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
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;
}
}
}
}
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>
@@ -0,0 +1,122 @@
// -----------------------------------------------------------------------
// <copyright file="MeshDataInternal.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------
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];
}
}
}
}
}
+168
View File
@@ -0,0 +1,168 @@
// -----------------------------------------------------------------------
// <copyright file="Zoom.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------
namespace TestApp.Rendering
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
/// <summary>
/// Manages the current world to screen transformation
/// </summary>
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;
}
/// <summary>
/// Zoom in or out of the viewport.
/// </summary>
/// <param name="amount">Zoom amount</param>
/// <param name="focusX">Relative x point position</param>
/// <param name="focusY">Relative y point position</param>
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;
}
}
}
+122
View File
@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{336AAF8A-5316-4303-9E73-5E38BD0B28AF}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TestApp</RootNamespace>
<AssemblyName>TestApp</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
<SccProjectName>SAK</SccProjectName>
<SccLocalPath>SAK</SccLocalPath>
<SccAuxPath>SAK</SccAuxPath>
<SccProvider>SAK</SccProvider>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Controls\ButtonDark.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\CheckBoxDark.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\Histogram.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Controls\TextBoxDark.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Examples.cs" />
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Controls\MeshRenderer.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Form2.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form2.Designer.cs">
<DependentUpon>Form2.cs</DependentUpon>
</Compile>
<Compile Include="ImageWriter.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rendering\MeshDataInternal.cs" />
<Compile Include="Rendering\Zoom.cs" />
<EmbeddedResource Include="Form1.resx">
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Form2.resx">
<DependentUpon>Form2.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Triangle\Triangle.csproj">
<Project>{F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}</Project>
<Name>Triangle</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
+54
View File
@@ -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
+885
View File
@@ -0,0 +1,885 @@
// -----------------------------------------------------------------------
// <copyright file="DivConquer.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Algorithm
{
using System;
using TriangleNet.Data;
using TriangleNet.Log;
/// <summary>
/// Builds a delaunay triangulation using the divide-and-conquer algorithm.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
class Dwyer
{
static Random rand = new Random(DateTime.Now.Millisecond);
bool useDwyer = true;
/// <summary>
/// Sort an array of vertices by x-coordinate, using the y-coordinate as a secondary key.
/// </summary>
/// <param name="sortarray"></param>
/// <param name="left"></param>
/// <param name="right"></param>
/// <remarks>
/// Uses quicksort. Randomized O(n log n) time. No, I did not make any of
/// the usual quicksort mistakes.
/// </remarks>
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);
}
}
/// <summary>
/// An order statistic algorithm, almost. Shuffles an array of vertices so that
/// the first 'median' vertices occur lexicographically before the remaining vertices.
/// </summary>
/// <param name="sortarray"></param>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="median"></param>
/// <param name="axis"></param>
/// <remarks>
/// 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.
/// </remarks>
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);
}
}
/// <summary>
/// Sorts the vertices as appropriate for the divide-and-conquer algorithm with
/// alternating cuts.
/// </summary>
/// <param name="sortarray"></param>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="axis"></param>
/// <remarks>
/// 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.
/// </remarks>
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);
}
}
/// <summary>
/// Merge two adjacent Delaunay triangulations into a single Delaunay triangulation.
/// </summary>
/// <param name="farleft">Bounding triangles of the left triangulation.</param>
/// <param name="innerleft">Bounding triangles of the left triangulation.</param>
/// <param name="innerright">Bounding triangles of the right triangulation.</param>
/// <param name="farright">Bounding triangles of the right triangulation.</param>
/// <param name="axis"></param>
/// <remarks>
/// 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.
/// </remarks>
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();
}
}
}
/// <summary>
/// Recursively form a Delaunay triangulation by the divide-and-conquer method.
/// </summary>
/// <param name="sortarray"></param>
/// <param name="left"></param>
/// <param name="right"></param>
/// <param name="axis"></param>
/// <param name="farleft"></param>
/// <param name="farright"></param>
/// <remarks>
/// 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).
/// </remarks>
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);
}
}
/// <summary>
/// Removes ghost triangles.
/// </summary>
/// <param name="startghost"></param>
/// <returns>Number of vertices on the hull.</returns>
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;
}
/// <summary>
/// Form a Delaunay triangulation by the divide-and-conquer method.
/// </summary>
/// <returns></returns>
/// <remarks>
/// Sorts the vertices, calls a recursive procedure to triangulate them, and
/// removes the bounding box, setting boundary markers as appropriate.
/// </remarks>
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);
}
}
}
@@ -0,0 +1,21 @@
// -----------------------------------------------------------------------
// <copyright file="ITriangulator.cs" company="">
// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Algorithm
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// TODO: Update summary.
/// </summary>
public interface ITriangulator
{
int Triangulate(Mesh mesh);
}
}
@@ -0,0 +1,184 @@
// -----------------------------------------------------------------------
// <copyright file="Incremental.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Algorithm
{
using TriangleNet.Data;
using TriangleNet.Log;
/// <summary>
/// Builds a delaunay triangulation using the incremental algorithm.
/// </summary>
class Incremental
{
Mesh mesh;
/// <summary>
/// Form an "infinite" bounding triangle to insert vertices into.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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;
}
/// <summary>
/// Remove the "infinite" bounding triangle, setting boundary markers as appropriate.
/// </summary>
/// <returns>Returns the number of edges on the convex hull of the triangulation.</returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
/// <summary>
/// Form a Delaunay triangulation by incrementally inserting vertices.
/// </summary>
/// <returns>Returns the number of edges on the convex hull of the
/// triangulation.</returns>
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();
}
}
}
@@ -0,0 +1,752 @@
// -----------------------------------------------------------------------
// <copyright file="SweepLine.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Algorithm
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TriangleNet.Data;
using TriangleNet.Log;
/// <summary>
/// Builds a delaunay triangulation using the sweepline algorithm.
/// </summary>
class SweepLine
{
/// <summary>
/// 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.).
/// </summary>
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<SplayNode> 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;
}
/// <summary>
/// Removes ghost triangles.
/// </summary>
/// <param name="startghost"></param>
/// <returns>Number of vertices on the hull.</returns>
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<SplayNode>();
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);
}
}
}
+202
View File
@@ -0,0 +1,202 @@
// -----------------------------------------------------------------------
// <copyright file="BadTriQueue.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using System.Collections.Generic;
using TriangleNet.Data;
/// <summary>
/// TODO: Update summary.
/// </summary>
class BadTriQueue
{
static readonly double SQRT2 = 1.4142135623730950488016887242096980785696718753769480732;
public List<BadTriangle> 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<BadTriangle>();
queuefront = new BadTriangle[4096];
queuetail = new BadTriangle[4096];
nextnonemptyq = new int[4096];
firstnonemptyq = -1;
}
#region Queue
/// <summary>
/// Add a bad triangle data structure to the end of a queue.
/// </summary>
/// <param name="badtri"></param>
/// <remarks>
// 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.
/// </remarks>
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;
}
/// <summary>
/// Add a bad triangle to the end of a queue.
/// </summary>
/// <param name="enqtri"></param>
/// <param name="minedge"></param>
/// <param name="enqapex"></param>
/// <param name="enqorg"></param>
/// <param name="enqdest"></param>
/// <remarks>
/// Allocates a badtriang data structure for the triangle, then passes it to enqueuebadtriang().
/// </remarks>
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);
}
/// <summary>
/// Remove a triangle from the front of the queue.
/// </summary>
/// <returns></returns>
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
}
}
+73
View File
@@ -0,0 +1,73 @@
// -----------------------------------------------------------------------
// <copyright file="Behavior.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
using System;
namespace TriangleNet
{
// TODO: Make Behavior non-static and an instance member of mesh class.
/// <summary>
/// Controls the behavior of the meshing software.
/// </summary>
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;
}
}
}
+528
View File
@@ -0,0 +1,528 @@
// -----------------------------------------------------------------------
// <copyright file="Carver.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using TriangleNet.Data;
using System;
/// <summary>
/// Carves holes into the triangulation.
/// </summary>
class Carver
{
Mesh mesh;
public Carver(Mesh mesh)
{
this.mesh = mesh;
}
/// <summary>
/// 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.
/// </summary>
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));
}
/// <summary>
/// Spread the virus from all infected triangles to any neighbors not
/// protected by subsegments. Delete all infected triangles.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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();
}
/// <summary>
/// Spread regional attributes and/or area constraints (from a .poly file)
/// throughout the mesh.
/// </summary>
/// <param name="attribute"></param>
/// <param name="area"></param>
/// <remarks>
/// 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.
/// </remarks>
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();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="holelist"></param>
/// <param name="holes"></param>
/// <param name="regionlist"></param>
/// <param name="regions"></param>
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;
}
}
}
}
+45
View File
@@ -0,0 +1,45 @@
// -----------------------------------------------------------------------
// <copyright file="BadSubseg.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// A queue used to store encroached subsegments.
/// </summary>
/// <remarks>
/// Each subsegment's vertices are stored so that we can check whether a
/// subsegment is still the same.
/// </remarks>
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);
}
};
}
+42
View File
@@ -0,0 +1,42 @@
// -----------------------------------------------------------------------
// <copyright file="BadTriangle.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// A queue used to store bad triangles.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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);
}
}
}
+27
View File
@@ -0,0 +1,27 @@
// -----------------------------------------------------------------------
// <copyright file="FlipStacker.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// A stack of triangles flipped during the most recent vertex insertion.
/// </summary>
/// <remarks>
/// The stack is used to undo the vertex insertion if the vertex encroaches
/// upon a subsegment.
/// </remarks>
class FlipStacker
{
public Otri flippedtri; // A recently flipped triangle.
public FlipStacker prevflip; // Previous flip in the stack.
}
}
+255
View File
@@ -0,0 +1,255 @@
// -----------------------------------------------------------------------
// <copyright file="Osub.cs">
// 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
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// An oriented subsegment.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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
/// <summary>
/// Reverse the orientation of a subsegment. [sym(ab) -> ba]
/// </summary>
/// <remarks>ssym() toggles the orientation of a subsegment.
/// </remarks>
public void Sym(ref Osub o2)
{
o2.ss = ss;
o2.ssorient = 1 - ssorient;
}
/// <summary>
/// Reverse the orientation of a subsegment. [sym(ab) -> ba]
/// </summary>
public void SymSelf()
{
ssorient = 1 - ssorient;
}
/// <summary>
/// Find adjoining subsegment with the same origin. [pivot(ab) -> a*]
/// </summary>
/// <remarks>spivot() finds the other subsegment (from the same segment)
/// that shares the same origin.
/// </remarks>
public void Pivot(ref Osub o2)
{
o2 = ss.subsegs[ssorient];
//sdecode(sptr, o2);
}
/// <summary>
/// Find adjoining subsegment with the same origin. [pivot(ab) -> a*]
/// </summary>
public void PivotSelf()
{
this = ss.subsegs[ssorient];
//sdecode(sptr, osub);
}
/// <summary>
/// Find next subsegment in sequence. [next(ab) -> b*]
/// </summary>
/// <remarks>snext() finds the next subsegment (from the same segment) in
/// sequence; one whose origin is the input subsegment's destination.
/// </remarks>
public void Next(ref Osub o2)
{
o2 = ss.subsegs[1 - ssorient];
//sdecode(sptr, o2);
}
/// <summary>
/// Find next subsegment in sequence. [next(ab) -> b*]
/// </summary>
public void NextSelf()
{
this = ss.subsegs[1 - ssorient];
//sdecode(sptr, osub);
}
/// <summary>
/// Get the origin of a subsegment
/// </summary>
public Vertex Org()
{
return ss.vertices[ssorient];
}
/// <summary>
/// Get the destination of a subsegment
/// </summary>
public Vertex Dest()
{
return ss.vertices[1 - ssorient];
}
/// <summary>
/// Set the origin or destination of a subsegment.
/// </summary>
public void SetOrg(Vertex ptr)
{
ss.vertices[ssorient] = ptr;
}
/// <summary>
/// Set destination of a subsegment.
/// </summary>
public void SetDest(Vertex ptr)
{
ss.vertices[1 - ssorient] = ptr;
}
/// <summary>
/// Get the origin of the segment that includes the subsegment.
/// </summary>
public Vertex SegOrg()
{
return ss.vertices[2 + ssorient];
}
/// <summary>
/// Get the destination of the segment that includes the subsegment.
/// </summary>
public Vertex SegDest()
{
return ss.vertices[3 - ssorient];
}
/// <summary>
/// Set the origin of the segment that includes the subsegment.
/// </summary>
public void SetSegOrg(Vertex ptr)
{
ss.vertices[2 + ssorient] = ptr;
}
/// <summary>
/// Set the destination of the segment that includes the subsegment.
/// </summary>
public void SetSegDest(Vertex ptr)
{
ss.vertices[3 - ssorient] = ptr;
}
/// <summary>
/// Read a boundary marker.
/// </summary>
/// <remarks>Boundary markers are used to hold user-defined tags for
/// setting boundary conditions in finite element solvers.</remarks>
public int Mark()
{
return ss.boundary;
}
/// <summary>
/// Set a boundary marker.
/// </summary>
public void SetMark(int value)
{
ss.boundary = value;
}
/// <summary>
/// Bond two subsegments together. [bond(abc, ba)]
/// </summary>
public void Bond(ref Osub o2)
{
ss.subsegs[ssorient] = o2;
o2.ss.subsegs[o2.ssorient] = this;
}
/// <summary>
/// Dissolve a subsegment bond (from one side).
/// </summary>
/// <remarks>Note that the other subsegment will still think it's
/// connected to this subsegment.</remarks>
public void Dissolve()
{
ss.subsegs[ssorient].ss = Mesh.dummysub;
}
/// <summary>
/// Copy a subsegment.
/// </summary>
public void Copy(ref Osub o2)
{
o2.ss = ss;
o2.ssorient = ssorient;
}
/// <summary>
/// Test for equality of subsegments.
/// </summary>
public bool Equal(Osub o2)
{
return ((ss == o2.ss) && (ssorient == o2.ssorient));
}
/// <summary>
/// Check a subsegment's deallocation.
/// </summary>
public static bool IsDead(Subseg sub)
{
return sub.subsegs[0].ss == null;
}
/// <summary>
/// Set a subsegment's deallocation.
/// </summary>
public static void Kill(Subseg sub)
{
sub.subsegs[0].ss = null;
sub.subsegs[1].ss = null;
}
/// <summary>
/// Finds a triangle abutting a subsegment.
/// </summary>
public void TriPivot(ref Otri ot)
{
ot = ss.triangles[ssorient];
//decode(ptr, otri)
}
/// <summary>
/// Dissolve a bond (from the subsegment side).
/// </summary>
public void TriDissolve()
{
ss.triangles[ssorient].triangle = Mesh.dummytri;
}
#endregion
}
}
+477
View File
@@ -0,0 +1,477 @@
// -----------------------------------------------------------------------
// <copyright file="Otri.cs">
// 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
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// An oriented triangle.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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.
/// <summary>
/// Find the abutting triangle; same edge. [sym(abc) -> ba*]
/// </summary>
/// <remarks>
/// Note that the edge direction is necessarily reversed, because the handle specified
/// by an oriented triangle is directed counterclockwise around the triangle.
/// </remarks>
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;
}
/// <summary>
/// Find the abutting triangle; same edge. [sym(abc) -> ba*]
/// </summary>
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.
/// <summary>
/// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca]
/// </summary>
public void Lnext(ref Otri o2)
{
o2.triangle = triangle;
o2.orient = plus1Mod3[orient];
}
/// <summary>
/// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca]
/// </summary>
public void LnextSelf()
{
orient = plus1Mod3[orient];
}
/// <summary>
/// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab]
/// </summary>
public void Lprev(ref Otri o2)
{
o2.triangle = triangle;
o2.orient = minus1Mod3[orient];
}
/// <summary>
/// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab]
/// </summary>
public void LprevSelf()
{
orient = minus1Mod3[orient];
}
/// <summary>
/// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*]
/// </summary>
/// <remarks>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.
/// </remarks>
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;
}
/// <summary>
/// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*]
/// </summary>
public void OnextSelf()
{
//LprevSelf();
orient = minus1Mod3[orient];
//SymSelf();
int tmp = orient;
orient = triangle.neighbors[tmp].orient;
triangle = triangle.neighbors[tmp].triangle;
}
/// <summary>
/// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b]
/// </summary>
/// <remarks>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.
/// </remarks>
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];
}
/// <summary>
/// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b]
/// </summary>
public void OprevSelf()
{
//SymSelf();
int tmp = orient;
orient = triangle.neighbors[tmp].orient;
triangle = triangle.neighbors[tmp].triangle;
//LnextSelf();
orient = plus1Mod3[orient];
}
/// <summary>
/// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba]
/// </summary>
/// <remarks>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.
/// </remarks>
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];
}
/// <summary>
/// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba]
/// </summary>
public void DnextSelf()
{
//SymSelf();
int tmp = orient;
orient = triangle.neighbors[tmp].orient;
triangle = triangle.neighbors[tmp].triangle;
//LprevSelf();
orient = minus1Mod3[orient];
}
/// <summary>
/// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*]
/// </summary>
/// <remarks>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.
/// </remarks>
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;
}
/// <summary>
/// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*]
/// </summary>
public void DprevSelf()
{
//LnextSelf();
orient = plus1Mod3[orient];
//SymSelf();
int tmp = orient;
orient = triangle.neighbors[tmp].orient;
triangle = triangle.neighbors[tmp].triangle;
}
/// <summary>
/// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*]
/// </summary>
/// <remarks>rnext() moves one edge counterclockwise about the adjacent
/// triangle. (It's best understood by reading Guibas and Stolfi. It
/// involves changing triangles twice.)
/// </remarks>
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;
}
/// <summary>
/// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*]
/// </summary>
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;
}
/// <summary>
/// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**]
/// </summary>
/// <remarks>rprev() moves one edge clockwise about the adjacent triangle.
/// (It's best understood by reading Guibas and Stolfi. It involves
/// changing triangles twice.)
/// </remarks>
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;
}
/// <summary>
/// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**]
/// </summary>
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;
}
/// <summary>
/// Origin [org(abc) -> a]
/// </summary>
public Vertex Org()
{
return triangle.vertices[plus1Mod3[orient]];
}
/// <summary>
/// Destination [dest(abc) -> b]
/// </summary>
public Vertex Dest()
{
return triangle.vertices[minus1Mod3[orient]];
}
/// <summary>
/// Apex [apex(abc) -> c]
/// </summary>
public Vertex Apex()
{
return triangle.vertices[orient];
}
/// <summary>
/// Set Origin
/// </summary>
public void SetOrg(Vertex ptr)
{
triangle.vertices[plus1Mod3[orient]] = ptr;
}
/// <summary>
/// Set Destination
/// </summary>
public void SetDest(Vertex ptr)
{
triangle.vertices[minus1Mod3[orient]] = ptr;
}
/// <summary>
/// Set Apex
/// </summary>
public void SetApex(Vertex ptr)
{
triangle.vertices[orient] = ptr;
}
/// <summary>
/// Bond two triangles together at the resepective handles. [bond(abc, bad)]
/// </summary>
public void Bond(ref Otri o2)
{
triangle.neighbors[orient] = o2;
o2.triangle.neighbors[o2.orient] = this;
}
/// <summary>
/// Dissolve a bond (from one side).
/// </summary>
/// <remarks>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.
/// </remarks>
public void Dissolve()
{
triangle.neighbors[orient].triangle = Mesh.dummytri;
}
/// <summary>
/// Copy an oriented triangle.
/// </summary>
public void Copy(ref Otri o2)
{
o2.triangle = triangle;
o2.orient = orient;
}
/// <summary>
/// Test for equality of oriented triangles.
/// </summary>
public bool Equal(Otri o2)
{
return ((triangle == o2.triangle) && (orient == o2.orient));
}
/// <summary>
/// Infect a triangle with the virus.
/// </summary>
public void Infect()
{
triangle.infected = true;
}
/// <summary>
/// Cure a triangle from the virus.
/// </summary>
public void Uninfect()
{
triangle.infected = false;
}
/// <summary>
/// Test a triangle for viral infection.
/// </summary>
public bool IsInfected()
{
return triangle.infected;
}
/// <summary>
/// Check a triangle's deallocation.
/// </summary>
public static bool IsDead(Triangle tria)
{
return tria.neighbors[0].triangle == null;
}
/// <summary>
/// Set a triangle's deallocation.
/// </summary>
public static void Kill(Triangle tria)
{
tria.neighbors[0].triangle = null;
tria.neighbors[2].triangle = null;
}
/// <summary>
/// Finds a subsegment abutting a triangle.
/// </summary>
public void SegPivot(ref Osub os)
{
os = triangle.subsegs[orient];
//sdecode(sptr, osub)
}
/// <summary>
/// Bond a triangle to a subsegment.
/// </summary>
public void SegBond(ref Osub os)
{
triangle.subsegs[orient] = os;
os.ss.triangles[os.ssorient] = this;
}
/// <summary>
/// Dissolve a bond (from the triangle side).
/// </summary>
public void SegDissolve()
{
triangle.subsegs[orient].ss = Mesh.dummysub;
}
#endregion
}
}
+35
View File
@@ -0,0 +1,35 @@
// -----------------------------------------------------------------------
// <copyright file="Point2.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// Represents a 2D point.
/// </summary>
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);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
// -----------------------------------------------------------------------
// <copyright file="Region.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// TODO: Update summary.
/// </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];
}
}
}
+37
View File
@@ -0,0 +1,37 @@
// -----------------------------------------------------------------------
// <copyright file="SplayNode.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// A node in the splay tree.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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.
}
}
+85
View File
@@ -0,0 +1,85 @@
// -----------------------------------------------------------------------
// <copyright file="Subseg.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// The subsegment data structure.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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;
}
/// <summary>
/// Reset the hash seed.
/// </summary>
/// <param name="value">The new has seed value.</param>
/// <remarks>Reset value will usally 0, if a new triangulation starts,
/// or the number of subsegments, if refinement is done.</remarks>
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);
}
}
}
+33
View File
@@ -0,0 +1,33 @@
// -----------------------------------------------------------------------
// <copyright file="SweepEvent.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// A node in a heap used to store events for the sweepline Delaunay algorithm.
/// </summary>
/// <remarks>
/// 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'.
/// </remarks>
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.
}
}
+104
View File
@@ -0,0 +1,104 @@
// -----------------------------------------------------------------------
// <copyright file="Triangle.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// The triangle data structure.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
/// <summary>
/// Reset the hash seed.
/// </summary>
/// <param name="value">The new has seed value.</param>
/// <remarks>Reset value will usally 0, if a new triangulation starts,
/// or the number of triangles, if refinement is done.</remarks>
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);
}
}
}
+164
View File
@@ -0,0 +1,164 @@
// -----------------------------------------------------------------------
// <copyright file="Vertex.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Data
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// The vertex data structure.
/// </summary>
/// <remarks>
/// Each vertex is actually an array of doubles. An integer boundary marker,
/// and sometimes a to a triangle, is appended after the doubles.
/// </remarks>
class Vertex : IComparable<Vertex>, IEquatable<Vertex>
{
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);
}
/// <summary>
/// Gets the specified coordinate of the vertex.
/// </summary>
/// <param name="i">Coordinate index.</param>
/// <returns>X coordinate, if index is 0, Y coordinate, if index is 1.</returns>
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.");
}
}
/// <summary>
/// Reset the hash seed.
/// </summary>
/// <param name="value">The new has seed value.</param>
/// <remarks>Reset value will usally 0, if a new triangulation starts,
/// or the number of points, if refinement is done.</remarks>
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;
}
}
}
+94
View File
@@ -0,0 +1,94 @@
// -----------------------------------------------------------------------
// <copyright file="Enums.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
/// <summary>
/// Mesh generation options.
/// </summary>
public enum Options
{
/// <summary>
/// Minimum angle constraint (numeric).
/// </summary>
MinAngle,
/// <summary>
/// Maximum angle constraint (numeric).
/// </summary>
MaxAngle,
/// <summary>
/// Global maximum area constraint (numeric).
/// </summary>
MaxArea,
/// <summary>
/// Maximum number of Steiner points (interger).
/// </summary>
SteinerPoints,
/// <summary>
/// No new vertices on the boundary (interger).
/// </summary>
NoBisect,
/// <summary>
/// Generate conforming Delaunay triangulations (boolean).
/// </summary>
ConformingDelaunay,
/// <summary>
/// Use boundary markers (boolean).
/// </summary>
BoundaryMarkers,
/// <summary>
/// Set default values for quality mesh generation (boolean).
/// </summary>
Quality,
/// <summary>
/// Create segments on the convex hull (boolean).
/// </summary>
Convex
};
/// <summary>
/// Implemented triangulation algorithms.
/// </summary>
public enum TriangulationAlgorithm
{
Dwyer,
Incremental,
SweepLine
};
/// <summary>
/// Labels that signify the result of point location.
/// </summary>
/// <remarks>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.
/// </remarks>
enum LocateResult { InTriangle, OnEdge, OnVertex, Outside };
/// <summary>
/// Labels that signify the result of vertex insertion.
/// </summary>
/// <remarks>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.
/// </remarks>
enum InsertVertexResult { Successful, Encroaching, Violating, Duplicate };
/// <summary>
/// Labels that signify the result of direction finding.
/// </summary>
/// <remarks>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.
/// </remarks>
enum FindDirectionResult { Within, Leftcollinear, Rightcollinear };
/// <summary>
/// The type of the mesh vertex.
/// </summary>
enum VertexType { InputVertex, SegmentVertex, FreeVertex, DeadVertex, UndeadVertex };
}
+359
View File
@@ -0,0 +1,359 @@
// -----------------------------------------------------------------------
// <copyright file="io.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Globalization;
using TriangleNet.Data;
using TriangleNet.Log;
/// <summary>
/// TODO: Update summary.
/// </summary>
public static class DataReader
{
class StackTri
{
public Otri tri = default(Otri);
public StackTri next;
}
#region Library
/// <summary>
/// Reconstruct a triangulation from its raw data representation.
/// </summary>
/// <param name="mesh"></param>
/// <param name="input"></param>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
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<Otri>[] 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<Otri>[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<Otri>(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
}
}
+382
View File
@@ -0,0 +1,382 @@
// -----------------------------------------------------------------------
// <copyright file="io.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System;
using System.IO;
using System.Collections.Generic;
using System.Globalization;
using TriangleNet.Data;
/// <summary>
/// Generates a mesh representaion using arrays.
/// </summary>
public static class DataWriter
{
static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
static int verticesCount;
static int elementsCount;
#region Library
/// <summary>
/// Number the vertices and write them to raw output data.
/// </summary>
/// <param name="mesh"></param>
/// <param name="data"></param>
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++;
}
}
}
/// <summary>
/// Write the triangles to raw output data.
/// </summary>
/// <param name="mesh"></param>
/// <param name="data"></param>
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++;
}
}
/// <summary>
/// Write the segments and holes to raw output data.
/// </summary>
/// <param name="mesh"></param>
/// <param name="data"></param>
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++;
}
}
/// <summary>
/// Write the edges to raw output data.
/// </summary>
/// <param name="mesh"></param>
/// <param name="data"></param>
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++;
}
}
}
}
/// <summary>
/// Write the triangle neighbors to raw output data.
/// </summary>
/// <param name="mesh"></param>
/// <param name="data"></param>
/// <remarks>WARNING: Be sure WriteElements has been called before,
/// so the elements are numbered right!</remarks>
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++;
}
}
/// <summary>
/// Gets the Voronoi diagram as raw output data.
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
/// <remarks>
/// 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.</remarks>
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
}
}
+618
View File
@@ -0,0 +1,618 @@
// -----------------------------------------------------------------------
// <copyright file="io.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System;
using System.IO;
using System.Globalization;
using TriangleNet.Data;
using TriangleNet.Log;
/// <summary>
/// Helper for reading Triangle files.
/// </summary>
public static class FileReader
{
static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
static int startIndex = 0;
/// <summary>
/// Read the input data from a file, which may be a .node or .poly file.
/// </summary>
/// <param name="filename">The file to read.</param>
/// <remarks>Will NOT read associated files by default.</remarks>
public static MeshData ReadFile(string filename)
{
return ReadFile(filename, false);
}
/// <summary>
/// Read the input data from a file, which may be a .node or .poly file.
/// </summary>
/// <param name="filename">The file to read.</param>
/// <param name="readsupp">Read associated files (ele, area, neigh).</param>
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]);
}
}
}
/// <summary>
/// Read the vertices from a file, which may be a .node or .poly file.
/// </summary>
/// <param name="nodefilename"></param>
/// <remarks>Will NOT read associated .ele by default.</remarks>
public static MeshData ReadNodeFile(string nodefilename)
{
return ReadNodeFile(nodefilename, false);
}
/// <summary>
/// Read the vertices from a file, which may be a .node or .poly file.
/// </summary>
/// <param name="nodefilename"></param>
/// <param name="readElements"></param>
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;
}
/// <summary>
/// Read the vertices and segments from a .poly file.
/// </summary>
/// <param name="polyfilename"></param>
/// <remarks>Will NOT read associated .ele by default.</remarks>
public static MeshData ReadPolyFile(string polyfilename)
{
return ReadPolyFile(polyfilename, false, false);
}
/// <summary>
/// Read the vertices and segments from a .poly file.
/// </summary>
/// <param name="polyfilename"></param>
/// <param name="readElements">If true, look for an associated .ele file.</param>
/// <remarks>Will NOT read associated .area by default.</remarks>
public static MeshData ReadPolyFile(string polyfilename, bool readElements)
{
return ReadPolyFile(polyfilename, readElements, false);
}
/// <summary>
/// Read the vertices and segments from a .poly file.
/// </summary>
/// <param name="polyfilename"></param>
/// <param name="readElements">If true, look for an associated .ele file.</param>
/// <param name="readElements">If true, look for an associated .area file.</param>
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;
}
/// <summary>
/// Read the elements from an .ele file.
/// </summary>
/// <param name="elefilename"></param>
/// <param name="data"></param>
/// <param name="readArea"></param>
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);
}
}
}
/// <summary>
/// Read the area constraints from an .area file.
/// </summary>
/// <param name="areafilename"></param>
/// <param name="intriangles"></param>
/// <param name="data"></param>
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);
}
}
}
}
}
+505
View File
@@ -0,0 +1,505 @@
// -----------------------------------------------------------------------
// <copyright file="io.cs" company="">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
using System;
using System.IO;
using System.Globalization;
using TriangleNet.Data;
/// <summary>
/// TODO: Update summary.
/// </summary>
public static class FileWriter
{
static NumberFormatInfo nfi = CultureInfo.InvariantCulture.NumberFormat;
// TODO: Intelligent file name guessing
#region File IO
/// <summary>
/// Number the vertices and write them to a .node file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
public static void WriteNodes(Mesh mesh, string filename)
{
using (StreamWriter writer = new StreamWriter(filename))
{
FileWriter.WriteNodes(mesh, writer);
}
}
/// <summary>
/// Number the vertices and write them to a .node file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
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++;
}
}
}
}
/// <summary>
/// Write the triangles to an .ele file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
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++;
}
}
}
/// <summary>
/// Write the segments and holes to a .poly file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
public static void WritePoly(Mesh mesh, string filename)
{
FileWriter.WritePoly(mesh, filename, true);
}
/// <summary>
/// Write the segments and holes to a .poly file.
/// </summary>
/// <param name="mesh">Data source.</param>
/// <param name="filename">File name.</param>
/// <param name="writeNodes">Write nodes into this file.</param>
/// <remarks>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.</remarks>
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++;
}
}
}
}
/// <summary>
/// Write the edges to an .edge file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
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++;
}
}
}
}
}
/// <summary>
/// Write the triangle neighbors to a .neigh file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
/// <remarks>WARNING: Be sure WriteElements has been called before,
/// so the elements are numbered right!</remarks>
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);
}
}
}
/// <summary>
/// Write the Voronoi diagram to a .voro file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
/// <returns></returns>
/// <remarks>
/// 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.</remarks>
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++;
}
}
}
}
}
/// <summary>
/// Write the triangulation to an .off file.
/// </summary>
/// <param name="mesh"></param>
/// <param name="filename"></param>
/// <remarks>
/// OFF stands for the Object File Format, a format used by the Geometry
/// Center's Geomview package.
/// </remarks>
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
}
}
+33
View File
@@ -0,0 +1,33 @@
// -----------------------------------------------------------------------
// <copyright file="TriangulateIO.cs">
// 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
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
/// <summary>
/// Stores the mesh data in- and output.
/// </summary>
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
}
}
+24
View File
@@ -0,0 +1,24 @@
// -----------------------------------------------------------------------
// <copyright file="TriangulateIO.cs">
// 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
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.IO
{
/// <summary>
/// Stores the mesh data in- and output.
/// </summary>
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;
}
}
+27
View File
@@ -0,0 +1,27 @@
// -----------------------------------------------------------------------
// <copyright file="ILogger.cs" company="">
// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Log
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// TODO: Update summary.
/// </summary>
public interface ILog<T>
{
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<T> Data { get; }
}
}
+74
View File
@@ -0,0 +1,74 @@
// -----------------------------------------------------------------------
// <copyright file="SimpleLogger.cs" company="">
// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Log
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// A simple logger, which logs messages to a List<string>.
/// </summary>
/// <remarks>Using singleton pattern as proposed by Jon Skeet.
/// http://csharpindepth.com/Articles/General/Singleton.aspx
/// </remarks>
public sealed class SimpleLogger : ILog<string>
{
private List<string> log = new List<string>();
#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<string> 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<string> Data
{
get { return log; }
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+463
View File
@@ -0,0 +1,463 @@
// -----------------------------------------------------------------------
// <copyright file="Primitives.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using System;
using TriangleNet.Data;
/// <summary>
/// Provides some primitives regularly used in computational geometry.
/// </summary>
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;
/// <summary>
/// Initialize the variables used for exact arithmetic.
/// </summary>
/// <remarks>
/// '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.
/// </remarks>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="pa"></param>
/// <param name="pb"></param>
/// <param name="pc"></param>
/// <returns>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.</returns>
/// <remarks>
/// 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.
/// </remarks>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="pa"></param>
/// <param name="pb"></param>
/// <param name="pc"></param>
/// <param name="pd"></param>
/// <returns>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.</returns>
/// <remarks>
/// 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.
/// </remarks>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="pa"></param>
/// <param name="pb"></param>
/// <param name="pc"></param>
/// <param name="pd"></param>
/// <returns></returns>
/// <remarks>
/// 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.
/// </remarks>
public static double NonRegular(Point2 pa, Point2 pb, Point2 pc, Point2 pd)
{
return InCircle(pa, pb, pc, pd);
}
/// <summary>
/// Find the circumcenter of a triangle.
/// </summary>
/// <param name="torg"></param>
/// <param name="tdest"></param>
/// <param name="tapex"></param>
/// <param name="circumcenter"></param>
/// <param name="xi"></param>
/// <param name="eta"></param>
/// <param name="offcenter"></param>
/// <remarks>
/// 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.
/// </remarks>
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;
}
/*
/// <summary>
/// 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.
/// </summary>
/// <param name="pa"></param>
/// <param name="pb"></param>
/// <param name="pc"></param>
/// <param name="pd"></param>
/// <param name="aheight"></param>
/// <param name="bheight"></param>
/// <param name="cheight"></param>
/// <param name="dheight"></param>
/// <returns>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.</returns>
/// <remarks>
/// 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.
/// </remarks>
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);
}
*/
}
}
@@ -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")]
+998
View File
@@ -0,0 +1,998 @@
// -----------------------------------------------------------------------
// <copyright file="Quality.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using System;
using System.Collections.Generic;
using TriangleNet.Data;
using TriangleNet.Log;
/// <summary>
/// Provides methods for mesh quality enforcement and testing.
/// </summary>
class Quality
{
Queue<BadSubseg> badsubsegs;
BadTriQueue queue;
Mesh mesh;
Func<Vertex, Vertex, Vertex, double, bool> userTest;
ILog<string> logger;
public Quality(Mesh m)
{
logger = SimpleLogger.Instance;
badsubsegs = new Queue<BadSubseg>();
queue = new BadTriQueue();
mesh = m;
}
/// <summary>
/// Deallocate space for a bad subsegment, marking it dead.
/// </summary>
/// <param name="dyingseg"></param>
public void AddBadSubseg(BadSubseg badseg)
{
badsubsegs.Enqueue(badseg);
}
#region Check
/// <summary>
/// Test the mesh for topological consistency.
/// </summary>
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);
}
/// <summary>
/// Ensure that the mesh is (constrained) Delaunay.
/// </summary>
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);
}
/// <summary>
/// Check a subsegment to see if it is encroached; add it to the list if it is.
/// </summary>
/// <param name="testsubseg"></param>
/// <returns>Returns a nonzero value if the subsegment is encroached.</returns>
/// <remarks>
/// 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.
/// </remarks>
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;
}
/// <summary>
/// Test a triangle for quality and size.
/// </summary>
/// <param name="testtri">Triangle to check.</param>
/// <remarks>
/// 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.
/// </remarks>
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
/// <summary>
/// Traverse the entire list of subsegments, and check each to see if it
/// is encroached. If so, add it to the list.
/// </summary>
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);
}
}
/// <summary>
/// Split all the encroached subsegments.
/// </summary>
/// <param name="triflaws">A flag that specifies whether one should take
/// note of new bad triangles that result from inserting vertices to repair
/// encroached subsegments.</param>
/// <remarks>
/// 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.
/// </remarks>
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;
}
}
/// <summary>
/// Test every triangle in the mesh for quality measures.
/// </summary>
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);
}
}
/// <summary>
/// Inserts a vertex at the circumcenter of a triangle. Deletes
/// the newly inserted vertex if it encroaches upon a segment.
/// </summary>
/// <param name="badtri"></param>
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.");
}
}
}
/// <summary>
/// Remove all the encroached subsegments and bad triangles from the triangulation.
/// </summary>
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
}
}
+80
View File
@@ -0,0 +1,80 @@
// -----------------------------------------------------------------------
// <copyright file="Sampler.cs">
// Triangle.NET code by Christian Woltering, http://home.edo.tu-dortmund.de/~woltering/triangle/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// Used for triangle sampling in the Mesh.Locate method.
/// </summary>
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;
/// <summary>
/// Update sampling parameters if mesh changed.
/// </summary>
/// <param name="mesh">Current mesh.</param>
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();
}
}
/// <summary>
/// Get a random sample set of triangle keys.
/// </summary>
/// <returns>Array of triangle keys.</returns>
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;
}
}
}
+641
View File
@@ -0,0 +1,641 @@
// -----------------------------------------------------------------------
// <copyright file="Statistic.cs">
// 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/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet
{
using System;
using System.Text;
using TriangleNet.Data;
/// <summary>
/// Gather mesh statistics.
/// </summary>
public class Statistic
{
#region Static members
/// <summary>
/// Number of incircle tests performed.
/// </summary>
public static long InCircleCount = 0;
/// <summary>
/// Number of counterclockwise tests performed.
/// </summary>
public static long CounterClockwiseCount = 0;
/// <summary>
/// Number of 3D orientation tests performed.
/// </summary>
public static long Orient3dCount = 0;
/// <summary>
/// Number of right-of-hyperbola tests performed.
/// </summary>
public static long HyperbolaCount = 0;
/// <summary>
/// // Number of circumcenter calculations performed.
/// </summary>
public static long CircumcenterCount = 0;
/// <summary>
/// Number of circle top calculations performed.
/// </summary>
public static long CircleTopCount = 0;
/// <summary>
/// Number of vertex relocation.
/// </summary>
public static long RelocationCount = 0;
#endregion
#region Properties
double minEdge = 0;
/// <summary>
/// Shortest edge
/// </summary>
public double ShortestEdge { get { return minEdge; } }
double maxEdge = 0;
/// <summary>
/// Longest edge
/// </summary>
public double LongestEdge { get { return maxEdge; } }
//
double minAspect = 0;
/// <summary>
/// Shortest altitude
/// </summary>
public double ShortestAltitude { get { return minAspect; } }
double maxAspect = 0;
/// <summary>
/// Largest aspect ratio
/// </summary>
public double LargestAspectRatio { get { return maxAspect; } }
double minArea = 0;
/// <summary>
/// Smallest area
/// </summary>
public double SmallestArea { get { return minArea; } }
double maxArea = 0;
/// <summary>
/// Largest area
/// </summary>
public double LargestArea { get { return maxArea; } }
double minAngle = 0;
/// <summary>
/// Smallest angle
/// </summary>
public double SmallestAngle { get { return minAngle; } }
double maxAngle = 0;
/// <summary>
/// Largest angle
/// </summary>
public double LargestAngle { get { return maxAngle; } }
int inVetrices = 0;
/// <summary>
/// Input vertices
/// </summary>
public int InputVertices { get { return inVetrices; } }
int inTriangles = 0;
/// <summary>
/// Input triangles
/// </summary>
public int InputTriangles { get { return inTriangles; } }
int inSegments = 0;
/// <summary>
/// Input segments
/// </summary>
public int InputSegments { get { return inSegments; } }
int inHoles = 0;
/// <summary>
/// Input holes
/// </summary>
public int InputHoles { get { return inHoles; } }
int outVertices = 0;
/// <summary>
/// Mesh vertices
/// </summary>
public int Vertices { get { return outVertices; } }
int outTriangles = 0;
/// <summary>
/// Mesh triangles
/// </summary>
public int Triangles { get { return outTriangles; } }
int outEdges = 0;
/// <summary>
/// Mesh edges
/// </summary>
public int Edges { get { return outEdges; } }
int boundaryEdges = 0;
/// <summary>
/// Exterior boundary edges
/// </summary>
public int BoundaryEdges { get { return boundaryEdges; } }
int intBoundaryEdges = 0;
/// <summary>
/// Interior boundary edges
/// </summary>
public int InteriorBoundaryEdges { get { return intBoundaryEdges; } }
int constrainedEdges = 0;
/// <summary>
/// Constrained edges
/// </summary>
public int ConstrainedEdges { get { return constrainedEdges; } }
int[] angleTable;
/// <summary>
/// Angle histogram
/// </summary>
public int[] AngleHistogram { get { return angleTable; } }
#endregion
/// <summary>
/// detailedHistogram()
/// </summary>
/// <param name="m"></param>
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 };
/// <summary>
/// Update statistics about the quality of the mesh.
/// </summary>
/// <param name="mesh"></param>
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));
}
}
}
*/
}
}
+89
View File
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{F7907A0A-B75F-400B-9E78-BFAD00DB4D6B}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TriangleNet</RootNamespace>
<AssemblyName>Triangle</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<SccProjectName>SAK</SccProjectName>
<SccLocalPath>SAK</SccLocalPath>
<SccAuxPath>SAK</SccAuxPath>
<SccProvider>SAK</SccProvider>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
</ItemGroup>
<ItemGroup>
<Compile Include="Algorithm\ITriangulator.cs" />
<Compile Include="BadTriQueue.cs" />
<Compile Include="Behavior.cs" />
<Compile Include="Carver.cs" />
<Compile Include="Data\BadSubseg.cs" />
<Compile Include="Data\BadTriangle.cs" />
<Compile Include="Data\FlipStacker.cs" />
<Compile Include="Data\Osub.cs" />
<Compile Include="Data\Otri.cs" />
<Compile Include="Data\Point2.cs" />
<Compile Include="Data\Region.cs" />
<Compile Include="Data\SplayNode.cs" />
<Compile Include="Data\Subseg.cs" />
<Compile Include="Data\SweepEvent.cs" />
<Compile Include="Data\Triangle.cs" />
<Compile Include="Data\Vertex.cs" />
<Compile Include="Algorithm\Dwyer.cs" />
<Compile Include="IO\DataReader.cs" />
<Compile Include="IO\FileWriter.cs" />
<Compile Include="IO\VoronoiData.cs" />
<Compile Include="IO\DataWriter.cs" />
<Compile Include="IO\FileReader.cs" />
<Compile Include="Log\ILog.cs" />
<Compile Include="Log\SimpleLogger.cs" />
<Compile Include="NewLocation.cs" />
<Compile Include="Quality.cs" />
<Compile Include="Enums.cs" />
<Compile Include="Algorithm\Incremental.cs" />
<Compile Include="Mesh.cs" />
<Compile Include="Primitives.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Sampler.cs" />
<Compile Include="Statistic.cs" />
<Compile Include="Algorithm\SweepLine.cs" />
<Compile Include="IO\MeshData.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>