Triangle.Rendering projection expects normalized device coordinates now.
This should allow correct rendering of meshes that use more than 32bit float precision for their coordinates (no more unchecked cast from double to float).
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Provides static methods to read and write mesh files.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<int>
|
||||
{
|
||||
#region Static methods
|
||||
|
||||
@@ -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<float>
|
||||
{
|
||||
#region Static methods
|
||||
@@ -12,25 +13,40 @@ namespace TriangleNet.Rendering.Buffer
|
||||
/// Create a vertex buffer from given point collection.
|
||||
/// </summary>
|
||||
/// <param name="points">The points to render.</param>
|
||||
/// <param name="bounds">The points bounding box.</param>
|
||||
/// <returns></returns>
|
||||
/// <returns>Returns the vertex buffer.</returns>
|
||||
public static IBuffer<float> Create(ICollection<Point> points)
|
||||
{
|
||||
return Create(points, new Rectangle(0d, 0d, 1d, 1d));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a normalized vertex buffer from given point collection.
|
||||
/// </summary>
|
||||
/// <param name="points">The points to render.</param>
|
||||
/// <param name="bounds">The bounding box used for normalization.</param>
|
||||
/// <returns>Returns a buffer of normalized coordinates.</returns>
|
||||
public static IBuffer<float> Create(ICollection<Point> 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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a vertex buffer from given vertex collection.
|
||||
/// Create a vertex buffer from given point collection.
|
||||
/// </summary>
|
||||
/// <param name="points">The points to render.</param>
|
||||
/// <returns>Returns the vertex buffer.</returns>
|
||||
public static IBuffer<float> Create(ICollection<Vertex> points)
|
||||
{
|
||||
return Create(points, new Rectangle(0d, 0d, 1d, 1d));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a normalized vertex buffer from given vertex collection.
|
||||
/// </summary>
|
||||
/// <param name="points">The vertices to render.</param>
|
||||
/// <param name="bounds">The vertices bounding box.</param>
|
||||
/// <returns></returns>
|
||||
/// <param name="bounds">The bounding box used for normalization.</param>
|
||||
/// <returns>Returns a buffer of normalized coordinates.</returns>
|
||||
public static IBuffer<float> Create(ICollection<Vertex> 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++;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Projection.cs" company="">
|
||||
// TODO: Update copyright text.
|
||||
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@@ -9,74 +9,95 @@ namespace TriangleNet.Rendering
|
||||
using System;
|
||||
using System.Drawing;
|
||||
|
||||
using TRectangle = Geometry.Rectangle;
|
||||
|
||||
/// <summary>
|
||||
/// Manages a world to screen transformation (2D orthographic projection).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// 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 <c>VertexBuffer.Create(points, bounds)</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Since the upper-left corner of the display is usually the screen coordinate origin
|
||||
/// (0,0), the project will automatically invert the y-axis.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current viewport (visible mesh).
|
||||
/// Gets or sets the current viewport (normalized coordinates).
|
||||
/// </summary>
|
||||
public RectangleF Viewport => viewport;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current scale.
|
||||
/// </summary>
|
||||
public float Scale => screen.Width / viewport.Width;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the zoom level.
|
||||
/// </summary>
|
||||
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)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Projection"/> class.
|
||||
/// </summary>
|
||||
/// <param name="screen">The current screen (viewport) dimensions.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inititialize the projection.
|
||||
/// Initialize the projection.
|
||||
/// </summary>
|
||||
/// <param name="world">The world that should be transformed to screen coordinates.</param>
|
||||
public void Initialize(Geometry.Rectangle world)
|
||||
/// <param name="nworld">The world that should be transformed to screen coordinates.</param>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle resize of the screen.
|
||||
/// Handle resize of the screen (viewport).
|
||||
/// </summary>
|
||||
/// <param name="newScreen">The new screen dimensions.</param>
|
||||
/// <param name="newScreen">The new screen (viewport) dimensions.</param>
|
||||
public void Resize(Rectangle newScreen)
|
||||
{
|
||||
// The viewport has to be updated, but we want to keep
|
||||
@@ -149,25 +170,23 @@ namespace TriangleNet.Rendering
|
||||
/// <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>
|
||||
/// <param name="amount">Zoom amount.</param>
|
||||
/// <param name="focusX">Relative x point position (in [0..1] range).</param>
|
||||
/// <param name="focusY">Relative y point position (in [0..1] range).</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the zoom to initial state.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
viewport = world;
|
||||
Level = 1;
|
||||
}
|
||||
|
||||
public void WorldToScreen(ref PointF pt)
|
||||
/// <summary>
|
||||
/// Project a normalized device coordinate to screen coordinates.
|
||||
/// </summary>
|
||||
/// <param name="pt">Input normalized device coordinate, output screen coordinate.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Project a screen coordinate to world coordinates.
|
||||
/// </summary>
|
||||
/// <param name="pt">Normalized position on screen (both coordinates in [0..1] range).</param>
|
||||
/// <param name="x">The world x-coordinate.</param>
|
||||
/// <param name="y">The world y-coordinate.</param>
|
||||
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_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user