Translating concave hull algorithm to c# - c#

So I am trying to translate the algorith found here for concave hulls: http://repositorium.sdum.uminho.pt/bitstream/1822/6429/1/ConcaveHull_ACM_MYS.pdf
(Page 65)
Ive read through the entire thing but I cant figure out how to implement sortByAngle and angle, im not to sure what method I should do inside of them. This is what I have so far:
//Main method
public static Vertex[] ConcaveHull(Vertex[] points, int k = 3)
{
if (k < 3)
throw new ArgumentException("K is required to be 3 or more", "k");
List<Vertex> hull = new List<Vertex>();
//Clean first, may have lots of duplicates
Vertex[] clean = RemoveDuplicates(points);
if (clean.Length < 3)
throw new ArgumentException("At least 3 dissimilar points reqired", "points");
if (clean.Length == 3)//This is the hull, its already as small as it can be.
return clean;
if (clean.Length < k)
throw new ArgumentException("K must be equal to or smaller then the amount of dissimilar points", "points");
Vertex firstPoint = clean[0]; //TODO find mid point
hull.Add(firstPoint);
Vertex currentPoint = firstPoint;
Vertex[] dataset = RemoveIndex(clean, 0);
double previousAngle = 0;
int step = 2;
int i;
while (((currentPoint != firstPoint) || (step == 2)) && (dataset.Length > 0))
{
if (step == 5)
dataset = Add(dataset, firstPoint);
Vertex[] kNearestPoints = nearestPoints(dataset, currentPoint, k);
Vertex[] cPoints = sortByAngle(kNearestPoints, currentPoint, previousAngle);
bool its = true;
i = 0;
while ((its) && (i < cPoints.Length))
{
i++;
int lastPoint = 0;
if (cPoints[0] == firstPoint)
lastPoint = 1;
int j = 2;
its = false;
while ((!its) && (j < hull.Count - lastPoint))
{
its = intersectsQ(hull[step - 1 - 1], cPoints[0], hull[step - i - j - 1], hull[step - j - 1]);
j++;
}
}
if (its)
{
return ConcaveHull(points, k + 1);
}
currentPoint = cPoints[0];
hull.Add(currentPoint);
previousAngle = angle(hull[step - 1], hull[step - 2]);
dataset = RemoveIndex(dataset, 0);
step++;
}
bool allInside = true;
i = dataset.Length;
while (allInside && i > 0)
{
allInside = new Polygon(dataset).Contains(currentPoint); //TODO havent finished ray casting yet.
i--;
}
if (!allInside)
return ConcaveHull(points, k + 1);
return hull.ToArray();
}
private static Vertex[] Add(Vertex[] vs, Vertex v)
{
List<Vertex> n = new List<Vertex>(vs);
n.Add(v);
return n.ToArray();
}
private static Vertex[] RemoveIndex(Vertex[] vs, int index)
{
List<Vertex> removed = new List<Vertex>();
for (int i = 0; i < vs.Length; i++)
if (i != index)
removed.Add(vs[i]);
return removed.ToArray();
}
private static Vertex[] RemoveDuplicates(Vertex[] vs)
{
List<Vertex> clean = new List<Vertex>();
VertexComparer vc = new VertexComparer();
foreach (Vertex v in vs)
{
if (!clean.Contains(v, vc))
clean.Add(v);
}
return clean.ToArray();
}
private static Vertex[] nearestPoints(Vertex[] vs, Vertex v, int k)
{
Dictionary<double, Vertex> lengths = new Dictionary<double, Vertex>();
List<Vertex> n = new List<Vertex>();
double[] sorted = lengths.Keys.OrderBy(d => d).ToArray();
for (int i = 0; i < k; i++)
{
n.Add(lengths[sorted[i]]);
}
return n.ToArray();
}
private static Vertex[] sortByAngle(Vertex[] vs, Vertex v, double angle)
{
//TODO
return new Vertex[]{};
}
private static bool intersectsQ(Vertex v1, Vertex v2, Vertex v3, Vertex v4)
{
return intersectsQ(new Edge(v1, v2), new Edge(v3, v4));
}
private static bool intersectsQ(Edge e1, Edge e2)
{
double x1 = e1.A.X;
double x2 = e1.B.X;
double x3 = e2.A.X;
double x4 = e2.B.X;
double y1 = e1.A.Y;
double y2 = e1.B.Y;
double y3 = e2.A.Y;
double y4 = e2.B.Y;
var x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
var y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
if (double.IsNaN(x) || double.IsNaN(y))
{
return false;
}
else
{
if (x1 >= x2)
{
if (!(x2 <= x && x <= x1)) { return false; }
}
else
{
if (!(x1 <= x && x <= x2)) { return false; }
}
if (y1 >= y2)
{
if (!(y2 <= y && y <= y1)) { return false; }
}
else
{
if (!(y1 <= y && y <= y2)) { return false; }
}
if (x3 >= x4)
{
if (!(x4 <= x && x <= x3)) { return false; }
}
else
{
if (!(x3 <= x && x <= x4)) { return false; }
}
if (y3 >= y4)
{
if (!(y4 <= y && y <= y3)) { return false; }
}
else
{
if (!(y3 <= y && y <= y4)) { return false; }
}
}
return true;
}
private static double angle(Vertex v1, Vertex v2)
{
// TODO fix
Vertex v3 = new Vertex(v1.X, 0);
if (Orientation(v3, v1, v2) == 0)
return 180;
double b = EuclideanDistance(v3, v1);
double a = EuclideanDistance(v1, v2);
double c = EuclideanDistance(v3, v2);
double angle = Math.Acos((Math.Pow(a, 2) + Math.Pow(b, 2) - Math.Pow(c, 2)) / (2 * a * b));
if (Orientation(v3, v1, v2) < 0)
angle = 360 - angle;
return angle;
}
private static double EuclideanDistance(Vertex v1, Vertex v2)
{
return Math.Sqrt(Math.Pow((v1.X - v2.X), 2) + Math.Pow((v1.Y - v2.Y), 2));
}
public static double Orientation(Vertex p1, Vertex p2, Vertex p)
{
double Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
if (Orin > 0)
return -1;//Left
if (Orin < 0)
return 1;//Right
return 0;//Colinier
}
I know that there is a load of code here. But im not sure if I can show the context and what I have without it.
Other classes:
public class Polygon
{
private Vertex[] vs;
public Polygon(Vertex[] Vertexes)
{
vs = Vertexes;
}
public Polygon(Bounds bounds)
{
vs = bounds.ToArray();
}
public Vertex[] ToArray()
{
return vs;
}
public IEnumerable<Edge> Edges()
{
if (vs.Length > 1)
{
Vertex P = vs[0];
for (int i = 1; i < vs.Length; i++)
{
yield return new Edge(P, vs[i]);
P = vs[i];
}
yield return new Edge(P, vs[0]);
}
}
public bool Contains(Vertex v)
{
return RayCasting.RayCast(this, v);
}
}
public class Edge
{
public Vertex A = new Vertex(0, 0);
public Vertex B = new Vertex(0, 0);
public Edge() { }
public Edge(Vertex a, Vertex b)
{
A = a;
B = b;
}
public Edge(double ax, double ay, double bx, double by)
{
A = new Vertex(ax, ay);
B = new Vertex(bx, by);
}
}
public class Bounds
{
public Vertex TopLeft;
public Vertex TopRight;
public Vertex BottomLeft;
public Vertex BottomRight;
public Bounds() { }
public Bounds(Vertex TL, Vertex TR, Vertex BL, Vertex BR)
{
TopLeft = TL;
TopRight = TR;
BottomLeft = BL;
BottomRight = BR;
}
public Vertex[] ToArray()
{
return new Vertex[] { TopLeft, TopRight, BottomRight, BottomLeft };
}
}
public class Vertex
{
public double X = 0;
public double Y = 0;
public Vertex() { }
public Vertex(double x, double y)
{
X = x;
Y = y;
}
public static Vertex[] Convert(string vs)
{
vs = vs.Replace("[", "");
vs = vs.Replace("]", "");
string[] spl = vs.Split(';');
List<Vertex> nvs = new List<Vertex>();
foreach (string s in spl)
{
try
{
nvs.Add(new Vertex(s));
}
catch
{
}
}
return nvs.ToArray();
}
public static string Stringify(Vertex[] vs)
{
string res = "[";
foreach (Vertex v in vs)
{
res += v.ToString();
res += ";";
}
res = res.RemoveLastCharacter();
res += "]";
return res;
}
public static string ToString(Vertex[] array)
{
string res = "[";
foreach (Vertex v in array)
res += v.ToString() + ",";
return res.RemoveLastCharacter() + "]";
}
/*
//When x < y return -1
//When x == y return 0
//When x > y return 1
public static int Compare(Vertex x, Vertex y)
{
//To find lowest
if (x.X < y.X)
{
return -1;
}
else if (x.X == y.X)
{
if (x.Y < y.Y)
{
return -1;
}
else if (x.Y == y.Y)
{
return 0;
}
else
{
return 1;
}
}
else
{
return 1;
}
}
*/
public static int CompareY(Vertex a, Vertex b)
{
if (a.Y < b.Y)
return -1;
if (a.Y == b.Y)
return 0;
return 1;
}
public static int CompareX(Vertex a, Vertex b)
{
if (a.X < b.X)
return -1;
if (a.X == b.X)
return 0;
return 1;
}
public double distance (Vertex b){
double dX = b.X - this.X;
double dY = b.Y - this.Y;
return Math.Sqrt((dX*dX) + (dY*dY));
}
public double slope (Vertex b){
double dX = b.X - this.X;
double dY = b.Y - this.Y;
return dY / dX;
}
public static int Compare(Vertex u, Vertex a, Vertex b)
{
if (a.X == b.X && a.Y == b.Y) return 0;
Vertex upper = new Vertex();
Vertex p1 = new Vertex();
Vertex p2 = new Vertex();
upper.X = (u.X + 180) * 360;
upper.Y = (u.Y + 90) * 180;
p1.X = (a.X + 180) * 360;
p1.Y = (a.Y + 90) * 180;
p2.X = (b.X + 180) * 360;
p2.Y = (b.Y + 90) * 180;
if(p1 == upper) return -1;
if(p2 == upper) return 1;
double m1 = upper.slope(p1);
double m2 = upper.slope(p2);
if (m1 == m2)
{
return p1.distance(upper) < p2.distance(upper) ? -1 : 1;
}
if (m1 <= 0 && m2 > 0) return -1;
if (m1 > 0 && m2 <= 0) return -1;
return m1 > m2 ? -1 : 1;
}
public static Vertex UpperLeft(Vertex[] vs)
{
Vertex top = vs[0];
for (int i = 1; i < vs.Length; i++)
{
Vertex temp = vs[i];
if (temp.Y > top.Y || (temp.Y == top.Y && temp.X < top.X))
{
top = temp;
}
}
return top;
}
}

Just a note on convention: you should start function names with upper case, and variables with lower case. In the function sortByAngle, you have a reference to the parameter angle and the function angle simultaneously.
Assuming Angle(...) is simply meant to calculate the angle between two points:
private static double Angle(Vertex v1, Vertex v2)
{
return Math.Atan2(v2.Y - v1.Y, v2.X - v1.X);
}
will give you the angle from v1 to v2, in radians between -pi and +pi. Do not mix degrees and radians. My suggestion is to always use radians, and only convert to degrees if necessary for human-readable output.
private static Vertex[] SortByAngle(Vertex[] vs, Vertex v, double angle)
{
List<Vertex> vertList = new List<Vertex>(vs);
vertList.Sort((v1, v2) => AngleDifference(angle, Angle(v, v1)).CompareTo(AngleDifference(angle, Angle(v, v2))));
return vertList.ToArray();
}
uses List.Sort to sort the vertices from greatest to least angle difference between the vertices point and itself, and angle. The order of v1 and v2 are swapped in the input tuple to sort descending, that is, greatest difference first. The difference between angles is calculated like so:
private static double AngleDifference(double a, double b)
{
while (a < b - Math.PI) a += Math.PI * 2;
while (b < a - Math.PI) b += Math.PI * 2;
return Math.Abs(a - b);
}
The first two lines ensure that the angles are not more than 180 degrees apart.

You have error in
private static Vertex[] nearestPoints(Vertex[] vs, Vertex v, int k)
{
Dictionary<double, Vertex> lengths = new Dictionary<double, Vertex>();
List<Vertex> n = new List<Vertex>();
double[] sorted = lengths.Keys.OrderBy(d => d).ToArray();
for (int i = 0; i < k; i++)
{
n.Add(lengths[sorted[i]]);
}
return n.ToArray();
}
according to code if you have several vertexes at the same distance, function returns only one. Since Dictionary uses unique keys.
BTW, did anyone finish this?

I don't have the time right now to read the paper, but I assume from my knowledge of conVEX hull algorithms that you're going around the points in a particular direction looking for the next point to link to.
If that's the case, "angle" would be the angle of the most recent line segment of the hull, and you want to sort the points by their angle from that line. Therefore you want to calculate the angles between a line (on the hull) and a set of lines (from the current point to each other point being considered). Whether the angles calculated are positive or negative depends upon whether you're going clockwise or anticlockwise. To calculate the angles, look at something like this:
Calculating the angle between two lines without having to calculate the slope? (Java)
Then just sort by the angles.

What about that?
private List<Vector> sortClockwiseFromCentroid(List<Vector> points, Vector center)
{
points = points.OrderBy(x => Math.Atan2(x.X - center.X, x.Y - center.Y)).ToList();
return points;
}

Related

Calculation island error with perlin noise

I'm developing a small project, which is a grid of 100 x 100 hexagons.
In the script below, I paint my hexagons with the perlin noise, but the format I want to island does not go away.
I'll leave my code and 2 examples as my map stays and how I wish it to stay.
My island
My Island
As i need
Im Need
int getColor(float x, float z)
{
xTO = (int)x / terrainWidth - 30;
zTO = (int)z / terrainHeight - 30;
float v = Mathf.PerlinNoise((xTO + x + seed) * freq, (zTO + z) * freq);
// v += 0.001f;
float form = formWorld(x, z);
if (v < 0.25f)
{
//water
return 0;
}
else if (v < 0.5f)
{
//sand
return 1;
}
else if (v < 0.75f)
{
//grass
return 2;
}
else
{
//Trees / Forest
MakeNewTree(new Vector3(xx, 0, z * 7.5f));
return 2;
}
}
If you want your image to look more like the second one, the best option is going to be adding a circular gradient which offsets your Perlin Noise.
The easiest way to do this is to measure the distance from the center and combine that with the perlin noise.
Here's some untested code.
int getColor(float x, float z)
{
xTO = (int)x / terrainWidth - 30;
zTO = (int)z / terrainHeight - 30;
float v = Mathf.PerlinNoise((xTO + x + seed) * freq, (zTO + z) * freq);
// v += 0.001f;
v -= CircleOffset(x,z)/2; //Change the two to make the island bigger.
float form = formWorld(x, z);
if (v < 0.25f)
{
//water
return 0;
}
else if (v < 0.5f)
{
//sand
return 1;
}
else if (v < 0.75f)
{
//grass
return 2;
}
else
{
//Trees / Forest
MakeNewTree(new Vector3(xx, 0, z * 7.5f));
return 2;
}
}
float CircleOffset(float x, float y)
{
Vector2 center = new Vector2(terrainWidth/2,terrainHeight/2);
float distance = Mathf.Sqrt((center.x - x)*(center.x - x) + (center.y - y) * (center.y - y));
return distance/terrainWidth;
}
Hope this helps!

Gift wrapping algorithm

I saved group of points on my panel to List<MyVector> savedPoints, then I calculated the the point with lowest coordinate y :
public void searchLowest()
{
MyVector temp;
double ylon = savedPoints[0].getY();
for (int i = 0; i < savedPoints.Count; i++)
{
if (savedPoints[i].getY() > ylon)
{
ylon = savedPoints[i].getY();
lowest = i;
}
}
temp = savedPoints[lowest];
}
after this I made a method to calculate polar angles :
public static double angle(MyVector vec1, MyVector vec2)
{
double angle = Math.Atan2(vec1.getY() - vec2.getY(), vec1.getX() - vec2.getX());
return angle;
}
now don't know how to use Gift wrapping algorithm in my case. The pseudocode on WikiPedia link is not really understandable for me, so I'm asking for help here.
I'm using C# and win forms (net.framework 4.0)
Thanks for any help.
Using this as a reference, here is teh code:
namespace GiftWrapping
{
using System.Drawing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
class Program
{
static void Main(string[] args)
{
List<Point> test = new List<Point>(
new Point[]
{
new Point(200,200), new Point(300,100), new Point(200,50), new Point(100,100),
new Point(200, 100), new Point(300, 200), new Point(250, 100),
});
foreach (Point point in ConvexHull(test))
{
Console.WriteLine(point);
}
Console.ReadKey();
}
public static List<Point> ConvexHull(List<Point> points)
{
if (points.Count < 3)
{
throw new ArgumentException("At least 3 points reqired", "points");
}
List<Point> hull = new List<Point>();
// get leftmost point
Point vPointOnHull = points.Where(p => p.X == points.Min(min => min.X)).First();
Point vEndpoint;
do
{
hull.Add(vPointOnHull);
vEndpoint = points[0];
for (int i = 1; i < points.Count; i++)
{
if ((vPointOnHull == vEndpoint)
|| (Orientation(vPointOnHull, vEndpoint, points[i]) == -1))
{
vEndpoint = points[i];
}
}
vPointOnHull = vEndpoint;
}
while (vEndpoint != hull[0]);
return hull;
}
private static int Orientation(Point p1, Point p2, Point p)
{
// Determinant
int Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
if (Orin > 0)
return -1; // (* Orientation is to the left-hand side *)
if (Orin < 0)
return 1; // (* Orientation is to the right-hand side *)
return 0; // (* Orientation is neutral aka collinear *)
}
}
}
adaptation to your private classes, would be your homework.
Here is an implementation using the System.Windows.Point class in WindowsBase:
public struct PolarVector {
public double Radius { get; set; }
public double Angle { get; set; }
public override string ToString() {
return "{" + Radius + "," + Angle + "}";
}
}
private static void Main(string[] args) {
var points = new[] {
new Point {X = 0, Y = 0},
//new Point {X = 2, Y = 0},
new Point {X = 0, Y = 2},
new Point {X = 1.5, Y = 0.5},
new Point {X = 2, Y = 2},
};
foreach(var point in ConvexHull(points)) {
Console.WriteLine(point);
}
Console.WriteLine();
if(Debugger.IsAttached) {
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
public static IList<Point> ConvexHull(IList<Point> points) {
var pointOnHull = LeftMost(points);
var pointsOnHull = new List<Point>();
Point currentPoint;
do {
pointsOnHull.Add(pointOnHull);
currentPoint = points[0];
foreach(var nextPoint in points.Skip(1)) {
if (currentPoint == pointOnHull || IsLeft(nextPoint, pointOnHull, currentPoint)) {
currentPoint = nextPoint;
}
}
pointOnHull = currentPoint;
}
while (currentPoint != pointsOnHull[0]);
return pointsOnHull;
}
private static Point LeftMost(IEnumerable<Point> points) {
return points.Aggregate((v1, v2) => v2.X < v1.X ? v2 : v1);
}
private static bool IsLeft(Point nextPoint, Point lastPoint, Point currentPoint) {
var nextVector = ToPolar(nextPoint, lastPoint);
var currentVector = ToPolar(currentPoint, lastPoint);
return nextVector.Radius != 0 && Normalize(nextVector.Angle - currentVector.Angle) > 0;
}
private static PolarVector ToPolar(Point target, Point start) {
var vector = target - start;
return new PolarVector { Radius = Math.Sqrt((vector.Y * vector.Y) + (vector.X * vector.X)), Angle = Math.Atan2(vector.Y, vector.X)};
}
private static double Normalize(double radians) {
while(radians > Math.PI) {
radians -= 2*Math.PI;
}
while (radians < -Math.PI) {
radians += 2*Math.PI;
}
return radians;
}
For Gift Wrapping algorithm implementation, it is advisable that one uses Left test technique
// Left test implementation given by Petr
private static int Orientation(Point p1, Point p2, Point p)
{
// Determinant
int Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
if (Orin > 0)
return -1; // (* Orientation is to the left-hand side *)
if (Orin < 0)
return 1; // (* Orientation is to the right-hand side *)
return 0; // (* Orientation is neutral aka collinear *)
}
Using this(Left test) comparison technique helps us in wrapping the gift faster, so to speak.
Never ever use arc tangent calculations, it will impact the run times in a big way.
Reference: Left test technique mentioned here - https://stackoverflow.com/a/1560510/1019673

Implementing Geofence - C#

I need to implement Geofence in C#. Geofence area can be round, rectangle, polygon etc. Does anyone have Geofence implementation in C#?
I found Geo Fencing - point inside/outside polygon. But, it supports polygon only.
I have tested various implementations and this example worked properly for me:
Example
public static bool PolyContainsPoint(List<Point> points, Point p) {
bool inside = false;
// An imaginary closing segment is implied,
// so begin testing with that.
Point v1 = points[points.Count - 1];
foreach (Point v0 in points)
{
double d1 = (p.Y - v0.Y) * (v1.X - v0.X);
double d2 = (p.X - v0.X) * (v1.Y - v0.Y);
if (p.Y < v1.Y)
{
// V1 below ray
if (v0.Y <= p.Y)
{
// V0 on or above ray
// Perform intersection test
if (d1 > d2)
{
inside = !inside; // Toggle state
}
}
}
else if (p.Y < v0.Y)
{
// V1 is on or above ray, V0 is below ray
// Perform intersection test
if (d1 < d2)
{
inside = !inside; // Toggle state
}
}
v1 = v0; //Store previous endpoint as next startpoint
}
return inside;
}
Refer to my Implementation:
Polygon
Circle
Adding both C# implementation here
It worked for me!
//Location will hold the latitude and longitude.
public class Location
{
public double lat { get; set; }
public double lng { get; set; }
public Location(double lat, double lng)
{
this.lat = lat;
this.lng = lng;
}
}
//Implementation for the Polygon.
bool IsPointInPolygon(List<Location> poly, Location point)
{
int i, j;
bool c = false;
for (i = 0, j = poly.Count - 1; i < poly.Count; j = i++)
{
if ((((poly[i].lat <= point.lat) && (point.lat < poly[j].lat))
|| ((poly[j].lat <= point.lat) && (point.lat < poly[i].lat)))
&& (point.lng < (poly[j].lng - poly[i].lng) * (point.lat - poly[i].lat)
/ (poly[j].lat - poly[i].lat) + poly[i].lng))
{
c = !c;
}
}
return c;
}
//Geofencing for the Circle.
//GetDistance will return total Kilometers
//p1 is the Center lat,long and p2 is the current location lat,long
//radius in meters
public bool IsPointInCircle(Location p1,Location p2,double radius)
{
return GetDistance(p1,p2)>radius*0.001?false:true;
}
public double GetDistance(Location pos1, Location pos2)
{
double e = pos1.lat * (Math.PI / 180);
double f = pos1.lng * (Math.PI / 180);
double g = pos2.lat * (Math.PI / 180);
double h = pos2.lng * (Math.PI / 180);
double i =
(Math.Cos(e) * Math.Cos(g) * Math.Cos(f) * Math.Cos(h)
+ Math.Cos(e) * Math.Sin(f) * Math.Cos(g) * Math.Sin(h)
+ Math.Sin(e) * Math.Sin(g));
double j = Math.Acos(i);
return (6371 * j);
}

How can I render curved text into a Bitmap?

I am currently dynamically creating a bitmap and using the graphics object from the bitmap to draw a string on it like so:
System.Drawing.Graphics graph = System.Drawing.Graphics.FromImage(bmp);
graph.DrawString(text, font, brush, new System.Drawing.Point(0, 0));
This returns a rectangular bitmap with the string written straight across from left to right.
I would like to also be able to draw the string in the shape of a rainbow.
How can I do this?
I recently had this problem (I was rendering text for printing onto CDs), so here's my solution:
private void DrawCurvedText(Graphics graphics, string text, Point centre, float distanceFromCentreToBaseOfText, float radiansToTextCentre, Font font, Brush brush)
{
// Circumference for use later
var circleCircumference = (float)(Math.PI * 2 * distanceFromCentreToBaseOfText);
// Get the width of each character
var characterWidths = GetCharacterWidths(graphics, text, font).ToArray();
// The overall height of the string
var characterHeight = graphics.MeasureString(text, font).Height;
var textLength = characterWidths.Sum();
// The string length above is the arc length we'll use for rendering the string. Work out the starting angle required to
// centre the text across the radiansToTextCentre.
float fractionOfCircumference = textLength / circleCircumference;
float currentCharacterRadians = radiansToTextCentre + (float)(Math.PI * fractionOfCircumference);
for (int characterIndex = 0; characterIndex < text.Length; characterIndex++)
{
char #char = text[characterIndex];
// Polar to cartesian
float x = (float)(distanceFromCentreToBaseOfText * Math.Sin(currentCharacterRadians));
float y = -(float)(distanceFromCentreToBaseOfText * Math.Cos(currentCharacterRadians));
using (GraphicsPath characterPath = new GraphicsPath())
{
characterPath.AddString(#char.ToString(), font.FontFamily, (int)font.Style, font.Size, Point.Empty,
StringFormat.GenericTypographic);
var pathBounds = characterPath.GetBounds();
// Transformation matrix to move the character to the correct location.
// Note that all actions on the Matrix class are prepended, so we apply them in reverse.
var transform = new Matrix();
// Translate to the final position
transform.Translate(centre.X + x, centre.Y + y);
// Rotate the character
var rotationAngleDegrees = currentCharacterRadians * 180F / (float)Math.PI - 180F;
transform.Rotate(rotationAngleDegrees);
// Translate the character so the centre of its base is over the origin
transform.Translate(-pathBounds.Width / 2F, -characterHeight);
characterPath.Transform(transform);
// Draw the character
graphics.FillPath(brush, characterPath);
}
if (characterIndex != text.Length - 1)
{
// Move "currentCharacterRadians" on to the next character
var distanceToNextChar = (characterWidths[characterIndex] + characterWidths[characterIndex + 1]) / 2F;
float charFractionOfCircumference = distanceToNextChar / circleCircumference;
currentCharacterRadians -= charFractionOfCircumference * (float)(2F * Math.PI);
}
}
}
private IEnumerable<float> GetCharacterWidths(Graphics graphics, string text, Font font)
{
// The length of a space. Necessary because a space measured using StringFormat.GenericTypographic has no width.
// We can't use StringFormat.GenericDefault for the characters themselves, as it adds unwanted spacing.
var spaceLength = graphics.MeasureString(" ", font, Point.Empty, StringFormat.GenericDefault).Width;
return text.Select(c => c == ' ' ? spaceLength : graphics.MeasureString(c.ToString(), font, Point.Empty, StringFormat.GenericTypographic).Width);
}
I needed to answer this question in raw C# and could not find any samples that do it so:
This solution requires a lot of Maths to solve. In summary it takes a set of points (2D vectors), aligns them to a baseline and then bends the points around a Spline. The code is fast enough to update in real time and handles loops, etc.
For ease the solution turns text into vectors using GraphicsPath.AddString and uses GraphicsPath.PathPoints/PathTypes for the points, however you can bend any shape or even bitmaps using the same functions. (I would not recommend doing 4k bitmaps in real time though).
The code has a simple Paint function followed by the Spline class. GraphicsPath is used in the Paint method to make the code hopefully easier to understand. ClickedPoints are the array of points you want the text bent around. I used a List as it was added to in Mouse events, use an array if you know the points beforehand.
public void Paint(System.Drawing.Graphics g)
{
List<System.Drawing.Point> clickedPoints = new List<System.Drawing.Point>();
Additional.Math.Beziers.BezierSplineCubic2D _Spline = new Additional.Math.Beziers.BezierSplineCubic2D();
// Create the spline, exit if no points to bend around.
System.Drawing.PointF[] cd = Additional.Math.Beziers.BezierSplineCubic2D.CreateCurve(ClickedPoints.ToArray(), 0, ClickedPoints.Count, 0);
_Spline = new Additional.Math.Beziers.BezierSplineCubic2D(cd);
if (_Spline.Beziers == null || _Spline.Length == 0) return;
// Start Optional: Remove if you only want the bent object
// Draw the spline curve the text will be put onto using inbuilt GDI+ calls
g.DrawCurve(System.Drawing.Pens.Blue, clickedPoints.ToArray());
// draw the control point data for the curve
for (int i = 0; i < cd.Length; i++)
{
g.DrawRectangle(System.Drawing.Pens.Green, cd[i].X - 2F, cd[i].Y - 2F, 4F, 4F);
}
// End Optional:
// Turn the text into points that can be bent - if no points then exit:
System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
path.AddString("Lorem ipsum dolor", new System.Drawing.FontFamily("Arial"), 0, 12.0F, new System.Drawing.Point(0, 0), new System.Drawing.StringFormat() { Alignment = System.Drawing.StringAlignment.Near });
textBounds = path.GetBounds();
curvedData = (System.Drawing.PointF[])path.PathPoints.Clone();
curvedTypes = (byte[])path.PathTypes.Clone();
dataLength = curvedData.Length;
if (dataLength == 0) return;
// Bend the shape(text) around the path (Spline)
_Spline.BendShapeToSpline(textBounds, dataLength, ref curvedData, ref curvedTypes);
// draw the transformed text path
System.Drawing.Drawing2D.GraphicsPath textPath = new System.Drawing.Drawing2D.GraphicsPath(curvedData, curvedTypes);
g.DrawPath(System.Drawing.Pens.Black, textPath);
}
And now for the spline class:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Additional.Math
{
namespace Vectors
{
public struct Vector2DFloat
{
public float X;
public float Y;
public void SetXY(float x, float y)
{
X = x;
Y = y;
}
public static Vector2DFloat Lerp(Vector2DFloat v0, Vector2DFloat v1, float t)
{
return v0 + (v1 - v0) * t;
}
public Vector2DFloat(Vector2DFloat value)
{
this.X = value.X;
this.Y = value.Y;
}
public Vector2DFloat(float x, float y)
{
this.X = x;
this.Y = y;
}
public Vector2DFloat Rotate90Degrees(bool positiveRotation)
{
return positiveRotation ? new Vector2DFloat(-Y, X) : new Vector2DFloat(Y, -X);
}
public Vector2DFloat Normalize()
{
float magnitude = (float)System.Math.Sqrt(X * X + Y * Y);
return new Vector2DFloat(X / magnitude, Y / magnitude);
}
public float Distance(Vector2DFloat target)
{
return (float)System.Math.Sqrt((X - target.X) * (X - target.X) + (Y - target.Y) * (Y - target.Y));
}
public float DistanceSquared(Vector2DFloat target)
{
return (X - target.X) * (X - target.X) + (Y - target.Y) * (Y - target.Y);
}
public double DistanceTo(Vector2DFloat target)
{
return System.Math.Sqrt(System.Math.Pow(target.X - X, 2F) + System.Math.Pow(target.Y - Y, 2F));
}
public System.Drawing.PointF ToPointF()
{
return new System.Drawing.PointF(X, Y);
}
public Vector2DFloat(System.Drawing.PointF value)
{
this.X = value.X;
this.Y = value.Y;
}
public static implicit operator Vector2DFloat(System.Drawing.PointF value)
{
return new Vector2DFloat(value);
}
public static Vector2DFloat operator +(Vector2DFloat first, Vector2DFloat second)
{
return new Vector2DFloat(first.X + second.X, first.Y + second.Y);
}
public static Vector2DFloat operator -(Vector2DFloat first, Vector2DFloat second)
{
return new Vector2DFloat(first.X - second.X, first.Y - second.Y);
}
public static Vector2DFloat operator *(Vector2DFloat first, float second)
{
return new Vector2DFloat(first.X * second, first.Y * second);
}
public static Vector2DFloat operator *(float first, Vector2DFloat second)
{
return new Vector2DFloat(second.X * first, second.Y * first);
}
public static Vector2DFloat operator *(Vector2DFloat first, int second)
{
return new Vector2DFloat(first.X * second, first.Y * second);
}
public static Vector2DFloat operator *(int first, Vector2DFloat second)
{
return new Vector2DFloat(second.X * first, second.Y * first);
}
public static Vector2DFloat operator *(Vector2DFloat first, double second)
{
return new Vector2DFloat((float)(first.X * second), (float)(first.Y * second));
}
public static Vector2DFloat operator *(double first, Vector2DFloat second)
{
return new Vector2DFloat((float)(second.X * first), (float)(second.Y * first));
}
public override bool Equals(object obj)
{
return this.Equals((Vector2DFloat)obj);
}
public bool Equals(Vector2DFloat p)
{
// If parameter is null, return false.
if (p == null)
{
return false;
}
// Optimization for a common success case.
if (this == p)
{
return true;
}
// If run-time types are not exactly the same, return false.
if (this.GetType() != p.GetType())
{
return false;
}
// Return true if the fields match.
// Note that the base class is not invoked because it is
// System.Object, which defines Equals as reference equality.
return (X == p.X) && (Y == p.Y);
}
public override int GetHashCode()
{
return (int)(System.Math.Round(X + Y, 4) * 10000);
}
public static bool operator ==(Vector2DFloat first, Vector2DFloat second)
{
// Check for null on left side.
if (first == null)
{
if (second == null)
{
// null == null = true.
return true;
}
// Only the left side is null.
return false;
}
// Equals handles case of null on right side.
return first.Equals(second);
}
public static bool operator !=(Vector2DFloat first, Vector2DFloat second)
{
return !(first == second);
}
}
}
namespace Beziers
{
public struct BezierCubic2D
{
public Vectors.Vector2DFloat P0;
public Vectors.Vector2DFloat P1;
public Vectors.Vector2DFloat P2;
public Vectors.Vector2DFloat P3;
public int ArcLengthDivisionCount;
public List<float> ArcLengths { get { if (_ArcLengths.Count == 0) CalculateArcLength(); return _ArcLengths; } }
public float ArcLength { get { if (_ArcLength == 0.0F) CalculateArcLength(); return _ArcLength; } }
private Vectors.Vector2DFloat A;
private Vectors.Vector2DFloat B;
private Vectors.Vector2DFloat C;
private List<float> _ArcLengths;
private float _ArcLength;
public BezierCubic2D(Vectors.Vector2DFloat p0, Vectors.Vector2DFloat p1, Vectors.Vector2DFloat p2, Vectors.Vector2DFloat p3)
{
P0 = p0;
P1 = p1;
P2 = p2;
P3 = p3;
// vt = At^3 + Bt^2 + Ct + p0
A = P3 - 3 * P2 + 3 * P1 - P0;
B = 3 * P2 - 6 * P1 + 3 * P0;
C = 3 * P1 - 3 * P0;
ArcLengthDivisionCount = 100;
_ArcLengths = new List<float>();
_ArcLength = 0.0F;
}
public BezierCubic2D(System.Drawing.PointF p0, System.Drawing.PointF p1, System.Drawing.PointF p2, System.Drawing.PointF p3)
{
P0 = p0;
P1 = p1;
P2 = p2;
P3 = p3;
// vt = At^3 + Bt^2 + Ct + p0
A = P3 - 3 * P2 + 3 * P1 - P0;
B = 3 * P2 - 6 * P1 + 3 * P0;
C = 3 * P1 - 3 * P0;
ArcLengthDivisionCount = 100;
_ArcLengths = new List<float>();
_ArcLength = 0.0F;
}
public BezierCubic2D(float p0X, float p0Y, float p1X, float p1Y, float p2X, float p2Y, float p3X, float p3Y)
{
P0 = new Vectors.Vector2DFloat(p0X, p0Y);
P1 = new Vectors.Vector2DFloat(p1X, p1Y);
P2 = new Vectors.Vector2DFloat(p2X, p2Y);
P3 = new Vectors.Vector2DFloat(p3X, p3Y);
// vt = At^3 + Bt^2 + Ct + p0
A = P3 - 3 * P2 + 3 * P1 - P0;
B = 3 * P2 - 6 * P1 + 3 * P0;
C = 3 * P1 - 3 * P0;
ArcLengthDivisionCount = 100;
_ArcLengths = new List<float>();
_ArcLength = 0.0F;
}
public Vectors.Vector2DFloat PointOnCurve(float t)
{
return A * System.Math.Pow(t, 3) + B * System.Math.Pow(t, 2) + C * t + P0;
}
public Vectors.Vector2DFloat PointOnCurveGeometric(float t)
{
Vectors.Vector2DFloat p4 = Vectors.Vector2DFloat.Lerp(P0, P1, t);
Vectors.Vector2DFloat p5 = Vectors.Vector2DFloat.Lerp(P1, P2, t);
Vectors.Vector2DFloat p6 = Vectors.Vector2DFloat.Lerp(P2, P3, t);
Vectors.Vector2DFloat p7 = Vectors.Vector2DFloat.Lerp(p4, p5, t);
Vectors.Vector2DFloat p8 = Vectors.Vector2DFloat.Lerp(p5, p6, t);
return Vectors.Vector2DFloat.Lerp(p7, p8, t);
}
public Vectors.Vector2DFloat PointOnCurveTangent(float t)
{
return 3 * A * System.Math.Pow(t, 2) + 2 * B * t + C;
}
public Vectors.Vector2DFloat PointOnCurvePerpendicular(float t, bool positiveRotation)
{
return (3 * A * System.Math.Pow(t, 2) + 2 * B * t + C).Rotate90Degrees(positiveRotation).Normalize() * 10F + PointOnCurve(t);
}
public Vectors.Vector2DFloat PointOnCurvePerpendicular(float t, bool positiveRotation, float pointHeight)
{
return (3 * A * System.Math.Pow(t, 2) + 2 * B * t + C).Rotate90Degrees(positiveRotation).Normalize() * pointHeight + PointOnCurve(t);
}
public float FindTAtPointOnBezier(float u)
{
float t;
int index = _ArcLengths.BinarySearch(u);
if (index >= 0)
t = index / (float)(_ArcLengths.Count - 1);
else if (index * -1 >= _ArcLengths.Count)
t = 1;
else if (index == 0)
t = 0;
else
{
index *= -1;
float lengthBefore = _ArcLengths[index - 1];
float lengthAfter = _ArcLengths[index];
float segmentLength = lengthAfter - lengthBefore;
float segmentFraction = (u - lengthBefore) / segmentLength;
// add that fractional amount to t
t = (index + segmentFraction) / (float)(_ArcLengths.Count - 1);
}
return t;
}
private void CalculateArcLength()
{
// calculate Arc Length through successive approximation. Use the squared version as it is faster.
_ArcLength = 0.0F;
int arrayMax = ArcLengthDivisionCount + 1;
_ArcLengths = new List<float>(arrayMax)
{
0.0F
};
Vectors.Vector2DFloat prior = P0, current;
for (int i = 1; i < arrayMax; i++)
{
current = PointOnCurve(i / (float)ArcLengthDivisionCount);
_ArcLength += current.Distance(prior);
_ArcLengths.Add(_ArcLength);
prior = current;
}
}
public override bool Equals(object obj)
{
return this.Equals((BezierCubic2D)obj);
}
public bool Equals(BezierCubic2D p)
{
// If parameter is null, return false.
if (p == null)
{
return false;
}
// Optimization for a common success case.
if (this == p)
{
return true;
}
// If run-time types are not exactly the same, return false.
if (this.GetType() != p.GetType())
{
return false;
}
// Return true if the fields match.
// Note that the base class is not invoked because it is
// System.Object, which defines Equals as reference equality.
return (P0 == p.P0) && (P1 == p.P1) && (P2 == p.P2) && (P3 == p.P3);
}
public override int GetHashCode()
{
return P0.GetHashCode() + P1.GetHashCode() + P2.GetHashCode() + P3.GetHashCode() % int.MaxValue;
}
public static bool operator ==(BezierCubic2D first, BezierCubic2D second)
{
// Check for null on left side.
if (first == null)
{
if (second == null)
{
// null == null = true.
return true;
}
// Only the left side is null.
return false;
}
// Equals handles case of null on right side.
return first.Equals(second);
}
public static bool operator !=(BezierCubic2D first, BezierCubic2D second)
{
return !(first == second);
}
}
public struct BezierSplineCubic2D
{
public BezierCubic2D[] Beziers;
public BezierCubic2D this[int index] { get { return Beziers[index]; } }
public int Length { get { return Beziers.Length; } }
public List<float> ArcLengths { get { if (_ArcLengths.Count == 0) CalculateArcLength(); return _ArcLengths; } }
public float ArcLength { get { if (_ArcLength == 0.0F) CalculateArcLength(); return _ArcLength; } }
private List<float> _ArcLengths;
private float _ArcLength;
public BezierSplineCubic2D(Vectors.Vector2DFloat[] source)
{
if (source == null || source.Length < 4 || (source.Length - 4) % 3 != 0) { Beziers = null; _ArcLength = 0.0F; _ArcLengths = new List<float>(); return; }
int length = ((source.Length - 4) / 3) + 1;
Beziers = new BezierCubic2D[length];
Beziers[0] = new BezierCubic2D(source[0], source[1], source[2], source[3]);
for (int i = 1; i < length; i++)
Beziers[i] = new BezierCubic2D(source[(i * 3)], source[(i * 3) + 1], source[(i * 3) + 2], source[(i * 3) + 3]);
_ArcLength = 0.0F;
_ArcLengths = new List<float>();
}
public BezierSplineCubic2D(System.Drawing.PointF[] source)
{
if (source == null || source.Length < 4 || (source.Length - 4) % 3 != 0) { Beziers = null; _ArcLength = 0.0F; _ArcLengths = new List<float>(); return; }
int length = ((source.Length - 4) / 3) + 1;
Beziers = new BezierCubic2D[length];
Beziers[0] = new BezierCubic2D(source[0], source[1], source[2], source[3]);
for (int i = 1; i < length; i++)
Beziers[i] = new BezierCubic2D(source[(i * 3)], source[(i * 3) + 1], source[(i * 3) + 2], source[(i * 3) + 3]);
_ArcLength = 0.0F;
_ArcLengths = new List<float>();
}
public BezierSplineCubic2D(System.Drawing.Point[] source)
{
if (source == null || source.Length < 4 || (source.Length - 4) % 3 != 0) { Beziers = null; _ArcLength = 0.0F; _ArcLengths = new List<float>(); return; }
int length = ((source.Length - 4) / 3) + 1;
Beziers = new BezierCubic2D[length];
Beziers[0] = new BezierCubic2D(source[0], source[1], source[2], source[3]);
for (int i = 1; i < length; i++)
Beziers[i] = new BezierCubic2D(source[(i * 3)], source[(i * 3) + 1], source[(i * 3) + 2], source[(i * 3) + 3]);
_ArcLength = 0.0F;
_ArcLengths = new List<float>();
}
public bool FindTAtPointOnSpline(float distanceAlongSpline, out BezierCubic2D bezier, out float t)
{
// to do: cache last distance and bezier. if new distance > old then start from old bezier.
if (distanceAlongSpline > ArcLength) { bezier = Beziers[Beziers.Length - 1]; t = distanceAlongSpline / ArcLength; return false; }
if (distanceAlongSpline <= 0.0F)
{
bezier = Beziers[0];
t = 0.0F;
return true;
}
for (int i = 0; i < Beziers.Length; i++)
{
float distanceRemainingBeyondCurrentBezier = distanceAlongSpline - Beziers[i].ArcLength;
if (distanceRemainingBeyondCurrentBezier < 0.0F)
{
// t is in current bezier.
bezier = Beziers[i];
t = bezier.FindTAtPointOnBezier(distanceAlongSpline);
return true;
}
else if (distanceRemainingBeyondCurrentBezier == 0.0F)
{
// t is 1.0F. Bezier is current one.
bezier = Beziers[i];
t = 1.0F;
return true;
}
// reduce the distance by the length of the bezier.
distanceAlongSpline -= Beziers[i].ArcLength;
}
// point is outside the spline.
bezier = new BezierCubic2D();
t = 0.0F;
return false;
}
public void BendShapeToSpline(System.Drawing.RectangleF bounds, int dataLength, ref System.Drawing.PointF[] data, ref byte[] dataTypes)
{
System.Drawing.PointF pt;
// move the origin for the data to 0,0
float left = bounds.Left, height = bounds.Y + bounds.Height;
for (int i = 0; i < dataLength; i++)
{
pt = data[i];
float textX = pt.X - left;
float textY = pt.Y - height;
if (FindTAtPointOnSpline(textX, out BezierCubic2D bezier, out float t))
{
data[i] = bezier.PointOnCurvePerpendicular(t, true, textY).ToPointF();
}
else
{
// roll back all points until we reach curvedTypes[i] == 0
for (int j = i - 1; j > -1; j--)
{
if ((dataTypes[j] & 0x80) == 0x80)
{
System.Drawing.PointF[] temp1 = new System.Drawing.PointF[j + 1];
Array.Copy(data, 0, temp1, 0, j + 1);
byte[] temp2 = new byte[j + 1];
Array.Copy(dataTypes, 0, temp2, 0, j + 1);
data = temp1;
dataTypes = temp2;
break;
}
}
break;
}
}
}
private void CalculateArcLength()
{
_ArcLength = 0.0F;
_ArcLengths = new List<float>(Beziers.Length);
for (int i = 0; i < Beziers.Length; i++)
{
_ArcLength += Beziers[i].ArcLength;
_ArcLengths.Add(_ArcLength);
}
}
internal static System.Drawing.PointF[] GetCurveTangents(System.Drawing.Point[] points, int count, float tension, int curveType)
{
if (points == null)
throw new ArgumentNullException("points");
System.Drawing.PointF[] pointfs = new System.Drawing.PointF[count];
for (int p = 0; p < count; p++)
{
pointfs[p] = new System.Drawing.PointF(points[p].X, points[p].Y);
}
return GetCurveTangents(pointfs, count, tension, curveType);
}
internal static System.Drawing.PointF[] GetCurveTangents(System.Drawing.PointF[] points, int count, float tension, int curveType)
{
float coefficient = tension / 3f;
System.Drawing.PointF[] tangents = new System.Drawing.PointF[count];
if (count < 2)
return tangents;
for (int i = 0; i < count; i++)
{
int r = i + 1;
int s = i - 1;
if (r >= count)
r = count - 1;
if (curveType == 0) // 0 == CurveType.Open
{
if (s < 0)
s = 0;
}
else // 1 == CurveType.Closed, end point jumps to start point
{
if (s < 0)
s += count;
}
tangents[i].X += (coefficient * (points[r].X - points[s].X));
tangents[i].Y += (coefficient * (points[r].Y - points[s].Y));
}
return tangents;
}
internal static System.Drawing.PointF[] CreateCurve(System.Drawing.Point[] points, int offset, int length, int curveType)
{
if (points == null)
throw new ArgumentNullException("points");
System.Drawing.PointF[] pointfs = new System.Drawing.PointF[length];
for (int p = 0; p < length; p++)
{
pointfs[p] = new System.Drawing.PointF(points[p].X, points[p].Y);
}
System.Drawing.PointF[] tangents = GetCurveTangents(pointfs, length, 0.5F, 0);
return CreateCurve(pointfs, tangents, offset, length, curveType);
}
internal static System.Drawing.PointF[] CreateCurve(System.Drawing.Point[] points, System.Drawing.PointF[] tangents, int offset, int length, int curveType)
{
if (points == null)
throw new ArgumentNullException("points");
System.Drawing.PointF[] pointfs = new System.Drawing.PointF[length];
for (int p = 0; p < length; p++)
{
pointfs[p] = new System.Drawing.PointF(points[p].X, points[p].Y);
}
return CreateCurve(pointfs, tangents, offset, length, curveType);
}
internal static System.Drawing.PointF[] CreateCurve(System.Drawing.PointF[] points, System.Drawing.PointF[] tangents, int offset, int length, int curveType)
{
List<System.Drawing.PointF> curve = new List<System.Drawing.PointF>();
int i;
Append(curve, points[offset].X, points[offset].Y, true);
for (i = offset; i < offset + length - 1; i++)
{
int j = i + 1;
float x1 = points[i].X + tangents[i].X;
float y1 = points[i].Y + tangents[i].Y;
float x2 = points[j].X - tangents[j].X;
float y2 = points[j].Y - tangents[j].Y;
float x3 = points[j].X;
float y3 = points[j].Y;
AppendBezier(curve, x1, y1, x2, y2, x3, y3, false);
}
return curve.ToArray<System.Drawing.PointF>();
}
internal static void Append(List<System.Drawing.PointF> points, float x, float y, bool compress)
{
System.Drawing.PointF pt = System.Drawing.PointF.Empty;
/* in some case we're allowed to compress identical points */
if (compress && (points.Count > 0))
{
/* points (X, Y) must be identical */
System.Drawing.PointF lastPoint = points[points.Count - 1];
if ((lastPoint.X == x) && (lastPoint.Y == y))
{
return;
}
}
pt.X = x;
pt.Y = y;
points.Add(pt);
}
internal static void AppendBezier(List<System.Drawing.PointF> points, float x1, float y1, float x2, float y2, float x3, float y3, bool isReverseWindingOnFill)
{
if (isReverseWindingOnFill)
{
Append(points, y1, x1, false);
Append(points, y2, x2, false);
Append(points, y3, x3, false);
}
else
{
Append(points, x1, y1, false);
Append(points, x2, y2, false);
Append(points, x3, y3, false);
}
}
}
}
}
I think the only way is to render each character individually and use the
Graphics.RotateTransform
to rotate the text. You'll need to work out the rotation angle and rendering offset yourself. You can use the
Graphics.MeasureCharacterRanges
to get the size of each character.
Unfortunatelly in GDI+ there is no way to attach Strings to a path (this is what you would be looking for).
So the only way to do this is doing it "by hand". That means splitting up the string into characters and placing them based on your own path calculations.
Unless you want to put a lot of work into this you should try to find a library (potentially complete GDI+ replacement) to do this or give up on your rainbow.
With WPF you can render text on a path (see link for a howto)

How to know if a line intersects a plane in C#?

I have two points (a line segment) and a rectangle. I would like to know how to calculate if the line segment intersects the rectangle.
From my "Geometry" class:
public struct Line
{
public static Line Empty;
private PointF p1;
private PointF p2;
public Line(PointF p1, PointF p2)
{
this.p1 = p1;
this.p2 = p2;
}
public PointF P1
{
get { return p1; }
set { p1 = value; }
}
public PointF P2
{
get { return p2; }
set { p2 = value; }
}
public float X1
{
get { return p1.X; }
set { p1.X = value; }
}
public float X2
{
get { return p2.X; }
set { p2.X = value; }
}
public float Y1
{
get { return p1.Y; }
set { p1.Y = value; }
}
public float Y2
{
get { return p2.Y; }
set { p2.Y = value; }
}
}
public struct Polygon: IEnumerable<PointF>
{
private PointF[] points;
public Polygon(PointF[] points)
{
this.points = points;
}
public PointF[] Points
{
get { return points; }
set { points = value; }
}
public int Length
{
get { return points.Length; }
}
public PointF this[int index]
{
get { return points[index]; }
set { points[index] = value; }
}
public static implicit operator PointF[](Polygon polygon)
{
return polygon.points;
}
public static implicit operator Polygon(PointF[] points)
{
return new Polygon(points);
}
IEnumerator<PointF> IEnumerable<PointF>.GetEnumerator()
{
return (IEnumerator<PointF>)points.GetEnumerator();
}
public IEnumerator GetEnumerator()
{
return points.GetEnumerator();
}
}
public enum Intersection
{
None,
Tangent,
Intersection,
Containment
}
public static class Geometry
{
public static Intersection IntersectionOf(Line line, Polygon polygon)
{
if (polygon.Length == 0)
{
return Intersection.None;
}
if (polygon.Length == 1)
{
return IntersectionOf(polygon[0], line);
}
bool tangent = false;
for (int index = 0; index < polygon.Length; index++)
{
int index2 = (index + 1)%polygon.Length;
Intersection intersection = IntersectionOf(line, new Line(polygon[index], polygon[index2]));
if (intersection == Intersection.Intersection)
{
return intersection;
}
if (intersection == Intersection.Tangent)
{
tangent = true;
}
}
return tangent ? Intersection.Tangent : IntersectionOf(line.P1, polygon);
}
public static Intersection IntersectionOf(PointF point, Polygon polygon)
{
switch (polygon.Length)
{
case 0:
return Intersection.None;
case 1:
if (polygon[0].X == point.X && polygon[0].Y == point.Y)
{
return Intersection.Tangent;
}
else
{
return Intersection.None;
}
case 2:
return IntersectionOf(point, new Line(polygon[0], polygon[1]));
}
int counter = 0;
int i;
PointF p1;
int n = polygon.Length;
p1 = polygon[0];
if (point == p1)
{
return Intersection.Tangent;
}
for (i = 1; i <= n; i++)
{
PointF p2 = polygon[i % n];
if (point == p2)
{
return Intersection.Tangent;
}
if (point.Y > Math.Min(p1.Y, p2.Y))
{
if (point.Y <= Math.Max(p1.Y, p2.Y))
{
if (point.X <= Math.Max(p1.X, p2.X))
{
if (p1.Y != p2.Y)
{
double xinters = (point.Y - p1.Y) * (p2.X - p1.X) / (p2.Y - p1.Y) + p1.X;
if (p1.X == p2.X || point.X <= xinters)
counter++;
}
}
}
}
p1 = p2;
}
return (counter % 2 == 1) ? Intersection.Containment : Intersection.None;
}
public static Intersection IntersectionOf(PointF point, Line line)
{
float bottomY = Math.Min(line.Y1, line.Y2);
float topY = Math.Max(line.Y1, line.Y2);
bool heightIsRight = point.Y >= bottomY &&
point.Y <= topY;
//Vertical line, slope is divideByZero error!
if (line.X1 == line.X2)
{
if (point.X == line.X1 && heightIsRight)
{
return Intersection.Tangent;
}
else
{
return Intersection.None;
}
}
float slope = (line.X2 - line.X1)/(line.Y2 - line.Y1);
bool onLine = (line.Y1 - point.Y) == (slope*(line.X1 - point.X));
if (onLine && heightIsRight)
{
return Intersection.Tangent;
}
else
{
return Intersection.None;
}
}
}
Do http://mathworld.wolfram.com/Line-LineIntersection.html for the line and each side of the rectangle.
Or: http://mathworld.wolfram.com/Line-PlaneIntersection.html
since it is missing i'll just add it for completeness
public static Intersection IntersectionOf(Line line1, Line line2)
{
// Fail if either line segment is zero-length.
if (line1.X1 == line1.X2 && line1.Y1 == line1.Y2 || line2.X1 == line2.X2 && line2.Y1 == line2.Y2)
return Intersection.None;
if (line1.X1 == line2.X1 && line1.Y1 == line2.Y1 || line1.X2 == line2.X1 && line1.Y2 == line2.Y1)
return Intersection.Intersection;
if (line1.X1 == line2.X2 && line1.Y1 == line2.Y2 || line1.X2 == line2.X2 && line1.Y2 == line2.Y2)
return Intersection.Intersection;
// (1) Translate the system so that point A is on the origin.
line1.X2 -= line1.X1; line1.Y2 -= line1.Y1;
line2.X1 -= line1.X1; line2.Y1 -= line1.Y1;
line2.X2 -= line1.X1; line2.Y2 -= line1.Y1;
// Discover the length of segment A-B.
double distAB = Math.Sqrt(line1.X2 * line1.X2 + line1.Y2 * line1.Y2);
// (2) Rotate the system so that point B is on the positive X axis.
double theCos = line1.X2 / distAB;
double theSin = line1.Y2 / distAB;
double newX = line2.X1 * theCos + line2.Y1 * theSin;
line2.Y1 = line2.Y1 * theCos - line2.X1 * theSin; line2.X1 = newX;
newX = line2.X2 * theCos + line2.Y2 * theSin;
line2.Y2 = line2.Y2 * theCos - line2.X2 * theSin; line2.X2 = newX;
// Fail if segment C-D doesn't cross line A-B.
if (line2.Y1 < 0 && line2.Y2 < 0 || line2.Y1 >= 0 && line2.Y2 >= 0)
return Intersection.None;
// (3) Discover the position of the intersection point along line A-B.
double posAB = line2.X2 + (line2.X1 - line2.X2) * line2.Y2 / (line2.Y2 - line2.Y1);
// Fail if segment C-D crosses line A-B outside of segment A-B.
if (posAB < 0 || posAB > distAB)
return Intersection.None;
// (4) Apply the discovered position to line A-B in the original coordinate system.
return Intersection.Intersection;
}
note that the method rotates the line segments so as to avoid direction-related problems
Use class:
System.Drawing.Rectangle
Method:
IntersectsWith();
If it is 2d, then all lines are on the only plane.
So, this is basic 3-D geometry. You should be able to do this with a straightforward equation.
Check out this page:
http://local.wasp.uwa.edu.au/~pbourke/geometry/planeline/.
The second solution should be easy to implement, as long as you translate the coordinates of your rectangle into the equation of a plane.
Furthermore, check that your denominator isn't zero (line doesn't intersect or is contained in the plane).
I hate browsing the MSDN docs (they're awfully slow and weird :-s) but I think they should have something similar to this Java method... and if they haven't, bad for them! XD (btw, it works for segments, not lines).
In any case, you can peek the open source Java SDK to see how is it implemented, maybe you'll learn some new trick (I'm always surprised when I look other people's code)
Isn't it possible to check the line against each side of the rectangle using simple line segment formula.

Categories