Gift wrapping algorithm - c#

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

Related

Drawing points along path spirally

Well, I'm trying to optimize what I did here (Smoothing noises with different amplitudes (Part 2)).
By this reason, I did a new implementation from scratch (https://youtu.be/o7pVEXhh3TI) to draw the path:
private void Start()
{
Polygon pol = File.ReadAllText(PolyPath).Deserialize<Polygon>();
// Create tex object
var list = pol.Vertices.AsEnumerable();
tex = list.CreateTextureObject(pol.Position, offset);
exampleTexture = new Texture2D(tex.Width, tex.Height);
exampleTexture.SetPixels32(new Color32[tex.Width * tex.Height]);
exampleTexture.Apply();
vertices = pol.Vertices.Select(v => (v - pol.Position) + offset).Clone().ToList();
_ss = new List<Segment>(pol.Segments.Select(s => new Segment((s.start + pol.Center - pol.Position) + offset, (s.end + pol.Center - pol.Position) + offset)));
foreach (Segment curSeg in _ss)
for (int i = -effectDistance; i < effectDistance; ++i)
{
Vector2 perp = Vector2.Perpendicular(((Vector2)curSeg.start - (Vector2)curSeg.end)).normalized;
segments.Add((Vector2)curSeg.start + perp * i);
F.DrawLine((Vector2)curSeg.start + perp * i, (Vector2)curSeg.end + perp * i, (x, y) => layers.Add(new Point(x, y)));
}
Debug.Log("Layer Count: " + layers.Count);
drawPath = true;
}
private void OnGUI()
{
if (exampleTexture == null)
return;
GUI.DrawTexture(new Rect((Screen.width - tex.Width) / 2, (Screen.height - tex.Height) / 2, tex.Width, tex.Height), exampleTexture);
if (drawPath)
{
{
Point? cur = layers.Count > 0 ? (Point?)layers.First() : null;
if (cur.HasValue)
{
exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(170, 0, 0, 255));
exampleTexture.Apply();
layers.Remove(cur.Value);
}
}
{
Point? cur = segments.Count > 0 ? (Point?)segments.First() : null;
if (cur.HasValue)
{
exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(0, 170, 0, 255));
exampleTexture.Apply();
segments.Remove(cur.Value);
}
}
{
Point? cur = vertices.Count > 0 ? (Point?)vertices.First() : null;
//Debug.Log(cur);
if (cur.HasValue)
{
exampleTexture.SetPixel(cur.Value.x, cur.Value.y, new Color32(255, 128, 0, 255));
exampleTexture.Apply();
vertices.Remove(cur.Value);
}
}
if (vertices.Count == 0 && segments.Count == 0 && layers.Count == 0)
drawPath = false;
}
}
This is what DrawLines actually do:
public static class F
{
public static void DrawLine(Point p1, Point p2, Action<int, int> action)
{
DrawLine(p1.x, p1.y, p2.x, p2.y, action);
}
public static void DrawLine(int x0, int y0, int x1, int y1, Action<int, int> action)
{
int sx = 0,
sy = 0;
int dx = Mathf.Abs(x1 - x0),
dy = Mathf.Abs(y1 - y0);
if (x0 < x1) { sx = 1; } else { sx = -1; }
if (y0 < y1) { sy = 1; } else { sy = -1; }
int err = dx - dy,
e2 = 0;
while (true)
{
action?.Invoke(x0, y0);
if ((x0 == x1) && (y0 == y1))
break;
e2 = 2 * err;
if (e2 > -dy)
{
err = err - dy;
x0 = x0 + sx;
}
if (e2 < dx)
{
err = err + dx;
y0 = y0 + sy;
}
}
}
}
This is an implemenentation of Bresenham algorithm.
This implementation is better because I have lowered iterations from 280k to 6k, but there is an issue as you can see this is innacurate...
The way this works first is getting the perpendicular of each segment on the shape (green pixels) and then drawing lines between the start and the end point of that segment. Segmenents are obtained using Ramer-Douglas-Peucker algorithm.
So I was thinking on draw the "orange" path spirally. I don't know how to explain this, basically, obtaining the same path but, with an scale (Translating/transforming? list of points from its center with an offset/distance) but I think I will have the same innacuracy.
Any guide will be appreciated. What algorithm could I use to draw the path with "layers"?
Following some of the information here, you might be able to use "inward/outward polygon offsetting" (aka "polygon buffering") to get the result you are interested in.
A tool such as Clipper can help.
Once you have a way to outwardly offset your shape, do the following:
First, draw the outer shape (black region below), then offset the inner shape outwards as far as you need it to go, and draw it on top of the outer shape (brown region below) using an appropriate noise/color scheme:
Then, apply a smaller offset, then draw that shape on top using a different noise/colorscheme (orange region below).
Repeat until you have as many gradients as you need:
Finally, draw the inner shape without any offsetting with its noise/color scheme:

Stack Overflow Exception with Quad Tree

I am experiencing an issue with a Quad Tree implementation I am working on in C#. In the file Tree.cs, the following line will cause a Stack Overflow Exception, starting consistently around 50 objects in the tree (probably enough to cause the first branch of the bottom right quad):
else
{
//bottom right
TreeList[3].PushB(b);
return;
}
For some reason it seems that, when I allow this code to be called, it creates an infinite loop, hence the Stack Overflow Exception. I am not seeing why this would cause an infinite recursion while the others don't.
Here's the code. Ball.cs and Tree.cs both reside in a Classes folder.
Ball.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QuadTree.Classes
{
class Ball
{
protected int x, y, r;
protected decimal vx, vy;
public static int min_w = 0,
max_w = 200,
min_h = 0,
max_h = 200;
//treating origin as top-left of screen
public Ball(int set_x = 1, int set_y = 1, decimal set_vx = 1, decimal set_vy = 1, int set_r = 1)
{
x = set_x;
y = set_y;
vx = set_vx;
vy = set_vy;
r = set_r;
}
public int get_x()
{
return x;
}
public int get_y()
{
return y;
}
public void Print()
{
Console.WriteLine("x: {0} y: {1} vx: {2} vy: {3} r: {4}", x, y, vx, vy, r);
}
//get the y-intercept of the current ball
protected decimal getB()
{
return (decimal)y - ((vy / vx) * (decimal)x);
}
//get the y-intercept given an x, y, and slope
public decimal getB(int x, int y, decimal m)
{
return (decimal)y - (m * (decimal)x);
}
//get the slop of the line that goes through both balls
protected decimal getM(Ball b)
{
return getM(y, b.y, x, b.x);
}
//get the slop of the line going through two points
public decimal getM(int y1, int y2, int x1, int x2)
{
if (x1 - x2 == 0)
{
return 0;
}
else
{
return ((decimal)(y1 - y2)) / ((decimal)(x1 - x2));
}
}
public void Move()
{
x += (int)vx;
y += (int)vy;
if (x > max_w)
{
vx *= -1;
x = x - (x - max_w);
}
else if (x < min_w)
{
vx *= -1;
x *= -1; //won't work if min_w != 0
}
if(y > max_h)
{
vy *= -1;
y = y - (y - max_h);
}
else if (y < min_h)
{
vy *= -1;
y *= -1; //won't work if min_h !=0
}
}
//detect if the current ball collides with the given ball
public void Collide(Ball b)
{
decimal d;
d = (decimal)Math.Sqrt(Math.Pow((x - b.x), 2) + Math.Pow((y - b.y), 2));
if (d<= r || d <= b.r)
{
ResolveCollision(b);
}
return;
}
//determine the resulting vectors after the collision
private void ResolveCollision(Ball b)
{
//get the line between the center points
decimal M;
M = getM(b);
//determine angle between the line and ball a
double theta_1;
if (b.vx != 0)
{
double top = (double)((M - (b.vy / b.vx)));
double bottom = (double)(1 + (M * (b.vy / b.vx)));
if (bottom != 0)
{
theta_1 = Math.Atan(top / bottom);
}
else
{
theta_1 = 0;
}
}
else
{
if (1 + M != 0)
{
theta_1 = Math.Atan((double)(M / (1 + M)));
}
else
{
theta_1 = 0;
}
}
theta_1 = theta_1 * (Math.PI / 180);
//calculate new vx and vy for ball a
//http://www.gamefromscratch.com/post/2012/11/24/GameDev-math-recipes-Rotating-one-point-around-another-point.aspx
double new_vx, new_vy;
new_vx = Math.Cos(theta_1) * (double)(vx) - Math.Sin(theta_1) * (double)(vy) + x;
new_vy = Math.Sin(theta_1) * (double)(vx) + Math.Cos(theta_1) * (double)(vy) + y;
vx = (decimal)new_vx - x;
vy = (decimal)new_vy - y;
//determine angle between the line and ball b
if (b.vx != 0)
{
double top = (double)((M - (b.vy / b.vx)));
double bottom = (double)(1 + (M * (b.vy / b.vx)));
if (bottom != 0)
{
theta_1 = Math.Atan(top / bottom);
}
else
{
theta_1 = 0;
}
}
else
{
if (1 + M != 0)
{
theta_1 = Math.Atan((double)(M / (1 + M)));
}
else
{
theta_1 = 0;
}
}
theta_1 = theta_1 * (Math.PI / 180);
//calculate new vx and vy for ball a
new_vx = Math.Cos(theta_1) * (double)(b.vx) - Math.Sin(theta_1) * (double)(b.vy) + b.x;
new_vy = Math.Sin(theta_1) * (double)(b.vx) + Math.Cos(theta_1) * (double)(b.vy) + b.y;
b.vx = (decimal)new_vx - x;
b.vy = (decimal)new_vy - y;
}
}
}
Tree.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QuadTree.Classes
{
class Tree //: IDisposable
{
protected int min_w,
max_w,
min_h,
max_h,
thresh_hold, level;
bool leaf = true;
protected List<Ball> BallList = new List<Ball>();
protected List<Tree> TreeList = new List<Tree>();
public Tree(int set_min_w, int set_max_w, int set_min_h, int set_max_h, int set_thresh_hold, int set_level)
{
min_w = set_min_w;
max_w = set_max_w;
min_h = set_min_h;
max_h = set_max_h;
thresh_hold = set_thresh_hold;
level = set_level;
}
//push a ball onto the tree
public void PushB(Ball b)
{
if(leaf)
{
BallList.Add(b);
if (BallList.Count > thresh_hold)
{
Branch();
}
}
else
{
LeafPush(b); //push the ball to a leaf node
}
return;
}
//push a ball onto a leaf of the current node
protected void LeafPush(Ball b)
{
if (b.get_x() <= max_w / 2)
{
//left
if (b.get_y() <= max_h / 2)
{
//top left
TreeList[0].PushB(b);
return;
}
else
{
//bottom left
TreeList[2].PushB(b);
return;
}
}
else
{
//right
if (b.get_y() <= max_h / 2)
{
//top right
TreeList[1].PushB(b);
return;
}
else
{
//bottom right
TreeList[3].PushB(b);
return;
}
}
}
private void Branch()
{
Console.WriteLine("Branching level {0}", level);
leaf = false;
TreeList.Add(new Tree(min_w, max_w / 2, min_h, max_h / 2, thresh_hold, level + 1)); //top left
TreeList.Add(new Tree((max_w / 2) + 1, max_w, min_h, max_h / 2, thresh_hold, level + 1)); //top right
TreeList.Add(new Tree(min_w, max_w / 2, (max_h / 2) + 1, max_h, thresh_hold, level + 1)); //bottom left
TreeList.Add(new Tree((max_w / 2) + 1, max_w, (max_h / 2) + 1, max_h, thresh_hold, level + 1)); //bottom right
foreach(Ball b in BallList)
{
LeafPush(b);
}
BallList.Clear();
return;
}
}
}
Program.cs
using QuadTree.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace QuadTree
{
class Program
{
static void Main(string[] args)
{
Random rnd = new Random();
List<Ball> BallList = new List<Ball>();
for (int i = 0; i < 100; i++)
{
BallList.Add(new Ball(rnd.Next(Ball.min_w, Ball.max_w),
rnd.Next(Ball.min_h, Ball.max_h),
rnd.Next(1, 5),
rnd.Next(1, 5),
rnd.Next(1, 5)));
}
Tree t = new Tree(Ball.min_w, Ball.max_w, Ball.min_h, Ball.max_h, 10, 0);
foreach (Ball b in BallList)
{
b.Move();
t.PushB(b);
}
Console.ReadLine();
}
}
}
You need to revise the way you're creating the sub-trees. When you create the fourth sub-tree (bottom right quadrant), you're using the following numbers:
(max_w / 2) + 1, max_w, (max_h / 2) + 1, max_h
This always results in the same dimensions (101, 200, 101, 200) for the bottom right quadrant branch because you're only using the maximum numbers. This is true for the bottom right quadrant in every subsequent branch as well.
The program will run fine until you hit the threshold on that fourth sub-tree. It then attempts to branch, and as it branches, it sends all of it's balls into the subsequent fourth sub-tree. This will keep occurring because all of those balls have coordinates in that quadrant. That is where your infinite loop is occurring.
If you're trying to keep subdividing the quadrants, then you need to base the new dimensions off both the minimum and maximum widths and heights of the parent quadrant.
EDIT:
This code should subdivide the quadrants properly:
int center_w = min_w + (max_w - min_w) / 2;
int center_h = min_h + (max_h - min_h) / 2;
TreeList.Add(new Tree(min_w, center_w, min_h, center_h,
thresh_hold, level + 1)); // top left
TreeList.Add(new Tree(center_w + 1, max_w, min_h, center_h,
thresh_hold, level + 1)); //top right
TreeList.Add(new Tree(min_w, center_w, center_h + 1, max_h,
thresh_hold, level + 1)); //bottom left
TreeList.Add(new Tree(center_w + 1, max_w, center_h + 1, max_h,
thresh_hold, level + 1)); //bottom right
So it seems that I needed logic to test if creating a new set of nodes would be viable. I decided I wanted a node to have a minimum width of 10, so I changed this code:
//push a ball onto the tree
public void PushB(Ball b)
{
if(leaf)
{
BallList.Add(b);
if (BallList.Count > thresh_hold)
{
//test if branching will produce a viable area
if ((max_w / 2) - min_w >= 10)
{
Branch();
}
}
}
else
{
LeafPush(b); //push the ball to a leaf node
}
return;
}
And now I no longer get the Stack Overflow Exception

Best method to check if a point lies on an arc in c#

What I want to do should be simple but it has been a while since I studied math.
Let's say I have Point and Arc classes as below. How can I check if the Point p lies on Arc a.
public class Point
{
public double X;
public double Y;
}
public class Arc
{
public double Radius;
public double StartAngle;
public double EndAngle;
// center of the arc
public double Xc;
public double Yc;
}
Point p = new Point() { X = 5, Y = 5 };
Arc a = new Arc()
{
Radius = 5,
StartAngle = 0,
EndAngle = Math.PI/2,
Xc = 0,
Yc = 0
};
I came up with this answer which is posted here for future reference. I know it is not the most efficient method but it does the job.
// first check if the point is on a circle with the radius of the arc.
// Next check if it is between the start and end angles of the arc.
public static bool IsPointOnArc(Point p, Arc a)
{
if (p.Y * p.Y == a.Radius * a.Radius - p.X * p.X)
{
double t = Math.Acos(p.X / a.Radius);
if (t >= a.StartAngle && t <= a.EndAngle)
{
return true;
}
}
return false;
}

Translating concave hull algorithm to 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;
}

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)

Categories