Line rasterisation: Cover all pixels, regardless of line gradient? - c#

Basically, I want to use a line algo to determine which cells to check for collisions for my raycaster.
Bresenham isn't great for this as it uses a unified-thickness approach, meaning that it ignores cells that aren't at least half-covering the line. Not great at all, because it means that some segments of my line aren't being checked for intersections with the cells, leading to errors.
I can't seem to find any "thick-line" algorithms, can anyone help me find one?
Green: What I would like.
Red: What I currently have and don't want.

I had exactly the same problem as you and found an very simple solution. Usually, Bresenham has two consecutive if's to determine whether it should increase the coordinate for the two dimensions:
public void drawLine(int x0, int y0, int x1, int y1, char ch) {
int dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy, e2; // error value e_xy
for (;;) {
put(x0, y0, ch);
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
// horizontal step?
if (e2 > dy) {
err += dy;
x0 += sx;
}
// vertical step?
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
Now all you have to do is to insert an else before the second if:
public void drawLineNoDiagonalSteps(int x0, int y0, int x1, int y1, char ch) {
int dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy, e2;
for (;;) {
put(x0, y0, ch);
if (x0 == x1 && y0 == y1) break;
e2 = 2 * err;
// EITHER horizontal OR vertical step (but not both!)
if (e2 > dy) {
err += dy;
x0 += sx;
} else if (e2 < dx) { // <--- this "else" makes the difference
err += dx;
y0 += sy;
}
}
}
Now the algorithm doesn't change both coordinates at once anymore.
I haven't thoroughly tested this but it seems to work pretty well.

This thread old, but I thought it'd be worth putting this on the Internet:
// This prints the pixels from (x, y), increasing by dx and dy.
// Based on the DDA algorithm (uses floating point calculations).
void pixelsAfter(int x, int y, int dx, int dy)
{
// Do not count pixels |dx|==|dy| diagonals twice:
int steps = Math.abs(dx) == Math.abs(dy)
? Math.abs(dx) : Math.abs(dx) + Math.abs(dy);
double xPos = x;
double yPos = y;
double incX = (dx + 0.0d) / steps;
double incY = (dy + 0.0d) / steps;
System.out.println(String.format("The pixels after (%d,%d) are:", x, y));
for(int k = 0; k < steps; k++)
{
xPos += incX;
yPos += incY;
System.out.println(String.format("A pixel (%d) after is (%d, %d)",
k + 1, (int)Math.floor(xPos), (int)Math.floor(yPos)));
}
}

Without loss of generality, assume x2 >= x1, then
int x = floor(x1);
int y = floor(y1);
double slope = (x2 - x1) / (y2 - y1);
if (y2 >= y1) {
while (y < y2) {
int r = floor(slope * (y - y1) + x1);
do {
usepixel(x, y);
++x;
} while (x < r);
usepixel(x, y);
++y;
}
}
else {
while (y > y2) {
int r = floor(slope * (y - y1) + x1);
do {
usepixel(x, y);
++x;
} while (x < r);
usepixel(x, y);
--y;
}
}
The floor calls can probably be written just as a cast-to-integer.

There is an interesting article available in GPU Gems, maybe it can help you: Chapter 22. Fast Prefiltered Lines

What about Bresenham with an additional constraint that no diagonal moves are allowed: Generate the points with the traditional algorithm, then as a post-processing step insert extra steps needed to make only orthogonal movements.

You could find all the intersections your ray has with the horizontal grid lines, and then mark all the cells on a row that either have an intersection point on one side, or are between the two cells with the intersections on the row.
Finding the intersections can be done by starting from the origin, advancing the point to the first intersection (and marking the cells in the process), finding out the vector that takes you from an intersection to the next (both these operations are basic similar triangles (or trig)) and then advancing column by column until you've gone far enough. Advancing column by column involves one vector addition per column, and a small loop to fill in the cells between the ones with intersections. Replace "mark" with "process" if you're processing the cells on the fly - this algorithm is guaranteed to mark each cell only once.
The same could be done with the vertical lines, but grids are generally stored in horizontal slices so I chose that. If you're using trig, you'll need to handle straight horizontal lines with a special case.
By the way, as far as I know, this is how old grid-based raycaster "3D" games (like Wolfenstein 3D) were done. I first read about this algorithm from this book, eons ago.

Related

Integer points inside a circle - problem with recursion

I am trying to write the program which will find points that have integer coords inside a circle. Program should read circle's radius from the user.
Sample correct answers are below picture
I need to write it using iteration and recursion. Iteration works correctly for radius=100:
static int FindPointsByIteration(double minusRadius, double radius)
{
int result = 0;
for (int x = (int)minusRadius; x <= (int)radius; x += 1)
{
for (int y = (int)minusRadius; y <= (int)radius; y += 1)
{
if (((x * x) + (y * y)) < (radius * radius)) result++;
}
}
return result;
}
Max radius without stack overflow is 69 if I open EXE file. In VS it's 62. Is it possible to optimize it?
static int FindPointsByRecursion(double x, double y, double radius, int result)
{
//Console.WriteLine($"x: {x}, y: {y},radius: {radius}, result: {result}");
if (((x * x) + (y * y)) < (radius * radius)) result++;
if (y >= -1 * (int)radius)
{
if (x <= (int)radius)
{
x++;
return FindPointsByRecursion(x, y, radius, result);
}
x = -1 * (int)radius;
y--;
return FindPointsByRecursion(x, y, radius, result);
}
return result;
}
I think you can simplified FindPointsByIteration by only accept one parameter: radius. The problem description states it is a circle (not an oval), it only features one radius. Moreover, radius is a scalar not a vector. It has the same quantity from any directions. Therefore, radius could also represent minusRadius. E.g.
static int FindPointsByIteration(double radius)
{
int points = 0;
for (int x = (int)-radius; x <= radius; x++)
{
for (int y = (int)-radius; y <= radius; y++)
{
if (((x * x) + (y * y)) < (radius * radius))
{
points++;
}
}
}
return points;
}
Above function performs (2R)^2 iteration. FindPointsByIteration(2) takes 16 iterations. FindPointsByIteration(100) takes 40,000 iterations. For iteration, it is fine. For recursion, it may be too much. We need to think of another tactic.
As it is a circle, we can cut it into 4 quadrants. We only count the points in quadrant I and multiple it by 4, the result should be close to the solution. However, there is a catch. We should not multiple the origin and the point lie on the axis e.g.( [0,y] and [x,0]) since they share between quadrants.
Consider a circle of radius 6:
There are 3 main category of its integer coordinate.
Origin (in red).
it is always [0,0]. Any circle should have one
Points on axis (in green).
It depends on the radius. Circle with radius > 1 would have. The number equals to the greatest integer less than the radius times 4.
Points inside quadrant (in blue)
Take quadrant I as example. We count the points by bottom-up, right-to-left approach. Start with [5,1] ([6,1] is known out-of-scope). If [5,1] is inside the scope, then the points for y:1 is 5. It is because any points with [5, 1<y<5] must be inside the scope (e.g. [4,1], [3,1] etc). Then we can move one row up at [5,2] and start the iteration again. Until you meet a point which is outside scope [5,4], then you move 1 column left [4,4] and start the iteration again. This way we can greatly reduce the number of recursion.
Example
static int FindPointsByRecursion(double radius)
{
if (radius < 0) { return 0; }
int points = 0;
// origin
if (radius > 0) { points++; }
// points on axis
int longestEdge = IntegerSmallerThan(radius);
points += longestEdge * 4;
// points contained in quadrant
points += FindPointsInQuadrant(1, longestEdge, radius) * 4;
return points;
}
// return the greatest integer just smaller then n
static int IntegerSmallerThan(double n)
{
int i = (int)n;
return (n == i) ? --i : i;
}
static int FindPointsInQuadrant(int x, int y, double radius)
{
// out of bound, return 0
if (x >= radius || y < 1) return 0;
if ((x * x) + (y * y) < (radius * radius))
{
// if inside scope, move 1 row up
return y + FindPointsInQuadrant(x + 1, y, radius);
}
else
{
// if outside scope, move 1 column left
return FindPointsInQuadrant(x, y - 1, radius);
}
}
Main
Console.WriteLine("FindPointsByIteration");
Console.WriteLine(FindPointsByIteration(1));
Console.WriteLine(FindPointsByIteration(2));
Console.WriteLine(FindPointsByIteration(2.1));
Console.WriteLine(FindPointsByIteration(2.5));
Console.WriteLine(FindPointsByIteration(5));
Console.WriteLine(FindPointsByIteration(100));
Console.WriteLine("\nFindPointsByRecursion");
Console.WriteLine(FindPointsByRecursion(1));
Console.WriteLine(FindPointsByRecursion(2));
Console.WriteLine(FindPointsByRecursion(2.1));
Console.WriteLine(FindPointsByRecursion(2.5));
Console.WriteLine(FindPointsByRecursion(5));
Console.WriteLine(FindPointsByRecursion(100));
Output
FindPointsByIteration
1
9
13
21
69
31397
FindPointsByRecursion
1
9
13
21
69
31397
Counting points in one quarter is a very good idea, but I think I found easier solution (of course with your help). Here's code that calls a method for the first time:
points=4* FindPointsByRecursion(radius, 1, (int)radius, points);
points+= ((int)radius - 1) * 4 + 1;
And a counting method here:
static int FindPointsByRecursion(double radius, int x, int y, int points)
{
if (y < 1) return points;
if ((x * x) + (y * y) < (int)(radius * radius))
{
points++;
return FindPointsByRecursion(radius, x + 1, y, points);
}
else
{
x = 1;
return FindPointsByRecursion(radius, x, y - 1, points);
}
return points;
}
It needs only one method.

Get a unique result from Bresenham algorithm

I want to rasterize a line using Bresenham's algorihm. My interpolated vertices shouldn't consist of diagonal steps. I did some search on StackOverflow and this topic seems to pretty much the thing I need.
The only problem I have with it is that I need to get the Same result if I changed the order of inputs, I mean if I swap the startPoint and endPoint of line I need to get the same set of interpolated vertices.
//the Method definition
List<Point> plotPoints(Point startPoint, Point endPoint);
//The thing I'm looking for
plotPoints(startPoint, endPoint)==plotPoints(endPoint, startPoint)
The code is almost same as The answer. However I did a bit customization for my purpose:
private float step=0.5;
public static List<Vector3> plotPoints(float x0, float y0, float x1, float y1) {
List<Vector3> plottedPoints = new List<Vector3>();
float dx = Mathf.Abs(x1 - x0), sx = x0 < x1 ? step : -step;
float dy = -Mathf.Abs(y1 - y0), sy = y0 < y1 ? step: -step;
float err = dx + dy, e2; /* error value e_xy */
for (; ; ) { /* loop */
if (x0 == x1 && y0 == y1) break;
plottedPoints.Add(new Vector3(x0,0, y0));
e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
else if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
}
return plottedPoints;
}
As said in the comments, a trick is to normalize the input so that if you swap the endpoints, they will be swapped back automatically.
A possible way is by enforcing that the endpoints are lexicographically ordered (the smallest X first and in case of a tie, the smallest Y).
There is no such problem with the DDA algorithm, which can be formulated explicitly as
X = X0 + (k.DX) rnd D
Y = Y0 + (k.DY) rnd D
where D = max(|DX|, |DY|), rnd is a rounding operation and k goes from 0 to D.
If you swap the endpoints, the deltas change sign and you trade
Y0 + (k.DY) rnd D
for
Y1 + ((D-k).(-DY)) rnd D = Y1 - DY + (k.DY) rnd D = Y0 + (k.DY) rnd D.
This means that the algorithm is naturally "reversible".
For this to be true, the rnd operation must enjoy a translation invariance property, rnd(x+n) = rnd(x)+n, which corresponds to (k.DY) rnd D = floor((k.DY + S) / D) where S is some rounding offset (typically 0 or D>>1).

Generating Sampling points on a 3D Triangle with the help of Straightforward sampling or Bresenham type sampling [duplicate]

This question already has answers here:
Algorithm to fill triangle
(3 answers)
Closed 5 years ago.
I have a triangle with 3 vertices,namely: (x1,y1,z1); (x2,y2,z2) and (x3,y3,z3).
I am using Bresenhams 3D Line algorithm,currently for generating 3D Points between two vertices of the triangle,depending on the resolution size(resSize).
void Bresenham3Algo(float x0, float y0, float z0, float x1, float y1, float z1)
{
float dx = Math.Abs(x1 - x0);
float sx = x0 < x1 ? resSize : -resSize;
float dy = Math.Abs(y1 - y0);
float sy = y0 < y1 ? resSize : -resSize;
float dz = Math.Abs(z1 - z0);
float sz = z0 < z1 ? resSize : -resSize;
float dm = Math.Max(dx, Math.Max(dy, dz)), i = dm;
x1 = y1 = z1 = dm / 2;
for (; ; )
{
Console.WriteLine(x0,y0,z0); //Printing points here
if (i <= 0) break;
x1 -= dx; if (x1 < 0) { x1 += dm; x0 += sx; }
y1 -= dy; if (y1 < 0) { y1 += dm; y0 += sy; }
z1 -= dz; if (z1 < 0) { z1 += dm; z0 += sz; }
i -= resSize;
}
}
So, As of now,I am calling the above function three times to generate 3D Sampling points on the boundary of the three Triangular edges.
Bresenham3Algo(x1,y1,z1,x2,y2,z2);
Bresenham3Algo(x2,y2,z2,x3,y3,z3);
Bresenham3Algo(x3,y3,z3,x1,y1,z1);
I am finding it difficult to find the internal sampling points lying inside the triangle.
For example,If I have the vertices (0,0,0); (5,0,0) and (3,3,0), With the help of the above function, I find 3D Points on the three triangular edges i.e.
(0,0,0),(1,0,0),(2,0,0),(3,0,0),(4,0,0),(5,0,0) -> first Edge
(3,3,0),(4,1,0),(4,2,0),(5,0,0) ->Second Edge
(0,0,0),(1,1,0),(2,2,0),(3,3,0) -> Third Edge
Now,I need to find the internal 3D Sampling points,lying inside the triangle i.e. (2,1,0) , (3,1,0), (3,2,0)
I would be glad,if someone can help me with this algo.
Thanks in Advance!
Assuming you aren't constrained to a regular grid, you do the following:
Rotate the triangle onto the x-y plane.
Draw and fill the triangle using your favourite algorithm in 2D (e.g. Bresenham).
Add z-values (all zero) to the points drawn
Rotate back to the original orientation.

Traverse Pixels in a circle from the center

I need an algorithm like Bresenham's circle algorithm, but with some modifications.
The algorithm must visit all pixels in the radius (so essentially a fill).
The algorithm must start from the center of the circle
It must visit all points that would normally be visited (no holes)
It must visit each point in the circle exactly once
One technique I came up with would first determine all pixel coordinates inside the circle by just going through the rectangle of the circle and checking with Math.Sqrt if it is inside the circle.
Then it would order the pixels by distance and then visit each of them.
That would be exactly what I want, with the exception of being fast.
So my questions is:
Is there a fast way to do this without fetching,ordering and then visiting each pixel?
Just for clarification I do not actually want to draw onto the image, I only want to traverse them in the described order.
First, we can use fact, that circle can be divided in 8 octants. So we just need to fill single octant and use simple +- coordinate change to get full circle. So if we try to fill only one octant, we need to worry only about 2 directions from center : left and left top. Also, clever use of data structures like priority queue (.NET doesn't have it, so you need to find it somewhere else) and hash map can drastically improve performance.
/// <summary>
/// Make sure it is structure.
/// </summary>
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public int DistanceSqrt()
{
return X * X + Y * Y;
}
}
/// <summary>
/// Points ordered by distance from center that are on "border" of the circle.
/// </summary>
public static PriorityQueue<Point> _pointsToAdd = new PriorityQueue<Point>();
/// <summary>
/// Set of pixels that were already added, so we don't visit single pixel twice. Could be replaced with 2D array of bools.
/// </summary>
public static HashSet<Point> _addedPoints = new HashSet<Point>();
public static List<Point> FillCircle(int radius)
{
List<Point> points = new List<Point>();
_pointsToAdd.Enqueue(new Point { X = 1, Y = 0 }, 1);
_pointsToAdd.Enqueue(new Point { X = 1, Y = 1 }, 2);
points.Add(new Point {X = 0, Y = 0});
while(true)
{
var point = _pointsToAdd.Dequeue();
_addedPoints.Remove(point);
if (point.X >= radius)
break;
points.Add(new Point() { X = -point.X, Y = point.Y });
points.Add(new Point() { X = point.Y, Y = point.X });
points.Add(new Point() { X = -point.Y, Y = -point.X });
points.Add(new Point() { X = point.X, Y = -point.Y });
// if the pixel is on border of octant, then add it only to even half of octants
bool isBorder = point.Y == 0 || point.X == point.Y;
if(!isBorder)
{
points.Add(new Point() {X = point.X, Y = point.Y});
points.Add(new Point() {X = -point.X, Y = -point.Y});
points.Add(new Point() {X = -point.Y, Y = point.X});
points.Add(new Point() {X = point.Y, Y = -point.X});
}
Point pointToLeft = new Point() {X = point.X + 1, Y = point.Y};
Point pointToLeftTop = new Point() {X = point.X + 1, Y = point.Y + 1};
if(_addedPoints.Add(pointToLeft))
{
// if it is first time adding this point
_pointsToAdd.Enqueue(pointToLeft, pointToLeft.DistanceSqrt());
}
if(_addedPoints.Add(pointToLeftTop))
{
// if it is first time adding this point
_pointsToAdd.Enqueue(pointToLeftTop, pointToLeftTop.DistanceSqrt());
}
}
return points;
}
I will leave the expansion to full list on you. Also make sure borders of the octants don't cause doubling of the points.
Ok, I couldn't handle it and did it myself. Also, to make sure it has properties you desire I did simple test :
var points = FillCircle(50);
bool hasDuplicates = points.Count != points.Distinct().Count();
bool isInOrder = points.Zip(points.Skip(1), (p1, p2) => p1.DistanceSqrt() <= p2.DistanceSqrt()).All(x => x);
I found a solution that satisfies my performance needs.
It's very simple, just a offset array.
static Point[] circleOffsets;
static int[] radiusToMaxIndex;
static void InitCircle(int radius)
{
List<Point> results = new List<Point>((radius * 2) * (radius * 2));
for (int y = -radius; y <= radius; y++)
for (int x = -radius; x <= radius; x++)
results.Add(new Point(x, y));
circleOffsets = results.OrderBy(p =>
{
int dx = p.X;
int dy = p.Y;
return dx * dx + dy * dy;
})
.TakeWhile(p =>
{
int dx = p.X;
int dy = p.Y;
var r = dx * dx + dy * dy;
return r < radius * radius;
})
.ToArray();
radiusToMaxIndex = new int[radius];
for (int r = 0; r < radius; r++)
radiusToMaxIndex[r] = FindLastIndexWithinDistance(circleOffsets, r);
}
static int FindLastIndexWithinDistance(Point[] offsets, int maxR)
{
int lastIndex = 0;
for (int i = 0; i < offsets.Length; i++)
{
var p = offsets[i];
int dx = p.X;
int dy = p.Y;
int r = dx * dx + dy * dy;
if (r > maxR * maxR)
{
return lastIndex + 1;
}
lastIndex = i;
}
return 0;
}
With this code you just get the index where to stop from radiusToMaxIndex, then loop through circleOffsets and visit those pixels.
It will cost lot of memory like this, but you can always change the datatype of the offsets from Point to a custom one with Bytes as members.
This solution is extremely fast, fast enough for my needs. It obviously has the drawback of using some memory, but lets be honest, instantiating a System.Windows.Form uses up more memory than this...
You have already mentioned Bresenhams's circle algorithm. That is a good starting point: You could start with the centre pixel and then draw Bresenham circles of increasing size.
The problem is that the Bresenham circle algorithm will miss pixels near the diagonals in a kind of Moiré effect. In another question, I have adopted the Bresenham algorithm for drawing between an inner and outer circle. With that algorithm as base, the strategy of drawing circles in a loop works.
Because the Bresenham algorithm can place pixels only at discrete integer coordinates, the order of visiting pixels will not be strictly in order of increasing distance. But the distance will always be within one pixel of the current circle you are drawing.
An implementation is below. That's in C, but it only uses scalars, so it shouldn't be hard to adapt to C#. The setPixel is what you do to each pixel when iterating.
void xLinePos(int x1, int x2, int y)
{
x1++;
while (x1 <= x2) setPixel(x1++, y);
}
void yLinePos(int x, int y1, int y2)
{
y1++;
while (y1 <= y2) setPixel(x, y1++);
}
void xLineNeg(int x1, int x2, int y)
{
x1--;
while (x1 >= x2) setPixel(x1--, y);
}
void yLineNeg(int x, int y1, int y2)
{
y1--;
while (y1 >= y2) setPixel(x, y1--);
}
void circle2(int xc, int yc, int inner, int outer)
{
int xo = outer;
int xi = inner;
int y = 0;
int erro = 1 - xo;
int erri = 1 - xi;
int patch = 0;
while (xo >= y) {
if (xi < y) {
xi = y;
patch = 1;
}
xLinePos(xc + xi, xc + xo, yc + y);
yLineNeg(xc + y, yc - xi, yc - xo);
xLineNeg(xc - xi, xc - xo, yc - y);
yLinePos(xc - y, yc + xi, yc + xo);
if (y) {
yLinePos(xc + y, yc + xi, yc + xo);
xLinePos(xc + xi, xc + xo, yc - y);
yLineNeg(xc - y, yc - xi, yc - xo);
xLineNeg(xc - xi, xc - xo, yc + y);
}
y++;
if (erro < 0) {
erro += 2 * y + 1;
} else {
xo--;
erro += 2 * (y - xo + 1);
}
if (y > inner) {
xi = y;
} else {
if (erri < 0) {
erri += 2 * y + 1;
} else {
xi--;
erri += 2 * (y - xi + 1);
}
}
}
if (patch) {
y--;
setPixel(xc + y, yc + y);
setPixel(xc + y, yc - y);
setPixel(xc - y, yc - y);
setPixel(xc - y, yc + y);
}
}
/*
* Scan pixels in circle in order of increasing distance
* from centre
*/
void scan(int xc, int yc, int r)
{
int i;
setPixel(xc, yc);
for (i = 0; i < r; i++) {
circle2(xc, yc, i, i + 1);
}
}
This code takes care of not visiting pixels that are in two octants by skipping coincident picels on alterante octants. (Edit: There was still abug in the original code, but it's fixed now by means of the ´patch` variable.)
There's also room for improvement: The inner circle is basically the outer circle of the previous iteration, so there's no point in calculating it twice; you could keep an array of the outer points of the previous circle.
The xLinePos functions are also a bit too complicated. There are never more than two pixels drawn in that function, usually only one.
If the roughness of the search order bothers you, you can run a more exact algorithm once at the beginning of the program, where you calculate a traversing order for all circles up to a reasonable maximum radius. You can then keep that data and use it for iterating all circles with smaller radii.

get angle of a line from horizon

I want to know how to get an angle of a line A-B from horizontal axis X. Other questions in SO do that only between two lines. I'm aware I can always draw second line A-C and calculate but I'm wondering if there's a faster method.
EDIT: I'm very sure I'm not doing a premature optimization.
You can use atan for that.
angle = atan((By-Ay)/(Bx-Ax))
private double Angulo(int x1, int y1, int x2, int y2)
{
double degrees;
// Avoid divide by zero run values.
if (x2 - x1 == 0)
{
if (y2 > y1)
degrees = 90;
else
degrees = 270;
}
else
{
// Calculate angle from offset.
double riseoverrun = (double)(y2 - y1) / (double)(x2 - x1);
double radians = Math.Atan(riseoverrun);
degrees = radians * ((double)180 / Math.PI);
// Handle quadrant specific transformations.
if ((x2 - x1) < 0 || (y2 - y1) < 0)
degrees += 180;
if ((x2 - x1) > 0 && (y2 - y1) < 0)
degrees -= 180;
if (degrees < 0)
degrees += 360;
}
return degrees;
}
If you need all four quadrants, Atan2 is more suitable than Atan.
public static int GetAngleBetweenPoints(PointF pt1, PointF pt2)
{
float dx = pt2.X - pt1.X;
float dy = pt2.Y - pt1.Y;
int deg = Convert.ToInt32(Math.Atan2(dy, dx) * (180 / Math.PI));
if (deg < 0) { deg += 360; }
return deg;
}
If
The angle is small,
you can live with small inaccuracies, and
You can use the angle in radians and not degrees,
then there is a fast solution: Under these conditions, you can assume that tan(a) = a = atan(a), and hence just omit the atan() call.
You could also use arccosine, if your line is in the form [r_x,r_y], where r_x is the change in x and r_y is the change in y.
angle = arccos( r_x/( r_x*r_x + r_y*r_y ) )
It's slightly more opaque, but it's basically the dot product law:
angle = arccos (r . v)
Where r and v are both unit vectors (vectors of length 1). In our case, v is the vector [1,0], and r is
[r_x,r_y] / (r_x^2+r_y^2)
in order to make it a unit vector.
The x-axis is actually a line with equation
y = 0
so you could use the solution you have already.

Categories