Initial check in.
git-svn-id: https://triangle.svn.codeplex.com/svn@66283 0e2699bc-83d4-4a8f-98e7-55e24ab8c7a5
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+176
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Generated
+489
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")]
|
||||
@@ -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>
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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")]
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user