Move Windows-specific projects into separate solution.

This commit is contained in:
wo80
2022-03-05 01:01:39 +01:00
parent 11e42c98f4
commit 23c2682c6e
23 changed files with 161 additions and 158 deletions
@@ -0,0 +1,26 @@
namespace TriangleNet.Rendering
{
using System.Drawing;
internal static class ExtensionMethods
{
/// <summary>
/// Check if segment (a, b) intersects rectangle.
/// </summary>
public static bool Intersects(this RectangleF rect, PointF a, PointF b)
{
// TODO: implement intersection.
return rect.Contains(a) || rect.Contains(b);
}
/// <summary>
/// Check if triangle (a, b, c) intersects rectangle.
/// </summary>
public static bool Intersects(this RectangleF rect, PointF a, PointF b, PointF c)
{
// TODO: implement intersection.
return rect.Contains(a) || rect.Contains(b) || rect.Contains(c);
}
}
}
@@ -0,0 +1,101 @@
namespace TriangleNet.Rendering.GDI
{
using System;
using System.Drawing;
using TriangleNet.Rendering.GDI.Native;
public class FunctionRenderer
{
TriVertex[] points;
GradientTriangle[] elements;
public Graphics RenderTarget { get; set; }
public IRenderContext Context { get; set; }
public void Render(IRenderLayer layer)
{
Create(layer);
var hdc = RenderTarget.GetHdc();
NativeMethods.GradientFill(hdc,
points, (uint)points.Length, elements, (uint)elements.Length,
GradientFillMode.GRADIENT_FILL_TRIANGLE);
RenderTarget.ReleaseHdc(hdc);
}
private void Create(IRenderLayer layer)
{
var zoom = Context.Zoom;
var colors = layer.Colors.Data;
int length = colors.Length;
int size = layer.Points.Size;
var data = layer.Points.Data;
if (length != data.Length / size)
{
throw new Exception();
}
this.points = new TriVertex[length];
TriVertex vertex;
Color color;
PointF p = new PointF((float)data[0], (float)data[1]);
zoom.NdcToScreen(ref p);
// Get correction distance
float dx = (p.X - (int)p.X) * 2.0f;
float dy = (p.Y - (int)p.Y) * 2.0f;
// Create vertices.
for (int i = 0; i < length; i++)
{
p.X = (float)data[size * i];
p.Y = (float)data[size * i + 1];
zoom.NdcToScreen(ref p);
color = colors[i];
vertex = new TriVertex();
vertex.x = (int)(p.X + dx);
vertex.y = (int)(p.Y + dy);
vertex.Red = (ushort)(color.R << 8);
vertex.Green = (ushort)(color.G << 8);
vertex.Blue = (ushort)(color.B << 8);
vertex.Alpha = (ushort)(color.A << 8);
this.points[i] = vertex;
}
var triangles = layer.Indices.Data;
length = triangles.Length / 3;
this.elements = new GradientTriangle[length];
GradientTriangle e;
// Create triangles.
for (int i = 0; i < length; i++)
{
e = new GradientTriangle();
e.Vertex1 = (uint)triangles[3 * i];
e.Vertex2 = (uint)triangles[3 * i + 1];
e.Vertex3 = (uint)triangles[3 * i + 2];
this.elements[i] = e;
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Drawing;
namespace TriangleNet.Rendering.GDI
{
internal class Helper
{
public static void Dispose(Dictionary<int, SolidBrush> brushes)
{
foreach (var brush in brushes.Values)
{
brush.Dispose();
}
}
public static Dictionary<int, SolidBrush> GetBrushDictionary(Dictionary<int, Color> ColorDictionary)
{
var brushes = new Dictionary<int, SolidBrush>();
foreach (var item in ColorDictionary)
{
brushes.Add(item.Key, new SolidBrush(item.Value));
}
return brushes;
}
}
}
+287
View File
@@ -0,0 +1,287 @@
namespace TriangleNet.Rendering.GDI
{
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using TriangleNet.Meshing;
/// <summary>
/// Enables rendering of polygons or meshes to a bitmap.
/// </summary>
public class ImageRenderer
{
ColorManager colors = LightScheme();
public ColorManager ColorScheme
{
get { return colors; }
set { colors = value; }
}
public bool EnableRegions { get; set; }
public bool EnablePoints { get; set; }
/// <summary>
/// Exports a polygon to PNG format.
/// </summary>
/// <param name="poly">The polygon.</param>
/// <param name="width">The desired width (pixel) of the image.</param>
/// <param name="file">The PNG filename.</param>
/// <param name="regions">Enable rendering of regions.</param>
/// <param name="points">Enable rendering of points.</param>
public static void Save(Geometry.IPolygon poly, string file = null, int width = 800,
bool points = true)
{
// Check file name
if (string.IsNullOrWhiteSpace(file))
{
file = string.Format("poly-{0}.png", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss"));
}
// Ensure .png extension.
if (file.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
{
Path.ChangeExtension(file, ".png");
}
var renderer = new ImageRenderer();
renderer.EnableRegions = false;
renderer.EnablePoints = points;
var bitmap = renderer.Render(poly, width);
bitmap.Save(file, ImageFormat.Png);
}
/// <summary>
/// Exports a mesh to PNG format.
/// </summary>
/// <param name="mesh">The mesh.</param>
/// <param name="width">The desired width (pixel) of the image.</param>
/// <param name="file">The PNG filename.</param>
/// <param name="regions">Enable rendering of regions.</param>
/// <param name="points">Enable rendering of points.</param>
public static void Save(IMesh mesh, string file = null, int width = 800,
bool regions = false, bool points = true)
{
// Check file name
if (string.IsNullOrWhiteSpace(file))
{
file = string.Format("mesh-{0}.png", DateTime.Now.ToString("yyyy-M-d-hh-mm-ss"));
}
// Ensure .png extension.
if (!file.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
{
Path.ChangeExtension(file, ".png");
}
var renderer = new ImageRenderer();
renderer.EnableRegions = regions;
renderer.EnablePoints = points;
var bitmap = renderer.Render(mesh, width);
bitmap.Save(file, ImageFormat.Png);
}
/// <summary>
/// Renders the polygon to a bitmap.
/// </summary>
/// <param name="poly">The polygon.</param>
/// <param name="width">The desired width (pixel) of the image.</param>
/// <returns>The bitmap.</returns>
/// <remarks>
/// The width has to be at least 2 * sqrt(n), n the number of vertices.
/// Otherwise, an empty bitmap
/// </remarks>
public Bitmap Render(Geometry.IPolygon poly, int width = 800)
{
Bitmap bitmap;
// Check if the specified width is reasonable
if (width < 2 * Math.Sqrt(poly.Points.Count))
{
return new Bitmap(1, 1);
}
var bounds = poly.Bounds();
// World margin on each side
float margin = (float)bounds.Height * 0.05f;
float scale = width / ((float)bounds.Width + 2 * margin);
var target = new Rectangle(0, 0, width, (int)((bounds.Height + 2 * margin) * scale));
bitmap = new Bitmap(width, target.Height, PixelFormat.Format32bppPArgb);
using (var g = Graphics.FromImage(bitmap))
{
g.Clear(colors.Background);
g.SmoothingMode = SmoothingMode.HighQuality;
var context = new RenderContext(new Projection(target), colors);
context.Add(poly);
if (!EnablePoints)
{
context.Enable(3, false);
}
var renderer = new LayerRenderer();
renderer.Context = context;
renderer.RenderTarget = g;
renderer.Render();
}
return bitmap;
}
/// <summary>
/// Renders the mesh to a bitmap.
/// </summary>
/// <param name="mesh">The current mesh.</param>
/// <param name="width">The desired width (pixel) of the image.</param>
/// <returns>The bitmap.</returns>
/// <remarks>
/// The width has to be at least 2 * sqrt(n), n the number of vertices.
/// Otherwise, an empty bitmap
/// </remarks>
public Bitmap Render(IMesh mesh, int width = 800)
{
Bitmap bitmap;
// Check if the specified width is reasonable
if (width < 2 * Math.Sqrt(mesh.Vertices.Count))
{
return new Bitmap(1, 1);
}
var bounds = mesh.Bounds;
// World margin on each side
float margin = (float)bounds.Height * 0.05f;
float scale = width / ((float)bounds.Width + 2 * margin);
var target = new Rectangle(0, 0, width, (int)((bounds.Height + 2 * margin) * scale));
bitmap = new Bitmap(width, target.Height, PixelFormat.Format32bppPArgb);
using (var g = Graphics.FromImage(bitmap))
{
g.Clear(colors.Background);
g.SmoothingMode = SmoothingMode.HighQuality;
var context = new RenderContext(new Projection(target), colors);
context.Add(mesh, true);
if (EnableRegions)
{
context.Add(GetRegions(mesh));
}
if (!EnablePoints)
{
context.Enable(3, false);
}
var renderer = new LayerRenderer();
renderer.Context = context;
renderer.RenderTarget = g;
renderer.Render();
}
return bitmap;
}
public Bitmap Render(IMesh mesh, Topology.DCEL.DcelMesh dcel, int width = 800)
{
Bitmap bitmap;
// Check if the specified width is reasonable
if (width < 2 * Math.Sqrt(mesh.Vertices.Count))
{
return new Bitmap(1, 1);
}
var bounds = mesh.Bounds;
// World margin on each side
float margin = (float)bounds.Height * 0.05f;
float scale = width / ((float)bounds.Width + 2 * margin);
var target = new Rectangle(0, 0, width, (int)((bounds.Height + 2 * margin) * scale));
bitmap = new Bitmap(width, target.Height, PixelFormat.Format32bppPArgb);
using (var g = Graphics.FromImage(bitmap))
{
g.Clear(colors.Background);
g.SmoothingMode = SmoothingMode.HighQuality;
var context = new RenderContext(new Projection(target), colors);
context.Add(mesh, true);
context.Add(dcel.Vertices.ToArray(), dcel.Edges, false);
if (EnableRegions)
{
context.Add(GetRegions(mesh));
}
if (!EnablePoints)
{
context.Enable(3, false);
}
var renderer = new LayerRenderer();
renderer.Context = context;
renderer.RenderTarget = g;
renderer.Render();
}
return bitmap;
}
private int[] GetRegions(IMesh mesh)
{
mesh.Renumber();
var labels = new int[mesh.Triangles.Count];
var regions = new SortedSet<int>();
foreach (var t in mesh.Triangles)
{
labels[t.ID] = t.Label;
regions.Add(t.Label);
}
if (colors.ColorDictionary == null)
{
colors.CreateColorDictionary(regions);
}
return labels;
}
public static ColorManager LightScheme()
{
var colors = new ColorManager();
colors.Background = Color.White;
colors.Point = Color.FromArgb(60, 80, 120);
colors.SteinerPoint = Color.DarkGreen;
colors.Line = Color.FromArgb(200, 200, 200);
colors.Segment = Color.SteelBlue;
colors.VoronoiLine = Color.FromArgb(160, 170, 180);
return colors;
}
}
}
+124
View File
@@ -0,0 +1,124 @@
namespace TriangleNet.Rendering.GDI
{
using System.Drawing;
public class LayerRenderer : IRenderer
{
MeshRenderer meshRenderer;
FunctionRenderer functionRenderer;
public IRenderContext Context { get; set; }
public Graphics RenderTarget { get; set; }
public LayerRenderer()
{
meshRenderer = new MeshRenderer();
functionRenderer = new FunctionRenderer();
}
public void Render()
{
meshRenderer.Context = Context;
meshRenderer.RenderTarget = RenderTarget;
functionRenderer.Context = Context;
functionRenderer.RenderTarget = RenderTarget;
// 0 = mesh (filled)
// 1 = mesh (wireframe)
// 2 = polygon
// 3 = points
// 4 = voronoi overlay
// 5 = vector field
// 6 = contour lines
int i = 0;
foreach (var layer in this.Context.RenderLayers)
{
if (!layer.IsEmpty() && layer.IsEnabled)
{
switch (i)
{
case 0:
RenderFilledMesh(layer);
break;
case 1:
RenderMesh(layer);
break;
case 2:
RenderPolygon(layer);
break;
case 3:
RenderPoints(layer);
break;
case 4:
RenderVoronoi(layer);
break;
case 5:
case 6:
default:
break;
}
}
i++;
}
}
private void RenderFilledMesh(IRenderLayer layer)
{
if (layer.Partition != null)
{
meshRenderer.RenderElements(layer.Points.Data, layer.Indices.Data, 3, layer.Partition.Data);
}
else if (layer.Colors != null)
{
functionRenderer.Render(layer);
}
}
private void RenderMesh(IRenderLayer layer)
{
if (layer.Indices.Size == 3)
{
meshRenderer.RenderElements(layer.Points.Data, layer.Indices.Data, 3, null);
}
else
{
using var pen = new Pen(Context.ColorManager.Line);
meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, pen);
}
}
private void RenderPolygon(IRenderLayer layer)
{
using var pen = new Pen(Context.ColorManager.Segment);
meshRenderer.RenderSegments(layer.Points.Data, layer.Indices.Data, pen);
}
private void RenderPoints(IRenderLayer layer)
{
meshRenderer.RenderPoints(layer.Points.Data, layer.Points.Size, layer.Count);
}
private void RenderVoronoi(IRenderLayer layer)
{
if (RenderManager.VORONOI_DEBUG)
{
meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, Pens.Purple);
meshRenderer.RenderPoints(layer.Points.Data, layer.Points.Size, 0, layer.Count, Brushes.Red);
}
else
{
using var pen = new Pen(Context.ColorManager.VoronoiLine);
meshRenderer.RenderEdges(layer.Points.Data, layer.Indices.Data, pen);
}
}
}
}
+175
View File
@@ -0,0 +1,175 @@
// -----------------------------------------------------------------------
// <copyright file="MeshRenderer.cs" company="">
// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Rendering.GDI
{
using System.Drawing;
/// <summary>
/// Renders a mesh.
/// </summary>
public class MeshRenderer
{
/// <summary>
/// Initializes a new instance of the <see cref="MeshRenderer" /> class.
/// </summary>
public MeshRenderer()
{
}
public Graphics RenderTarget { get; set; }
public IRenderContext Context { get; set; }
public void RenderPoints(float[] points, int size, int limit = 0)
{
int n = points.Length / size;
int m = limit > 0 ? limit : n;
using var Point = new SolidBrush(Context.ColorManager.Point);
using var SteinerPoint = new SolidBrush(Context.ColorManager.SteinerPoint);
// Draw unchanged points
RenderPoints(points, size, 0, m, Point);
// Draw new (Steiner) points
if (limit > 0)
{
RenderPoints(points, size, m, n, SteinerPoint);
}
}
public void RenderPoints(float[] points, int size, int start, int end, Brush brush)
{
var g = this.RenderTarget;
var zoom = this.Context.Zoom;
int i, k, n = points.Length / size;
PointF p = new PointF();
// Render points
for (i = start; i < end; i++)
{
k = size * i;
p.X = points[k];
p.Y = points[k + 1];
if (zoom.Viewport.Contains(p))
{
zoom.NdcToScreen(ref p);
g.FillEllipse(brush, p.X - 1.5f, p.Y - 1.5f, 3, 3);
}
}
}
public void RenderSegments(float[] points, int[] indices, Pen pen)
{
RenderLines(points, indices, pen);
}
public void RenderEdges(float[] points, int[] indices, Pen pen)
{
RenderLines(points, indices, pen);
}
public void RenderElements(float[] points, int[] indices, int size, int[] partition)
{
var g = this.RenderTarget;
var zoom = this.Context.Zoom;
int n = indices.Length / size;
int k0, k1, k2;
var tri = new PointF[size];
bool filled = partition != null;
var brushes = filled ? Helper.GetBrushDictionary(Context.ColorManager.ColorDictionary) : null;
// TODO: remove hard-coded color
var pen = new Pen(Color.FromArgb(20, 20, 20));
// Draw triangles
for (int i = 0; i < n; i++)
{
k0 = 2 * indices[3 * i];
k1 = 2 * indices[3 * i + 1];
k2 = 2 * indices[3 * i + 2];
tri[0].X = points[k0];
tri[0].Y = points[k0 + 1];
tri[1].X = points[k1];
tri[1].Y = points[k1 + 1];
tri[2].X = points[k2];
tri[2].Y = points[k2 + 1];
if (zoom.Viewport.Intersects(tri[0], tri[1], tri[2]))
{
zoom.NdcToScreen(ref tri[0]);
zoom.NdcToScreen(ref tri[1]);
zoom.NdcToScreen(ref tri[2]);
if (filled)
{
var b = brushes[partition[i]];
if (b.Color.A > 0)
{
g.FillPolygon(b, tri);
}
}
else
{
g.DrawPolygon(pen, tri);
}
}
}
pen.Dispose();
if (filled)
{
Helper.Dispose(brushes);
}
}
public void RenderLines(float[] points, int[] indices, Pen pen)
{
var g = this.RenderTarget;
var zoom = this.Context.Zoom;
int n = indices.Length / 2;
int k0, k1;
PointF p0 = new PointF();
PointF p1 = new PointF();
// Draw edges
for (int i = 0; i < n; i++)
{
k0 = 2 * indices[2 * i];
k1 = 2 * indices[2 * i + 1];
p0.X = points[k0];
p0.Y = points[k0 + 1];
p1.X = points[k1];
p1.Y = points[k1 + 1];
if (zoom.Viewport.Intersects(p0, p1))
{
zoom.NdcToScreen(ref p0);
zoom.NdcToScreen(ref p1);
g.DrawLine(pen, p0, p1);
}
}
}
}
}
@@ -0,0 +1,35 @@
namespace TriangleNet.Rendering.GDI.Native
{
using System;
/// <summary>
/// Specifies gradient fill mode
/// </summary>
[Flags]
internal enum GradientFillMode : uint
{
/// <summary>
/// In this mode, two endpoints describe a rectangle. The rectangle is defined
/// to have a constant color (specified by the TRIVERTEX structure) for the
/// left and right edges. GDI interpolates the color from the left to right
/// edge and fills the interior
/// </summary>
GRADIENT_FILL_RECT_H = 0,
/// <summary>
/// In this mode, two endpoints describe a rectangle. The rectangle is
/// defined to have a constant color (specified by the TRIVERTEX structure)
/// for the top and bottom edges. GDI interpolates the color from the top
/// to bottom edge and fills the interior
/// </summary>
GRADIENT_FILL_RECT_V = 1,
/// <summary>
/// In this mode, an array of TRIVERTEX structures is passed to GDI
/// along with a list of array indexes that describe separate triangles.
/// GDI performs linear interpolation between triangle vertices and fills
/// the interior. Drawing is done directly in 24- and 32-bpp modes.
/// Dithering is performed in 16-, 8-, 4-, and 1-bpp mode
/// </summary>
GRADIENT_FILL_TRIANGLE = 2
}
}
@@ -0,0 +1,28 @@
namespace TriangleNet.Rendering.GDI.Native
{
using System.Runtime.InteropServices;
/// <summary>
/// The GRADIENT_RECT structure specifies the index of two vertices in the
/// pVertex array in the GradientFill function. These two vertices form the
/// upper-left and lower-right boundaries of a rectangle.
/// </summary>
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144958.aspx
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct GradientRect
{
/// <summary>
/// The upper-left corner of a rectangle.
/// </summary>
public uint UpperLeft;
/// <summary>
/// The lower-right corner of a rectangle.
/// </summary>
public uint LowerRight;
}
}
@@ -0,0 +1,32 @@
namespace TriangleNet.Rendering.GDI.Native
{
using System.Runtime.InteropServices;
/// <summary>
/// The GRADIENT_TRIANGLE structure specifies the index of three
/// vertices in the pVertex array in the GradientFill function.
/// These three vertices form one triangle
/// </summary>
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144959.aspx
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct GradientTriangle
{
/// <summary>
/// The first point of the triangle where sides intersect.
/// </summary>
public uint Vertex1;
/// <summary>
/// The second point of the triangle where sides intersect.
/// </summary>
public uint Vertex2;
/// <summary>
/// The third point of the triangle where sides intersect.
/// </summary>
public uint Vertex3;
}
}
@@ -0,0 +1,90 @@
namespace TriangleNet.Rendering.GDI.Native
{
using System;
using System.Runtime.InteropServices;
/// <summary>
/// PInvoke signatures for GradientFill methods.
/// </summary>
/// <remarks>
/// Minimum requirements: Windows 2000 Professional
///
/// http://msdn.microsoft.com/en-us/library/windows/desktop/dd144957.aspx
/// </remarks>
internal static class NativeMethods
{
/// <summary>
/// The GradientFill function fills rectangle and triangle structures
/// </summary>
/// <param name="hdc">Handle to the destination device context</param>
/// <param name="pVertex">Array of TRIVERTEX structures that each define a triangle vertex</param>
/// <param name="nVertex">The number of vertices in pVertex</param>
/// <param name="pMesh">Array of elements</param>
/// <param name="nMesh">The number of elements in pMesh</param>
/// <param name="ulMode">Specifies gradient fill mode</param>
/// <returns>If the function succeeds, the return value is true, false</returns>
public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, uint[] pMesh, uint nMesh,
GradientFillMode ulMode)
{
return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode);
}
/// <summary>
/// The GradientFill function fills rectangle and triangle structures
/// </summary>
/// <param name="hdc">Handle to the destination device context</param>
/// <param name="pVertex">Array of TRIVERTEX structures that each define a triangle vertex</param>
/// <param name="nVertex">The number of vertices in pVertex</param>
/// <param name="pMesh">Array of GRADIENT_TRIANGLE structures in triangle mode</param>
/// <param name="nMesh">The number of elements in pMesh</param>
/// <param name="ulMode">Specifies gradient fill mode</param>
/// <returns>If the function succeeds, the return value is true, false</returns>
public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, GradientTriangle[] pMesh,
uint nMesh, GradientFillMode ulMode)
{
return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode);
}
/// <summary>
/// The GradientFill function fills rectangle and triangle structures
/// </summary>
/// <param name="hdc">Handle to the destination device context</param>
/// <param name="pVertex">Array of TRIVERTEX structures that each define a triangle vertex</param>
/// <param name="nVertex">The number of vertices in pVertex</param>
/// <param name="pMesh">an array of GRADIENT_RECT structures in rectangle mode</param>
/// <param name="nMesh">The number of elements in pMesh</param>
/// <param name="ulMode">Specifies gradient fill mode</param>
/// <returns>If the function succeeds, the return value is true, false</returns>
public static bool GradientFill([In] IntPtr hdc, TriVertex[] pVertex, uint nVertex, GradientRect[] pMesh,
uint nMesh, GradientFillMode ulMode)
{
return Native.GradientFill(hdc, pVertex, nVertex, pMesh, nMesh, ulMode);
}
#region Nested type: Native
internal class Native
{
[DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GradientFill([In] IntPtr hdc,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex,
uint nVertex, uint[] pMesh, uint nMesh, GradientFillMode ulMode);
[DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GradientFill([In] IntPtr hdc,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex,
uint nVertex, GradientRect[] pMesh, uint nMesh, GradientFillMode ulMode);
[DllImport("msimg32.dll", EntryPoint = "GradientFill", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GradientFill([In] IntPtr hdc,
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 2)] TriVertex[] pVertex,
uint nVertex, GradientTriangle[] pMesh, uint nMesh, GradientFillMode ulMode);
}
#endregion
}
}
@@ -0,0 +1,45 @@
namespace TriangleNet.Rendering.GDI.Native
{
using System.Runtime.InteropServices;
/// <summary>
/// The TRIVERTEX structure contains color information and position information.
/// </summary>
/// <remarks>
/// http://msdn.microsoft.com/en-us/library/windows/desktop/dd145142.aspx
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct TriVertex
{
/// <summary>
/// The x-coordinate, in logical units, of the upper-left corner of the rectangle
/// </summary>
public int x;
/// <summary>
/// The y-coordinate, in logical units, of the upper-left corner of the rectangle
/// </summary>
public int y;
/// <summary>
/// The color information at the point of x, y
/// </summary>
public ushort Red;
/// <summary>
/// The color information at the point of x, y
/// </summary>
public ushort Green;
/// <summary>
/// The color information at the point of x, y
/// </summary>
public ushort Blue;
/// <summary>
/// The color information at the point of x, y
/// </summary>
public ushort Alpha;
}
}
+251
View File
@@ -0,0 +1,251 @@
// -----------------------------------------------------------------------
// <copyright file="RendererControl.cs" company="">
// Christian Woltering, Triangle.NET, http://triangle.codeplex.com/
// </copyright>
// -----------------------------------------------------------------------
namespace TriangleNet.Rendering.GDI
{
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Globalization;
using System.Windows.Forms;
/// <summary>
/// Renders a mesh using GDI.
/// </summary>
public class RenderControl : Control, IRenderControl
{
// Rendering stuff
private BufferedGraphics buffer;
private BufferedGraphicsContext context;
//ColorManager renderColors;
bool initialized = false;
string coordinate = string.Empty;
Timer timer;
/// <summary>
/// Initializes a new instance of the <see cref="RenderControl" /> class.
/// </summary>
public RenderControl()
{
//this.SetStyle(ControlStyles.UserPaint, true);
//this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
//this.SetStyle(ControlStyles.Selectable, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
//renderColors = ColorManager.Default();
this.BackColor = Color.Black;
context = BufferedGraphicsManager.Current;// new BufferedGraphicsContext();
timer = new Timer();
timer.Interval = 3000;
timer.Tick += (sender, e) =>
{
timer.Stop();
coordinate = string.Empty;
this.Invalidate();
};
}
public IRenderer Renderer { get; set; }
/// <summary>
/// Initialize the graphics buffer (should be called in the forms load event).
/// </summary>
public void Initialize()
{
//zoom.Initialize(this.ClientRectangle);
InitializeBuffer();
initialized = true;
this.Invalidate();
}
public override void Refresh()
{
this.Render();
}
/// <summary>
/// Update graphics buffer and zoom after a resize.
/// </summary>
public void HandleResize()
{
var zoom = this.Renderer.Context.Zoom;
zoom.Resize(this.ClientRectangle);
InitializeBuffer();
}
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);
buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle);
if (initialized)
{
this.Render();
}
}
}
private void Render()
{
coordinate = string.Empty;
if (buffer == null)
{
return;
}
var g = buffer.Graphics;
var renderer = this.Renderer as LayerRenderer;
g.Clear(renderer.Context.ColorManager.Background);
if (!initialized || renderer == null)
{
return;
}
g.SmoothingMode = SmoothingMode.AntiAlias;
renderer.RenderTarget = g;
renderer.Render();
this.Invalidate();
}
#region Protected overrides
protected override void OnPaint(PaintEventArgs e)
{
if (!initialized)
{
base.OnPaint(e);
return;
}
buffer.Render();
if (!string.IsNullOrEmpty(coordinate) && Renderer.Context.HasData)
{
Graphics g = e.Graphics;
g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
TextRenderer.DrawText(g, coordinate, Font, new Point(10, 10), Color.White);
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
// Do nothing
if (!initialized)
{
base.OnPaintBackground(pevent);
}
}
protected override void OnMouseWheel(MouseEventArgs e)
{
if (!initialized) return;
var zoom = this.Renderer.Context.Zoom;
if (zoom.Zoom(e.Delta, (float)e.X / Width, (float)e.Y / Height))
{
// Redraw
this.Render();
}
}
protected override void OnMouseClick(MouseEventArgs e)
{
// We need to manually set the focus to get proper handling of
// the KeyUp and MouseWheel events.
this.Focus();
if (!initialized) return;
var zoom = this.Renderer.Context.Zoom;
if (e.Button == MouseButtons.Middle)
{
zoom.Reset();
this.Render();
}
else if (e.Button == MouseButtons.Left)
{
timer.Stop();
PointF c = new PointF((float)e.X / Width, (float)e.Y / Height);
zoom.ScreenToWorld(c, out double x, out double y);
coordinate = string.Format(NumberFormatInfo.InvariantInfo,
"X:{0} Y:{1}", x, y);
this.Invalidate();
timer.Start();
}
}
protected override void OnKeyUp(KeyEventArgs e)
{
if (!initialized) return;
var zoom = this.Renderer.Context.Zoom;
bool redraw = false;
if (e.KeyCode == Keys.Up)
{
redraw = zoom.Translate(0, 1);
}
else if (e.KeyCode == Keys.Down)
{
redraw = zoom.Translate(0, -1);
}
else if (e.KeyCode == Keys.Left)
{
redraw = zoom.Translate(-1, 0);
}
else if (e.KeyCode == Keys.Right)
{
redraw = zoom.Translate(1, 0);
}
if (redraw)
{
this.Render();
}
e.Handled = true;
}
#endregion
}
}
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>TriangleNet.Rendering.GDI</RootNamespace>
<AssemblyName>Triangle.Rendering.GDI</AssemblyName>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Triangle\Triangle.csproj" />
<ProjectReference Include="..\Triangle.Rendering\Triangle.Rendering.csproj" />
</ItemGroup>
</Project>