diff --git a/Triangle.NET/TestApp/FormMain.Designer.cs b/Triangle.NET/TestApp/FormMain.Designer.cs index a6437c1..fa68f8d 100644 --- a/Triangle.NET/TestApp/FormMain.Designer.cs +++ b/Triangle.NET/TestApp/FormMain.Designer.cs @@ -55,6 +55,8 @@ this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator(); this.menuToolsRcm = new System.Windows.Forms.ToolStripMenuItem(); this.btnMesh = new MeshExplorer.Controls.DarkButton(); + this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); + this.menuToolsTopology = new System.Windows.Forms.ToolStripMenuItem(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); this.splitContainer1.Panel1.SuspendLayout(); this.splitContainer1.SuspendLayout(); @@ -285,6 +287,8 @@ this.menuTools.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.menuToolsGen, this.menuToolsCheck, + this.toolStripSeparator5, + this.menuToolsTopology, this.toolStripSeparator4, this.menuToolsRcm}); this.menuTools.Name = "menuTools"; @@ -330,6 +334,18 @@ this.btnMesh.UseVisualStyleBackColor = true; this.btnMesh.Click += new System.EventHandler(this.btnMesh_Click); // + // toolStripSeparator5 + // + this.toolStripSeparator5.Name = "toolStripSeparator5"; + this.toolStripSeparator5.Size = new System.Drawing.Size(192, 6); + // + // menuToolsTopology + // + this.menuToolsTopology.Name = "menuToolsTopology"; + this.menuToolsTopology.Size = new System.Drawing.Size(195, 22); + this.menuToolsTopology.Text = "Topology Explorer"; + this.menuToolsTopology.Click += new System.EventHandler(this.menuToolsTopology_Click); + // // FormMain // this.AllowDrop = true; @@ -394,6 +410,8 @@ private Views.MeshControlView meshControlView; private Views.StatisticView statisticView; private Views.AboutView aboutView; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator5; + private System.Windows.Forms.ToolStripMenuItem menuToolsTopology; } } diff --git a/Triangle.NET/TestApp/FormMain.cs b/Triangle.NET/TestApp/FormMain.cs index ae13757..0ab4a8c 100644 --- a/Triangle.NET/TestApp/FormMain.cs +++ b/Triangle.NET/TestApp/FormMain.cs @@ -738,6 +738,11 @@ namespace MeshExplorer } } + private void menuToolsTopology_Click(object sender, EventArgs e) + { + (new FormTopology()).ShowDialog(this); + } + private void menuToolsRcm_Click(object sender, EventArgs e) { Renumber(); diff --git a/Triangle.NET/TestApp/FormTopology.Designer.cs b/Triangle.NET/TestApp/FormTopology.Designer.cs new file mode 100644 index 0000000..5d95f85 --- /dev/null +++ b/Triangle.NET/TestApp/FormTopology.Designer.cs @@ -0,0 +1,112 @@ +namespace MeshExplorer +{ + partial class FormTopology + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.renderControl = new MeshExplorer.Topology.TopologyRenderControl(); + this.topoControlView = new MeshExplorer.Topology.TopologyControlView(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + this.SuspendLayout(); + // + // splitContainer1 + // + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.IsSplitterFixed = true; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.renderControl); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.topoControlView); + this.splitContainer1.Size = new System.Drawing.Size(674, 455); + this.splitContainer1.SplitterDistance = 475; + this.splitContainer1.SplitterWidth = 1; + this.splitContainer1.TabIndex = 0; + // + // renderControl + // + this.renderControl.BackColor = System.Drawing.Color.Black; + this.renderControl.Dock = System.Windows.Forms.DockStyle.Fill; + this.renderControl.Location = new System.Drawing.Point(0, 0); + this.renderControl.Name = "renderControl"; + this.renderControl.Size = new System.Drawing.Size(475, 455); + this.renderControl.TabIndex = 0; + this.renderControl.Text = "topologyRenderControl"; + this.renderControl.MouseClick += new System.Windows.Forms.MouseEventHandler(this.renderControl_MouseClick); + // + // topoControlView + // + this.topoControlView.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(76)))), ((int)(((byte)(76)))), ((int)(((byte)(76))))); + this.topoControlView.Dock = System.Windows.Forms.DockStyle.Fill; + this.topoControlView.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.topoControlView.ForeColor = System.Drawing.Color.White; + this.topoControlView.Location = new System.Drawing.Point(0, 0); + this.topoControlView.Name = "topoControlView"; + this.topoControlView.Size = new System.Drawing.Size(198, 455); + this.topoControlView.TabIndex = 2; + // + // FormTopology + // + 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(674, 455); + this.Controls.Add(this.splitContainer1); + 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.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FormTopology"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.Text = "Triangle.NET - Topology Explorer"; + this.Load += new System.EventHandler(this.FormTopology_Load); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.SplitContainer splitContainer1; + private Topology.TopologyRenderControl renderControl; + private Topology.TopologyControlView topoControlView; + } +} \ No newline at end of file diff --git a/Triangle.NET/TestApp/FormTopology.cs b/Triangle.NET/TestApp/FormTopology.cs new file mode 100644 index 0000000..045db86 --- /dev/null +++ b/Triangle.NET/TestApp/FormTopology.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using MeshExplorer.Topology; +using TriangleNet; +using TriangleNet.Geometry; +using TriangleNet.Tools; + +namespace MeshExplorer +{ + public partial class FormTopology : Form + { + Mesh mesh; + QuadTree tree; + OrientedTriangle current; + + public FormTopology() + { + InitializeComponent(); + } + + private void FormTopology_Load(object sender, EventArgs e) + { + var geometry = RectanglePolygon.Generate(4); + + mesh = new Mesh(); + mesh.Triangulate(geometry); + + renderControl.Initialize(mesh); + + topoControlView.PrimitiveCommandInvoked += PrimitiveCommandHandler; + + current = new OrientedTriangle(); + } + + void PrimitiveCommandHandler(object sender, GenericEventArgs e) + { + if (current.Triangle != null) + { + InvokePrimitive(e.Argument); + } + } + + private void renderControl_MouseClick(object sender, MouseEventArgs e) + { + var p = e.Location; + var size = renderControl.Size; + + var tri = FindTriangleAt(((float)p.X) / size.Width, ((float)p.Y) / size.Height); + + current.Triangle = tri; + current.Orientation = 0; + + renderControl.Update(current); + + topoControlView.SetTriangle(current); + } + + private ITriangle FindTriangleAt(float x, float y) + { + // Get mesh coordinates + var p = renderControl.Zoom.ScreenToWorld(x, y); + + topoControlView.SetPosition(p); + + if (tree == null) + { + tree = new QuadTree(mesh, 5, 2); + } + + return tree.Query(p.X, p.Y); + } + + private bool TriangleContainsPoint(ITriangle triangle, float x, float y) + { + bool t1, t2, t3; + + t1 = Sign(x, y, triangle.GetVertex(0), triangle.GetVertex(1)) < 0.0; + t2 = Sign(x, y, triangle.GetVertex(1), triangle.GetVertex(2)) < 0.0; + t3 = Sign(x, y, triangle.GetVertex(2), triangle.GetVertex(0)) < 0.0; + + return (t1 == t2) && (t2 == t3); + } + + private double Sign(double x, double y, Point p, Point q) + { + return (x - q.X) * (p.Y - q.Y) - (p.X - q.X) * (y - q.Y); + } + + + private void InvokePrimitive(string name) + { + if (name == "sym") + { + current.Sym(); + } + else if (name == "lnext") + { + current.Lnext(); + } + else if (name == "lprev") + { + current.Lprev(); + } + else if (name == "onext") + { + current.Onext(); + } + else if (name == "oprev") + { + current.Oprev(); + } + else if (name == "dnext") + { + current.Dnext(); + } + else if (name == "dprev") + { + current.Dprev(); + } + else if (name == "rnext") + { + current.Rnext(); + } + else if (name == "rprev") + { + current.Rprev(); + } + + renderControl.Update(current); + topoControlView.SetTriangle(current); + } + } +} diff --git a/Triangle.NET/TestApp/FormTopology.resx b/Triangle.NET/TestApp/FormTopology.resx new file mode 100644 index 0000000..29dcb1b --- /dev/null +++ b/Triangle.NET/TestApp/FormTopology.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Triangle.NET/TestApp/GenericEventArgs.cs b/Triangle.NET/TestApp/GenericEventArgs.cs new file mode 100644 index 0000000..9a68171 --- /dev/null +++ b/Triangle.NET/TestApp/GenericEventArgs.cs @@ -0,0 +1,19 @@ +using System; + +namespace MeshExplorer +{ + public class GenericEventArgs : EventArgs + { + T argument; + + public T Argument + { + get { return argument; } + } + + public GenericEventArgs(T arg) + { + argument = arg; + } + } +} diff --git a/Triangle.NET/TestApp/Mesh Explorer.csproj b/Triangle.NET/TestApp/Mesh Explorer.csproj index 3788f9e..785e533 100644 --- a/Triangle.NET/TestApp/Mesh Explorer.csproj +++ b/Triangle.NET/TestApp/Mesh Explorer.csproj @@ -96,6 +96,12 @@ FormMain.cs + + Form + + + FormTopology.cs + @@ -104,6 +110,7 @@ + @@ -116,6 +123,18 @@ + + + + UserControl + + + TopologyControlView.cs + + + Component + + UserControl @@ -148,6 +167,12 @@ FormMain.cs + + FormTopology.cs + + + TopologyControlView.cs + AboutView.cs diff --git a/Triangle.NET/TestApp/Topology/OrientedTriangle.cs b/Triangle.NET/TestApp/Topology/OrientedTriangle.cs new file mode 100644 index 0000000..2b32674 --- /dev/null +++ b/Triangle.NET/TestApp/Topology/OrientedTriangle.cs @@ -0,0 +1,156 @@ + +namespace MeshExplorer.Topology +{ + using TriangleNet.Data; + using TriangleNet.Geometry; + + public class OrientedTriangle + { + /// + /// + /// + public ITriangle Triangle { get; set; } + + /// + /// Ranges from 0 to 2. + /// + public int Orientation { get; set; } + + #region Oriented triangle primitives + + // For fast access + static readonly int[] plus1Mod3 = { 1, 2, 0 }; + static readonly int[] minus1Mod3 = { 2, 0, 1 }; + + /// + /// Find the abutting triangle; same edge. [sym(abc) -> ba*] + /// + public void Sym() + { + //this = tri.triangles[Orientation]; + // decode(ptr, otri); + + var org = this.Org(); + Triangle = Triangle.GetNeighbor(Orientation); + Orientation = GetOrientation(Triangle, org.ID); + } + + /// + /// Find the next edge (counterclockwise) of a triangle. [lnext(abc) -> bca] + /// + public void Lnext() + { + Orientation = plus1Mod3[Orientation]; + } + + /// + /// Find the previous edge (clockwise) of a triangle. [lprev(abc) -> cab] + /// + public void Lprev() + { + Orientation = minus1Mod3[Orientation]; + } + + /// + /// Find the next edge counterclockwise with the same origin. [onext(abc) -> ac*] + /// + public void Onext() + { + Lprev(); + Sym(); + } + + /// + /// Find the next edge clockwise with the same origin. [oprev(abc) -> a*b] + /// + public void Oprev() + { + Sym(); + Lnext(); + } + + /// + /// Find the next edge counterclockwise with the same destination. [dnext(abc) -> *ba] + /// + public void Dnext() + { + Sym(); + Lprev(); + } + + /// + /// Find the next edge clockwise with the same destination. [dprev(abc) -> cb*] + /// + public void Dprev() + { + Lnext(); + Sym(); + } + + /// + /// Find the next edge (counterclockwise) of the adjacent triangle. [rnext(abc) -> *a*] + /// + public void Rnext() + { + Sym(); + Lnext(); + Sym(); + } + + /// + /// Find the previous edge (clockwise) of the adjacent triangle. [rprev(abc) -> b**] + /// + public void Rprev() + { + Sym(); + Lprev(); + Sym(); + } + + /// + /// Origin [org(abc) -> a] + /// + public Vertex Org() + { + return Triangle.GetVertex(plus1Mod3[Orientation]); + } + + /// + /// Destination [dest(abc) -> b] + /// + public Vertex Dest() + { + return Triangle.GetVertex(minus1Mod3[Orientation]); + } + + /// + /// Apex [apex(abc) -> c] + /// + public Vertex Apex() + { + return Triangle.GetVertex(Orientation); + } + + #endregion + + private int GetOrientation(ITriangle tri, int org) + { + if (tri == null) + { + return 0; + } + + if (tri.P0 == org) + { + return 1; + } + + if (tri.P1 == org) + { + return 2; + } + + return 0; + } + } +} diff --git a/Triangle.NET/TestApp/Topology/RectanglePolygon.cs b/Triangle.NET/TestApp/Topology/RectanglePolygon.cs new file mode 100644 index 0000000..6deb24b --- /dev/null +++ b/Triangle.NET/TestApp/Topology/RectanglePolygon.cs @@ -0,0 +1,47 @@ + +namespace MeshExplorer.Topology +{ + using TriangleNet.Geometry; + + internal static class RectanglePolygon + { + public static InputGeometry Generate(int n, double bounds = 10.0) + { + var geometry = new InputGeometry((n + 1) * (n + 1)); + + double x, y, d = 2 * bounds / n; + + int mark = 0; + + for (int i = 0; i <= n; i++) + { + y = -bounds + i * d; + + for (int j = 0; j <= n; j++) + { + x = -bounds + j * d; + + geometry.AddPoint(x, y, mark); + } + } + + // Add boundary segments + for (int i = 0; i < n; i++) + { + // Bottom + geometry.AddSegment(i, i + 1); + + // Right + geometry.AddSegment(i * (n + 1) + n, (i + 1) * (n + 1) + n); + + // Top + geometry.AddSegment(n * (n + 1) + i, n * (n + 1) + (i + 1)); + + // Left + geometry.AddSegment(i * (n + 1), (i + 1) * (n + 1)); + } + + return geometry; + } + } +} diff --git a/Triangle.NET/TestApp/Topology/TopologyControlView.Designer.cs b/Triangle.NET/TestApp/Topology/TopologyControlView.Designer.cs new file mode 100644 index 0000000..69e23f3 --- /dev/null +++ b/Triangle.NET/TestApp/Topology/TopologyControlView.Designer.cs @@ -0,0 +1,359 @@ +namespace MeshExplorer.Topology +{ + partial class TopologyControlView + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.lbS2 = new System.Windows.Forms.Label(); + this.lbS1 = new System.Windows.Forms.Label(); + this.lbS0 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.lbN2 = new System.Windows.Forms.Label(); + this.lbN1 = new System.Windows.Forms.Label(); + this.lbN0 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.lbPosition = new System.Windows.Forms.Label(); + this.lbV2 = new System.Windows.Forms.Label(); + this.lbV1 = new System.Windows.Forms.Label(); + this.lbV0 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.lbTriangle = new System.Windows.Forms.Label(); + this.darkButton9 = new MeshExplorer.Controls.DarkButton(); + this.darkButton7 = new MeshExplorer.Controls.DarkButton(); + this.darkButton5 = new MeshExplorer.Controls.DarkButton(); + this.darkButton3 = new MeshExplorer.Controls.DarkButton(); + this.darkButton8 = new MeshExplorer.Controls.DarkButton(); + this.darkButton6 = new MeshExplorer.Controls.DarkButton(); + this.darkButton4 = new MeshExplorer.Controls.DarkButton(); + this.darkButton2 = new MeshExplorer.Controls.DarkButton(); + this.darkButton1 = new MeshExplorer.Controls.DarkButton(); + this.SuspendLayout(); + // + // lbS2 + // + this.lbS2.Location = new System.Drawing.Point(137, 122); + this.lbS2.Name = "lbS2"; + this.lbS2.Size = new System.Drawing.Size(53, 13); + this.lbS2.TabIndex = 16; + this.lbS2.Text = "-"; + this.lbS2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbS1 + // + this.lbS1.Location = new System.Drawing.Point(137, 107); + this.lbS1.Name = "lbS1"; + this.lbS1.Size = new System.Drawing.Size(53, 13); + this.lbS1.TabIndex = 15; + this.lbS1.Text = "-"; + this.lbS1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbS0 + // + this.lbS0.Location = new System.Drawing.Point(137, 92); + this.lbS0.Name = "lbS0"; + this.lbS0.Size = new System.Drawing.Size(53, 13); + this.lbS0.TabIndex = 17; + this.lbS0.Text = "-"; + this.lbS0.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label6 + // + this.label6.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; + this.label6.Location = new System.Drawing.Point(134, 71); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(57, 13); + this.label6.TabIndex = 19; + this.label6.Text = "Segments"; + // + // lbN2 + // + this.lbN2.Location = new System.Drawing.Point(66, 122); + this.lbN2.Name = "lbN2"; + this.lbN2.Size = new System.Drawing.Size(57, 13); + this.lbN2.TabIndex = 13; + this.lbN2.Text = "-"; + this.lbN2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbN1 + // + this.lbN1.Location = new System.Drawing.Point(66, 107); + this.lbN1.Name = "lbN1"; + this.lbN1.Size = new System.Drawing.Size(57, 13); + this.lbN1.TabIndex = 14; + this.lbN1.Text = "-"; + this.lbN1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbN0 + // + this.lbN0.Location = new System.Drawing.Point(66, 92); + this.lbN0.Name = "lbN0"; + this.lbN0.Size = new System.Drawing.Size(57, 13); + this.lbN0.TabIndex = 12; + this.lbN0.Text = "-"; + this.lbN0.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label5 + // + this.label5.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; + this.label5.Location = new System.Drawing.Point(63, 71); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(61, 13); + this.label5.TabIndex = 10; + this.label5.Text = "Neighbors"; + // + // lbPosition + // + this.lbPosition.AutoSize = true; + this.lbPosition.Location = new System.Drawing.Point(70, 15); + this.lbPosition.Name = "lbPosition"; + this.lbPosition.Size = new System.Drawing.Size(11, 13); + this.lbPosition.TabIndex = 11; + this.lbPosition.Text = "-"; + // + // lbV2 + // + this.lbV2.Location = new System.Drawing.Point(16, 122); + this.lbV2.Name = "lbV2"; + this.lbV2.Size = new System.Drawing.Size(38, 13); + this.lbV2.TabIndex = 6; + this.lbV2.Text = "-"; + this.lbV2.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbV1 + // + this.lbV1.Location = new System.Drawing.Point(16, 107); + this.lbV1.Name = "lbV1"; + this.lbV1.Size = new System.Drawing.Size(38, 13); + this.lbV1.TabIndex = 5; + this.lbV1.Text = "-"; + this.lbV1.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // lbV0 + // + this.lbV0.Location = new System.Drawing.Point(16, 92); + this.lbV0.Name = "lbV0"; + this.lbV0.Size = new System.Drawing.Size(38, 13); + this.lbV0.TabIndex = 7; + this.lbV0.Text = "-"; + this.lbV0.TextAlign = System.Drawing.ContentAlignment.MiddleRight; + // + // label4 + // + this.label4.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; + this.label4.Location = new System.Drawing.Point(8, 71); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(47, 13); + this.label4.TabIndex = 9; + this.label4.Text = "Vertices"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(6, 15); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(52, 13); + this.label1.TabIndex = 8; + this.label1.Text = "Position:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(7, 37); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(51, 13); + this.label2.TabIndex = 8; + this.label2.Text = "Triangle:"; + // + // lbTriangle + // + this.lbTriangle.AutoSize = true; + this.lbTriangle.Location = new System.Drawing.Point(70, 37); + this.lbTriangle.Name = "lbTriangle"; + this.lbTriangle.Size = new System.Drawing.Size(11, 13); + this.lbTriangle.TabIndex = 11; + this.lbTriangle.Text = "-"; + // + // darkButton9 + // + this.darkButton9.Location = new System.Drawing.Point(99, 367); + this.darkButton9.Name = "darkButton9"; + this.darkButton9.Size = new System.Drawing.Size(78, 23); + this.darkButton9.TabIndex = 21; + this.darkButton9.Text = "Rprev"; + this.darkButton9.UseVisualStyleBackColor = true; + this.darkButton9.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton7 + // + this.darkButton7.Location = new System.Drawing.Point(99, 338); + this.darkButton7.Name = "darkButton7"; + this.darkButton7.Size = new System.Drawing.Size(78, 23); + this.darkButton7.TabIndex = 22; + this.darkButton7.Text = "Dprev"; + this.darkButton7.UseVisualStyleBackColor = true; + this.darkButton7.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton5 + // + this.darkButton5.Location = new System.Drawing.Point(99, 309); + this.darkButton5.Name = "darkButton5"; + this.darkButton5.Size = new System.Drawing.Size(78, 23); + this.darkButton5.TabIndex = 23; + this.darkButton5.Text = "Oprev"; + this.darkButton5.UseVisualStyleBackColor = true; + this.darkButton5.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton3 + // + this.darkButton3.Location = new System.Drawing.Point(99, 280); + this.darkButton3.Name = "darkButton3"; + this.darkButton3.Size = new System.Drawing.Size(78, 23); + this.darkButton3.TabIndex = 24; + this.darkButton3.Text = "Lprev"; + this.darkButton3.UseVisualStyleBackColor = true; + this.darkButton3.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton8 + // + this.darkButton8.Location = new System.Drawing.Point(11, 367); + this.darkButton8.Name = "darkButton8"; + this.darkButton8.Size = new System.Drawing.Size(78, 23); + this.darkButton8.TabIndex = 25; + this.darkButton8.Text = "Rnext"; + this.darkButton8.UseVisualStyleBackColor = true; + this.darkButton8.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton6 + // + this.darkButton6.Location = new System.Drawing.Point(11, 338); + this.darkButton6.Name = "darkButton6"; + this.darkButton6.Size = new System.Drawing.Size(78, 23); + this.darkButton6.TabIndex = 26; + this.darkButton6.Text = "Dnext"; + this.darkButton6.UseVisualStyleBackColor = true; + this.darkButton6.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton4 + // + this.darkButton4.Location = new System.Drawing.Point(11, 309); + this.darkButton4.Name = "darkButton4"; + this.darkButton4.Size = new System.Drawing.Size(78, 23); + this.darkButton4.TabIndex = 27; + this.darkButton4.Text = "Onext"; + this.darkButton4.UseVisualStyleBackColor = true; + this.darkButton4.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton2 + // + this.darkButton2.Location = new System.Drawing.Point(11, 280); + this.darkButton2.Name = "darkButton2"; + this.darkButton2.Size = new System.Drawing.Size(78, 23); + this.darkButton2.TabIndex = 28; + this.darkButton2.Text = "Lnext"; + this.darkButton2.UseVisualStyleBackColor = true; + this.darkButton2.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // darkButton1 + // + this.darkButton1.Location = new System.Drawing.Point(11, 251); + this.darkButton1.Name = "darkButton1"; + this.darkButton1.Size = new System.Drawing.Size(166, 23); + this.darkButton1.TabIndex = 20; + this.darkButton1.Text = "Sym"; + this.darkButton1.UseVisualStyleBackColor = true; + this.darkButton1.Click += new System.EventHandler(this.btnPrimitive_Click); + // + // TopoControlView + // + 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.Controls.Add(this.darkButton9); + this.Controls.Add(this.darkButton7); + this.Controls.Add(this.darkButton5); + this.Controls.Add(this.darkButton3); + this.Controls.Add(this.darkButton8); + this.Controls.Add(this.darkButton6); + this.Controls.Add(this.darkButton4); + this.Controls.Add(this.darkButton2); + this.Controls.Add(this.darkButton1); + this.Controls.Add(this.lbS2); + this.Controls.Add(this.lbS1); + this.Controls.Add(this.lbS0); + this.Controls.Add(this.label6); + this.Controls.Add(this.lbN2); + this.Controls.Add(this.lbN1); + this.Controls.Add(this.lbN0); + this.Controls.Add(this.label5); + this.Controls.Add(this.lbTriangle); + this.Controls.Add(this.lbPosition); + this.Controls.Add(this.lbV2); + this.Controls.Add(this.lbV1); + this.Controls.Add(this.lbV0); + this.Controls.Add(this.label4); + this.Controls.Add(this.label2); + 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.White; + this.Name = "TopoControlView"; + this.Size = new System.Drawing.Size(195, 439); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lbS2; + private System.Windows.Forms.Label lbS1; + private System.Windows.Forms.Label lbS0; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label lbN2; + private System.Windows.Forms.Label lbN1; + private System.Windows.Forms.Label lbN0; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Label lbPosition; + private System.Windows.Forms.Label lbV2; + private System.Windows.Forms.Label lbV1; + private System.Windows.Forms.Label lbV0; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label lbTriangle; + private Controls.DarkButton darkButton9; + private Controls.DarkButton darkButton7; + private Controls.DarkButton darkButton5; + private Controls.DarkButton darkButton3; + private Controls.DarkButton darkButton8; + private Controls.DarkButton darkButton6; + private Controls.DarkButton darkButton4; + private Controls.DarkButton darkButton2; + private Controls.DarkButton darkButton1; + } +} diff --git a/Triangle.NET/TestApp/Topology/TopologyControlView.cs b/Triangle.NET/TestApp/Topology/TopologyControlView.cs new file mode 100644 index 0000000..377b7e8 --- /dev/null +++ b/Triangle.NET/TestApp/Topology/TopologyControlView.cs @@ -0,0 +1,85 @@ +using System; +using System.Drawing; +using System.Globalization; +using System.Windows.Forms; +using TriangleNet.Geometry; + +namespace MeshExplorer.Topology +{ + public partial class TopologyControlView : UserControl + { + public event EventHandler> PrimitiveCommandInvoked; + + public TopologyControlView() + { + InitializeComponent(); + } + + public void SetPosition(PointF p) + { + var nfi = NumberFormatInfo.InvariantInfo; + + lbPosition.Text = String.Format(nfi, "X: {0:0.0}, Y: {1:0.0}", p.X, p.Y); + } + + public void SetTriangle(OrientedTriangle tri) + { + var t = tri.Triangle; + + if (t != null) + { + lbTriangle.Text = t.ID.ToString(); + + lbV0.Text = t.P0.ToString(); + lbV1.Text = t.P1.ToString(); + lbV2.Text = t.P2.ToString(); + + lbN0.Text = t.N0.ToString(); + lbN1.Text = t.N1.ToString(); + lbN2.Text = t.N2.ToString(); + + lbS0.Text = GetSegmentString(t.GetSegment(0)); + lbS1.Text = GetSegmentString(t.GetSegment(1)); + lbS2.Text = GetSegmentString(t.GetSegment(2)); + } + else + { + lbTriangle.Text = "-"; + + lbV0.Text = "-"; + lbV1.Text = "-"; + lbV2.Text = "-"; + + lbN0.Text = "-"; + lbN1.Text = "-"; + lbN2.Text = "-"; + + lbS0.Text = "-"; + lbS1.Text = "-"; + lbS2.Text = "-"; + } + } + + private string GetSegmentString(ISegment seg) + { + return seg == null ? "-" : "[" + seg.P0 + " - " + seg.P1 + "]"; + } + + private void btnPrimitive_Click(object sender, EventArgs e) + { + var button = sender as Button; + + if (button != null) + { + var name = button.Text.ToLowerInvariant(); + + var handler = PrimitiveCommandInvoked; + + if (handler != null) + { + handler(this, new GenericEventArgs(name)); + } + } + } + } +} diff --git a/Triangle.NET/TestApp/Topology/TopologyControlView.resx b/Triangle.NET/TestApp/Topology/TopologyControlView.resx new file mode 100644 index 0000000..29dcb1b --- /dev/null +++ b/Triangle.NET/TestApp/Topology/TopologyControlView.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Triangle.NET/TestApp/Topology/TopologyRenderControl.cs b/Triangle.NET/TestApp/Topology/TopologyRenderControl.cs new file mode 100644 index 0000000..78b2bc3 --- /dev/null +++ b/Triangle.NET/TestApp/Topology/TopologyRenderControl.cs @@ -0,0 +1,141 @@ + +namespace MeshExplorer.Topology +{ + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Text; + using System.Windows.Forms; + using MeshRenderer.Core; + using TriangleNet; + + public class TopologyRenderControl : Control + { + // Rendering stuff + private BufferedGraphics buffer; + private BufferedGraphicsContext context; + + Zoom zoom; + TopologyRenderer renderer; + + bool initialized = false; + + public Zoom Zoom + { + get { return zoom; } + } + + /// + /// Initializes a new instance of the class. + /// + public TopologyRenderControl() + { + SetStyle(ControlStyles.ResizeRedraw, true); + + this.BackColor = Color.Black; + + zoom = new Zoom(true); + context = new BufferedGraphicsContext(); + } + + /// + /// Initialize the graphics buffer (should be called in the forms load event). + /// + public void Initialize(Mesh mesh) + { + renderer = new TopologyRenderer(mesh); + + zoom.Initialize(this.ClientRectangle); + //zoom.ClipMargin = 10.0f; + + var b = mesh.Bounds; + zoom.Update(new BoundingBox((float)b.Xmin, (float)b.Xmax, + (float)b.Ymin, (float)b.Ymax)); + + InitializeBuffer(); + + initialized = true; + + this.Render(); + } + + public void Update(OrientedTriangle otri) + { + renderer.SelectTriangle(otri.Triangle == null ? null : otri); + this.Render(); + } + + private void InitializeBuffer() + { + if (this.Width > 0 && this.Height > 0) + { + if (buffer != null) + { + if (this.ClientRectangle == buffer.Graphics.VisibleClipBounds) + { + this.Invalidate(); + + // Bounds didn't change. Probably we just restored the window + // from minimized state. + return; + } + + buffer.Dispose(); + } + + buffer = context.Allocate(Graphics.FromHwnd(this.Handle), this.ClientRectangle); + + if (initialized) + { + this.Render(); + } + } + } + + private void Render() + { + if (buffer == null) + { + return; + } + + Graphics g = buffer.Graphics; + g.Clear(this.BackColor); + + if (!initialized || renderer == null) + { + return; + } + + g.SmoothingMode = SmoothingMode.AntiAlias; + g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit; + + renderer.Render(g, zoom); + + this.Invalidate(); + } + + #region Control overrides + + protected override void OnPaint(PaintEventArgs pe) + { + if (!initialized) + { + base.OnPaint(pe); + return; + } + + buffer.Render(); + } + + protected override void OnPaintBackground(PaintEventArgs pevent) + { + // Do nothing + if (!initialized) + { + base.OnPaintBackground(pevent); + } + } + + #endregion + } +} diff --git a/Triangle.NET/TestApp/Topology/TopologyRenderer.cs b/Triangle.NET/TestApp/Topology/TopologyRenderer.cs new file mode 100644 index 0000000..290b3d5 --- /dev/null +++ b/Triangle.NET/TestApp/Topology/TopologyRenderer.cs @@ -0,0 +1,281 @@ + +namespace MeshExplorer.Topology +{ + using System; + using System.Drawing; + using MeshRenderer.Core; + using TriangleNet; + using TriangleNet.Geometry; + + public class TopologyRenderer + { + Zoom zoom; + Mesh mesh; + PointF[] points; + + // Colors + Color Background = Color.FromArgb(0, 0, 0); + Brush Point = new SolidBrush(Color.FromArgb(0, 80, 0)); + Brush Triangle = new SolidBrush(Color.FromArgb(50, 50, 50)); + Pen Line = new Pen(Color.FromArgb(30, 30, 30)); + Pen Segment = new Pen(Color.DarkBlue); + + Brush SelectedTriangle = new SolidBrush(Color.FromArgb(50, 0, 0)); + Pen SelectedEdge = new Pen(Color.DarkRed, 2.0f); + + Font font, fontTri; + + OrientedTriangle selection; + + /// + /// Initializes a new instance of the class. + /// + public TopologyRenderer(Mesh mesh) + { + this.mesh = mesh; + + points = new PointF[mesh.Vertices.Count]; + + int k = 0; + + foreach (var v in mesh.Vertices) + { + points[k++] = new PointF((float)v.X, (float)v.Y); + } + + font = new Font("Arial", 7.5f); + fontTri = new Font("Arial", 12f, FontStyle.Bold); + } + + /// + /// Renders the mesh. + /// + public void Render(Graphics g, Zoom zoom) + { + this.zoom = zoom; + + if (mesh.Edges != null) + { + this.RenderSelectedTriangle(g); + this.RenderEdges(g); + this.RenderTriangleIds(g); + } + else if (mesh.Triangles != null) + { + this.RenderTriangles(g); + } + + if (mesh.Segments != null) + { + this.RenderSegments(g); + } + + RenderSelectedEdge(g); + + if (mesh.Vertices != null) + { + this.RenderPoints(g); + } + } + + public void SelectTriangle(OrientedTriangle tri) + { + selection = tri; + } + + #region Helpers + + private PointF GetIncenter(PointF p0, PointF p1, PointF p2) + { + double cx, cy, a, b, c, + dax = p1.X - p0.X, + dbx = p2.X - p1.X, + dcx = p0.X - p2.X, + day = p1.Y - p0.Y, + dby = p2.Y - p1.Y, + dcy = p0.Y - p2.Y; + + a = Math.Sqrt(dax * dax + day * day); + b = Math.Sqrt(dbx * dbx + dby * dby); + c = Math.Sqrt(dcx * dcx + dcy * dcy); + + cx = (a * p2.X + b * p0.X + c * p1.X) / (a + b + c); + cy = (a * p2.Y + b * p0.Y + c * p1.Y) / (a + b + c); + + return new PointF((float)cx, (float)cy); + } + + private PointF GetCentroid(PointF p0, PointF p1, PointF p2) + { + double cx, cy; + + cx = (p0.X + p1.X + p2.X) / 3; + cy = (p0.Y + p1.Y + p2.Y) / 3; + + return new PointF((float)cx, (float)cy); + } + + private PointF GetPoint(ITriangle tri, int index) + { + var v = tri.GetVertex(index); + + return new PointF((float)v.X, (float)v.Y); + } + + private PointF GetPoint(ISegment seg, int index) + { + var v = seg.GetVertex(index); + + return new PointF((float)v.X, (float)v.Y); + } + + #endregion + + private void RenderPoints(Graphics g) + { + int n = points.Length; + PointF pt; + + int id = selection != null ? selection.Org().ID : -1; + + for (int i = 0; i < n; i++) + { + var brush = i == id ? Brushes.DarkRed : Point; + + pt = zoom.WorldToScreen(points[i].X, points[i].Y); + g.FillEllipse(brush, pt.X - 10f, pt.Y - 10f, 20, 20); + + pt.X -= i > 9 ? 7 : 4; + pt.Y -= 6; + g.DrawString(i.ToString(), font, Brushes.White, pt); + } + } + + private void RenderTriangles(Graphics g) + { + PointF p0, p1, p2, center; + + var triangles = mesh.Triangles; + + // Draw triangles + foreach (var tri in triangles) + { + p0 = points[tri.P0]; + p1 = points[tri.P1]; + p2 = points[tri.P2]; + + p0 = zoom.WorldToScreen(p0.X, p0.Y); + p1 = zoom.WorldToScreen(p1.X, p1.Y); + p2 = zoom.WorldToScreen(p2.X, p2.Y); + + g.DrawLine(Line, p0, p1); + g.DrawLine(Line, p1, p2); + g.DrawLine(Line, p2, p0); + + center = GetIncenter(p0, p1, p2); + center.X -= 5; + center.Y -= 5; + + g.DrawString(tri.ID.ToString(), fontTri, Triangle, center); + } + } + + private void RenderTriangleIds(Graphics g) + { + PointF p0, p1, p2, center; + + var triangles = mesh.Triangles; + + // Draw triangles + foreach (var tri in triangles) + { + p0 = points[tri.P0]; + p1 = points[tri.P1]; + p2 = points[tri.P2]; + + p0 = zoom.WorldToScreen(p0.X, p0.Y); + p1 = zoom.WorldToScreen(p1.X, p1.Y); + p2 = zoom.WorldToScreen(p2.X, p2.Y); + + center = GetIncenter(p0, p1, p2); + center.X -= 5; + center.Y -= 5; + + g.DrawString(tri.ID.ToString(), fontTri, Triangle, center); + } + } + + private void RenderEdges(Graphics g) + { + PointF p0, p1; + + var edges = mesh.Edges; + + // Draw edges + foreach (var edge in edges) + { + p0 = points[edge.P0]; + p1 = points[edge.P1]; + + p0 = zoom.WorldToScreen(p0.X, p0.Y); + p1 = zoom.WorldToScreen(p1.X, p1.Y); + + g.DrawLine(Line, p0, p1); + } + } + + private void RenderSegments(Graphics g) + { + PointF p0, p1; + + var segments = mesh.Segments; + + foreach (var seg in segments) + { + p0 = points[seg.P0]; + p1 = points[seg.P1]; + + p0 = zoom.WorldToScreen(p0.X, p0.Y); + p1 = zoom.WorldToScreen(p1.X, p1.Y); + + g.DrawLine(Segment, p0, p1); + } + } + + private void RenderSelectedEdge(Graphics g) + { + if (selection != null) + { + PointF p0, p1; + + p0 = points[selection.Org().ID]; + p1 = points[selection.Dest().ID]; + + p0 = zoom.WorldToScreen(p0.X, p0.Y); + p1 = zoom.WorldToScreen(p1.X, p1.Y); + + g.DrawLine(SelectedEdge, p0, p1); + } + } + + private void RenderSelectedTriangle(Graphics g) + { + if (selection != null) + { + var tri = selection.Triangle; + + var p = new PointF[3]; + + p[0] = points[tri.P0]; + p[1] = points[tri.P1]; + p[2] = points[tri.P2]; + + p[0] = zoom.WorldToScreen(p[0].X, p[0].Y); + p[1] = zoom.WorldToScreen(p[1].X, p[1].Y); + p[2] = zoom.WorldToScreen(p[2].X, p[2].Y); + + g.FillPolygon(SelectedTriangle, p); + } + } + } +} diff --git a/Triangle.NET/Triangle/Tools/QuadTree.cs b/Triangle.NET/Triangle/Tools/QuadTree.cs index 6b61549..82aa032 100644 --- a/Triangle.NET/Triangle/Tools/QuadTree.cs +++ b/Triangle.NET/Triangle/Tools/QuadTree.cs @@ -18,7 +18,7 @@ namespace TriangleNet.Tools { QuadNode root; - internal ITriangle[] triangles; + internal Dictionary triangles; internal int sizeBound; internal int maxDepth; @@ -42,7 +42,12 @@ namespace TriangleNet.Tools this.maxDepth = maxDepth; this.sizeBound = sizeBound; - triangles = mesh.Triangles.ToArray(); + triangles = new Dictionary(); + + foreach (var tri in mesh.Triangles) + { + triangles.Add(tri.id, tri); + } int currentDepth = 0; @@ -64,6 +69,7 @@ namespace TriangleNet.Tools if (IsPointInTriangle(point, tri.GetVertex(0), tri.GetVertex(1), tri.GetVertex(2))) { result.Add(tri); + break; } } @@ -160,11 +166,11 @@ namespace TriangleNet.Tools if (init) { // Allocate memory upfront - triangles.Capacity = tree.triangles.Length; + triangles.Capacity = tree.triangles.Count; - foreach (var tri in tree.triangles) + foreach (var id in tree.triangles.Keys) { - triangles.Add(tri.ID); + triangles.Add(id); } } } @@ -208,10 +214,8 @@ namespace TriangleNet.Tools Point[] triangle = new Point[3]; // Find region for every triangle vertex - foreach (var index in triangles) + foreach (var tri in tree.triangles.Values) { - ITriangle tri = tree.triangles[index]; - triangle[0] = tri.GetVertex(0); triangle[1] = tri.GetVertex(1); triangle[2] = tri.GetVertex(2);