diff --git a/src/MeshExplorer/FormTopology.cs b/src/MeshExplorer/FormTopology.cs
index af7ea42..3ec3d89 100644
--- a/src/MeshExplorer/FormTopology.cs
+++ b/src/MeshExplorer/FormTopology.cs
@@ -52,20 +52,20 @@ namespace MeshExplorer
topoControlView.SetTriangle(current.Triangle);
}
- private ITriangle FindTriangleAt(float x, float y)
+ private ITriangle FindTriangleAt(float xr, float yr)
{
// Get mesh coordinates
- var p = new System.Drawing.PointF(x, y);
- renderControl.Zoom.ScreenToWorld(ref p);
+ var p = new System.Drawing.PointF(xr, yr);
+ renderControl.Zoom.ScreenToWorld(p, out double x, out double y);
- topoControlView.SetPosition(p);
+ topoControlView.SetPosition(x, y);
if (tree == null)
{
tree = new TriangleQuadTree(mesh, 5, 2);
}
- return tree.Query(p.X, p.Y);
+ return tree.Query(x, y);
}
private void InvokePrimitive(string name)
diff --git a/src/MeshExplorer/IO/FileProcessor.cs b/src/MeshExplorer/IO/FileProcessor.cs
index eb22796..d676664 100644
--- a/src/MeshExplorer/IO/FileProcessor.cs
+++ b/src/MeshExplorer/IO/FileProcessor.cs
@@ -6,13 +6,12 @@
namespace MeshExplorer.IO
{
+ using MeshExplorer.IO.Formats;
using System;
using System.Collections.Generic;
using System.IO;
- using MeshExplorer.IO.Formats;
- using TriangleNet.IO;
- using TriangleNet.Geometry;
using TriangleNet;
+ using TriangleNet.Geometry;
///
/// Provides static methods to read and write mesh files.
diff --git a/src/MeshExplorer/Topology/TopologyControlView.cs b/src/MeshExplorer/Topology/TopologyControlView.cs
index 665af76..351684e 100644
--- a/src/MeshExplorer/Topology/TopologyControlView.cs
+++ b/src/MeshExplorer/Topology/TopologyControlView.cs
@@ -15,11 +15,11 @@ namespace MeshExplorer.Topology
InitializeComponent();
}
- public void SetPosition(PointF p)
+ public void SetPosition(double x, double y)
{
var nfi = NumberFormatInfo.InvariantInfo;
- lbPosition.Text = String.Format(nfi, "X: {0:0.0}, Y: {1:0.0}", p.X, p.Y);
+ lbPosition.Text = string.Format(nfi, "X: {0:0.0}, Y: {1:0.0}", x, y);
}
public void SetTriangle(ITriangle tri)
diff --git a/src/MeshExplorer/Topology/TopologyRenderControl.cs b/src/MeshExplorer/Topology/TopologyRenderControl.cs
index 5818118..d8090c9 100644
--- a/src/MeshExplorer/Topology/TopologyRenderControl.cs
+++ b/src/MeshExplorer/Topology/TopologyRenderControl.cs
@@ -45,15 +45,13 @@ namespace MeshExplorer.Topology
renderer = new TopologyRenderer(mesh);
zoom = new Projection(ClientRectangle);
- //zoom.ClipMargin = 10.0f;
-
zoom.Initialize(mesh.Bounds);
InitializeBuffer();
initialized = true;
- this.Render();
+ Render();
}
public void Update(Otri otri)
diff --git a/src/MeshExplorer/Topology/TopologyRenderer.cs b/src/MeshExplorer/Topology/TopologyRenderer.cs
index e2c6f4c..cf815b9 100644
--- a/src/MeshExplorer/Topology/TopologyRenderer.cs
+++ b/src/MeshExplorer/Topology/TopologyRenderer.cs
@@ -6,7 +6,7 @@ namespace MeshExplorer.Topology
using TriangleNet;
using TriangleNet.Geometry;
using TriangleNet.Rendering;
-
+
public class TopologyRenderer
{
Projection zoom;
@@ -39,9 +39,16 @@ namespace MeshExplorer.Topology
int k = 0;
+ var b = mesh.Bounds;
+
+ double s = 1d / Math.Max(b.Width, b.Height);
+ double dx = b.X;
+ double dy = b.Y;
+
foreach (var v in mesh.Vertices)
{
- points[k++] = new PointF((float)v.X, (float)v.Y);
+ // Normalized device coordinates.
+ points[k++] = new PointF((float)((v.X - dx) * s), (float)((v.Y - dy) * s));
}
font = new Font("Arial", 7.5f);
@@ -146,7 +153,7 @@ namespace MeshExplorer.Topology
var brush = i == id ? Brushes.DarkRed : Point;
pt = points[i];
- zoom.WorldToScreen(ref pt);
+ zoom.NdcToScreen(ref pt);
g.FillEllipse(brush, pt.X - 10f, pt.Y - 10f, 20, 20);
pt.X -= i > 9 ? 7 : 4;
@@ -168,9 +175,9 @@ namespace MeshExplorer.Topology
p1 = points[tri.GetVertexID(1)];
p2 = points[tri.GetVertexID(2)];
- zoom.WorldToScreen(ref p0);
- zoom.WorldToScreen(ref p1);
- zoom.WorldToScreen(ref p2);
+ zoom.NdcToScreen(ref p0);
+ zoom.NdcToScreen(ref p1);
+ zoom.NdcToScreen(ref p2);
g.DrawLine(Line, p0, p1);
g.DrawLine(Line, p1, p2);
@@ -197,9 +204,9 @@ namespace MeshExplorer.Topology
p1 = points[tri.GetVertexID(1)];
p2 = points[tri.GetVertexID(2)];
- zoom.WorldToScreen(ref p0);
- zoom.WorldToScreen(ref p1);
- zoom.WorldToScreen(ref p2);
+ zoom.NdcToScreen(ref p0);
+ zoom.NdcToScreen(ref p1);
+ zoom.NdcToScreen(ref p2);
center = GetIncenter(p0, p1, p2);
center.X -= 5;
@@ -221,8 +228,8 @@ namespace MeshExplorer.Topology
p0 = points[edge.P0];
p1 = points[edge.P1];
- zoom.WorldToScreen(ref p0);
- zoom.WorldToScreen(ref p1);
+ zoom.NdcToScreen(ref p0);
+ zoom.NdcToScreen(ref p1);
g.DrawLine(Line, p0, p1);
}
@@ -239,8 +246,8 @@ namespace MeshExplorer.Topology
p0 = points[seg.P0];
p1 = points[seg.P1];
- zoom.WorldToScreen(ref p0);
- zoom.WorldToScreen(ref p1);
+ zoom.NdcToScreen(ref p0);
+ zoom.NdcToScreen(ref p1);
g.DrawLine(Segment, p0, p1);
}
@@ -255,8 +262,8 @@ namespace MeshExplorer.Topology
p0 = points[currentOrg.ID];
p1 = points[currentDest.ID];
- zoom.WorldToScreen(ref p0);
- zoom.WorldToScreen(ref p1);
+ zoom.NdcToScreen(ref p0);
+ zoom.NdcToScreen(ref p1);
g.DrawLine(SelectedEdge, p0, p1);
}
@@ -272,9 +279,9 @@ namespace MeshExplorer.Topology
p[1] = points[currentTri.GetVertexID(1)];
p[2] = points[currentTri.GetVertexID(2)];
- zoom.WorldToScreen(ref p[0]);
- zoom.WorldToScreen(ref p[1]);
- zoom.WorldToScreen(ref p[2]);
+ zoom.NdcToScreen(ref p[0]);
+ zoom.NdcToScreen(ref p[1]);
+ zoom.NdcToScreen(ref p[2]);
g.FillPolygon(SelectedTriangle, p);
}
diff --git a/src/Triangle.Rendering/Buffer/IndexBuffer.cs b/src/Triangle.Rendering/Buffer/IndexBuffer.cs
index 108d8a7..a9a2152 100644
--- a/src/Triangle.Rendering/Buffer/IndexBuffer.cs
+++ b/src/Triangle.Rendering/Buffer/IndexBuffer.cs
@@ -1,11 +1,11 @@
+using System.Collections.Generic;
+using System.Linq;
+using TriangleNet.Geometry;
+using TriangleNet.Topology;
+
namespace TriangleNet.Rendering.Buffer
{
- using System.Collections.Generic;
- using System.Linq;
- using TriangleNet.Geometry;
- using TriangleNet.Topology;
-
public class IndexBuffer : BufferBase
{
#region Static methods
diff --git a/src/Triangle.Rendering/Buffer/VertexBuffer.cs b/src/Triangle.Rendering/Buffer/VertexBuffer.cs
index 3dcb1f7..b8b8815 100644
--- a/src/Triangle.Rendering/Buffer/VertexBuffer.cs
+++ b/src/Triangle.Rendering/Buffer/VertexBuffer.cs
@@ -1,9 +1,10 @@
+using System;
+using System.Collections.Generic;
+using TriangleNet.Geometry;
+
namespace TriangleNet.Rendering.Buffer
{
- using System.Collections.Generic;
- using TriangleNet.Geometry;
-
public class VertexBuffer : BufferBase
{
#region Static methods
@@ -12,25 +13,40 @@ namespace TriangleNet.Rendering.Buffer
/// Create a vertex buffer from given point collection.
///
/// The points to render.
- /// The points bounding box.
- ///
+ /// Returns the vertex buffer.
+ public static IBuffer Create(ICollection points)
+ {
+ return Create(points, new Rectangle(0d, 0d, 1d, 1d));
+ }
+
+ ///
+ /// Create a normalized vertex buffer from given point collection.
+ ///
+ /// The points to render.
+ /// The bounding box used for normalization.
+ /// Returns a buffer of normalized coordinates.
public static IBuffer Create(ICollection points, Rectangle bounds)
{
var buffer = new VertexBuffer(2 * points.Count);
var data = buffer.Data;
- float x, y;
+ double dx = bounds.X;
+ double dy = bounds.Y;
+
+ double scale = 1.0 / Math.Max(bounds.Width, bounds.Height);
int i = 0;
+ double x, y;
+
foreach (var p in points)
{
- x = (float)p.X;
- y = (float)p.Y;
+ x = (p.X - dx) * scale;
+ y = (p.Y - dy) * scale;
- data[2 * i] = x;
- data[2 * i + 1] = y;
+ data[2 * i] = (float)x;
+ data[2 * i + 1] = (float)y;
i++;
}
@@ -39,23 +55,43 @@ namespace TriangleNet.Rendering.Buffer
}
///
- /// Create a vertex buffer from given vertex collection.
+ /// Create a vertex buffer from given point collection.
+ ///
+ /// The points to render.
+ /// Returns the vertex buffer.
+ public static IBuffer Create(ICollection points)
+ {
+ return Create(points, new Rectangle(0d, 0d, 1d, 1d));
+ }
+
+ ///
+ /// Create a normalized vertex buffer from given vertex collection.
///
/// The vertices to render.
- /// The vertices bounding box.
- ///
+ /// The bounding box used for normalization.
+ /// Returns a buffer of normalized coordinates.
public static IBuffer Create(ICollection points, Rectangle bounds)
{
var buffer = new VertexBuffer(2 * points.Count);
var data = buffer.Data;
+ double dx = bounds.X;
+ double dy = bounds.Y;
+
+ double scale = 1.0 / Math.Max(bounds.Width, bounds.Height);
+
int i = 0;
+ double x, y;
+
foreach (var p in points)
{
- data[2 * i] = (float)p.X;
- data[2 * i + 1] = (float)p.Y;
+ x = (p.X - dx) * scale;
+ y = (p.Y - dy) * scale;
+
+ data[2 * i] = (float)x;
+ data[2 * i + 1] = (float)y;
i++;
}
diff --git a/src/Triangle.Rendering/GDI/FunctionRenderer.cs b/src/Triangle.Rendering/GDI/FunctionRenderer.cs
index 24f0c08..2af4cac 100644
--- a/src/Triangle.Rendering/GDI/FunctionRenderer.cs
+++ b/src/Triangle.Rendering/GDI/FunctionRenderer.cs
@@ -48,7 +48,7 @@ namespace TriangleNet.Rendering.GDI
Color color;
PointF p = new PointF((float)data[0], (float)data[1]);
- zoom.WorldToScreen(ref p);
+ zoom.NdcToScreen(ref p);
// Get correction distance
float dx = (p.X - (int)p.X) * 2.0f;
@@ -60,7 +60,7 @@ namespace TriangleNet.Rendering.GDI
p.X = (float)data[size * i];
p.Y = (float)data[size * i + 1];
- zoom.WorldToScreen(ref p);
+ zoom.NdcToScreen(ref p);
color = colors[i];
diff --git a/src/Triangle.Rendering/GDI/MeshRenderer.cs b/src/Triangle.Rendering/GDI/MeshRenderer.cs
index df19ca5..f630cee 100644
--- a/src/Triangle.Rendering/GDI/MeshRenderer.cs
+++ b/src/Triangle.Rendering/GDI/MeshRenderer.cs
@@ -58,7 +58,7 @@ namespace TriangleNet.Rendering.GDI
if (zoom.Viewport.Contains(p))
{
- zoom.WorldToScreen(ref p);
+ zoom.NdcToScreen(ref p);
g.FillEllipse(brush, p.X - 1.5f, p.Y - 1.5f, 3, 3);
}
}
@@ -109,9 +109,9 @@ namespace TriangleNet.Rendering.GDI
if (zoom.Viewport.Intersects(tri[0], tri[1], tri[2]))
{
- zoom.WorldToScreen(ref tri[0]);
- zoom.WorldToScreen(ref tri[1]);
- zoom.WorldToScreen(ref tri[2]);
+ zoom.NdcToScreen(ref tri[0]);
+ zoom.NdcToScreen(ref tri[1]);
+ zoom.NdcToScreen(ref tri[2]);
if (filled)
{
@@ -162,8 +162,8 @@ namespace TriangleNet.Rendering.GDI
if (zoom.Viewport.Intersects(p0, p1))
{
- zoom.WorldToScreen(ref p0);
- zoom.WorldToScreen(ref p1);
+ zoom.NdcToScreen(ref p0);
+ zoom.NdcToScreen(ref p1);
g.DrawLine(pen, p0, p1);
}
diff --git a/src/Triangle.Rendering/Projection.cs b/src/Triangle.Rendering/Projection.cs
index 5ed8f5f..090e199 100644
--- a/src/Triangle.Rendering/Projection.cs
+++ b/src/Triangle.Rendering/Projection.cs
@@ -1,6 +1,6 @@
// -----------------------------------------------------------------------
//
-// TODO: Update copyright text.
+// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
//
// -----------------------------------------------------------------------
@@ -9,74 +9,95 @@ namespace TriangleNet.Rendering
using System;
using System.Drawing;
+ using TRectangle = Geometry.Rectangle;
+
///
/// Manages a world to screen transformation (2D orthographic projection).
///
+ ///
+ ///
+ /// The projection implementation is actually not world-to-screen, but NDC-to-screen
+ /// (Normalized-Device-Coordinates). NDC here is - in contrast for example to OpenGL, the
+ /// transformation of world coordinates to a unit rectangle with origin (0,0) and a max
+ /// side length 1 (the width/height ratio is preserved). It's a simple translate-scale
+ /// transform, which is automatically applied in VertexBuffer.Create(points, bounds).
+ ///
+ ///
+ /// Since the upper-left corner of the display is usually the screen coordinate origin
+ /// (0,0), the project will automatically invert the y-axis.
+ ///
+ ///
public class Projection
{
- // The screen.
+ // The original mesh bounds (needed for screen-to-world projection).
+ TRectangle world_;
+
+ // Precomputed scaling factor for normalized coordinates.
+ double scale_;
+
+ // The screen dimensions.
Rectangle screen;
- // The complete mesh.
- RectangleF world;
-
- RectangleF viewport;
+ // The original mesh and the viewport in normalized coordinates.
+ RectangleF world, viewport;
///
- /// Gets or sets the current viewport (visible mesh).
+ /// Gets or sets the current viewport (normalized coordinates).
///
public RectangleF Viewport => viewport;
- ///
- /// Gets the current scale.
- ///
- public float Scale => screen.Width / viewport.Width;
-
///
/// Gets the zoom level.
///
public int Level { get; private set; }
- // The y-direction of windows screen coordinates is upside down,
- // so invertY should be set to true.
- bool invertY;
+ private const int MAX_ZOOM = 100;
- const int maxZoomLevel = 100;
-
- public Projection(Rectangle screen, bool invertY = true)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The current screen (viewport) dimensions.
+ public Projection(Rectangle screen)
{
this.screen = screen;
- this.invertY = invertY;
- world = viewport = screen;
+ world = viewport = new RectangleF(screen.X, screen.Y, screen.Width, screen.Height);
Level = 1;
}
///
- /// Inititialize the projection.
+ /// Initialize the projection.
///
- /// The world that should be transformed to screen coordinates.
- public void Initialize(Geometry.Rectangle world)
+ /// The world that should be transformed to screen coordinates.
+ public void Initialize(TRectangle world)
{
Level = 1;
- float ww = (float)world.Width;
- float wh = (float)world.Height;
+ // Bounding box of original (non-normalized) coordinates.
+ world_ = world;
+
+ // Scaling factor for normalized coordinates.
+ scale_ = Math.Max(world.Width, world.Height);
+
+ // Dimensions in normalized coordinates.
+ float ww = (float)(world.Width / scale_);
+ float wh = (float)(world.Height / scale_);
+
+ // Add a margin so there's some space around the screen borders.
+ float margin = (ww < wh) ? wh * 0.05f : ww * 0.05f;
int sw = screen.Width;
int sh = screen.Height;
- // Add a margin so there's some space around the border
- float margin = (ww < wh) ? wh * 0.05f : ww * 0.05f;
-
float wRatio = ww / wh;
float sRatio = sw / (float)sh;
- float scale = (sRatio < wRatio) ? (ww + margin) / sw : (wh + margin) / sh;
+ float scale = (sRatio < wRatio) ? (ww + margin) / sw : (wh + margin) / sh;
- float centerX = (float)world.X + ww / 2;
- float centerY = (float)world.Y + wh / 2;
+ // Center in normalized coordinates (left = bottom = 0)
+ float centerX = ww / 2;
+ float centerY = wh / 2;
// Get the initial viewport (complete mesh centered on the screen)
this.world = viewport = new RectangleF(
@@ -87,9 +108,9 @@ namespace TriangleNet.Rendering
}
///
- /// Handle resize of the screen.
+ /// Handle resize of the screen (viewport).
///
- /// The new screen dimensions.
+ /// The new screen (viewport) dimensions.
public void Resize(Rectangle newScreen)
{
// The viewport has to be updated, but we want to keep
@@ -149,25 +170,23 @@ namespace TriangleNet.Rendering
///
/// Zoom in or out of the viewport.
///
- /// Zoom amount
- /// Relative x point position
- /// Relative y point position
+ /// Zoom amount.
+ /// Relative x point position (in [0..1] range).
+ /// Relative y point position (in [0..1] range).
public bool Zoom(int amount, float focusX, float focusY)
{
float width, height;
- if (invertY)
- {
- focusY = 1 - focusY;
- }
+ // Invert y coordinate.
+ focusY = 1 - focusY;
if (amount > 0) // Zoom in
{
Level++;
- if (Level > maxZoomLevel)
+ if (Level > MAX_ZOOM)
{
- Level = maxZoomLevel;
+ Level = MAX_ZOOM;
return false;
}
@@ -221,22 +240,40 @@ namespace TriangleNet.Rendering
return true;
}
+ ///
+ /// Reset the zoom to initial state.
+ ///
public void Reset()
{
viewport = world;
Level = 1;
}
- public void WorldToScreen(ref PointF pt)
+ ///
+ /// Project a normalized device coordinate to screen coordinates.
+ ///
+ /// Input normalized device coordinate, output screen coordinate.
+ public void NdcToScreen(ref PointF pt)
{
pt.X = (pt.X - viewport.X) / viewport.Width * screen.Width;
pt.Y = (1 - (pt.Y - viewport.Y) / viewport.Height) * screen.Height;
}
+ ///
+ /// Project a screen coordinate to world coordinates.
+ ///
+ /// Normalized position on screen (both coordinates in [0..1] range).
+ /// The world x-coordinate.
+ /// The world y-coordinate.
public void ScreenToWorld(PointF pt, out double x, out double y)
{
- x = viewport.X + viewport.Width * pt.X;
- y = viewport.Y + viewport.Height * (1 - pt.Y);
+ // Position in normalized coordinates.
+ var nx = viewport.X + viewport.Width * pt.X;
+ var ny = viewport.Y + viewport.Height * (1 - pt.Y);
+
+ // Translate and scale to world coordinates.
+ x = world_.X + nx * scale_;
+ y = world_.Y + ny * scale_;
}
}
}