Determining if a point is inside a circle on an isometric map - c#

I'm trying to figure out a way to check if a certain point is inside or outside a circle on an isometric map. I'm currently using the following method to draw the circle:
public static List<Coords> GetBrushCircleCoords(int x0, int y0, int radius)
{
List<Coords> coords = new List<Coords>();
int x = radius;
int y = 0;
int err = 0;
while (x >= y)
{
coords.Add(new Coords(x0 + x, y0 + y));
coords.Add(new Coords(x0 + y, y0 + x));
coords.Add(new Coords(x0 - y, y0 + x));
coords.Add(new Coords(x0 - x, y0 + y));
coords.Add(new Coords(x0 - x, y0 - y));
coords.Add(new Coords(x0 - y, y0 - x));
coords.Add(new Coords(x0 + y, y0 - x));
coords.Add(new Coords(x0 + x, y0 - y));
y += 1;
err += 1 + 2 * y;
if (2 * (err - x) + 1 > 0)
{
x -= 1;
err += 1 - 2 * x;
}
}
return coords;
}
And the approach I'm trying to determine if the point is inside the circle is basically taking the desired point, determine its distance to the center and checking if it's bigger than the radius with the following method:
public static int GetDistance(Coords _from, Coords _to)
{
return Math.Max(Math.Abs(_from.X - _to.X), Math.Abs(_from.Y - _to.Y));
}
However, it seems the GetDistance method isn't the best way to calculate this as the distance calculated by it is fairly shorter than the one used on the GetBrushCircleCoords. What would be the correct way of determining if a certain point is inside/outside this circle?

On a Euclidean plane distance function (metric) is given by the Pythagorean Theorem. So shouldn't GetDistance be something like:
public static double GetDistance(Coords from, Coords to)
{
//a^2 + b^2 = c^2
var a = from.X - to.X;
var b = from.Y - to.Y;
var c = Math.Sqrt(a*a+b*b);
return c;
}

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.

Algorithm for finding new points of rotating a vector

I am trying to programmatically find the point created from rotating a vector around it's origin point (could be anywhere in 2D space).
We see that we have our line (or vector for the math) A at some point of (x, y) that might be anywhere in 2D space. It runs to point B at some (x, y). We rotate it by Theta which then moves to some point C at an (x, y). The problem for me comes with trying to programmatically use math to solve for such.
Originally the thought was to form a triangle and use trig but this angle could be exactly 180 (unlikely but possible) which obviously no triangle can work. Would anyone have ideas?
I am using C# and my own vector object (below) to test out the creation of lines. Any help is appreciated!
struct Vector2D {
double x, y, theta;
Vector2D(double x, double y) {
(this.x, this.y) = (x, y);
theta = x != 0 ? Math.Atan(y / x) : 0;
}
double Magnitude() {
return Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2));
}
(double,double) PointFromRotation(double angle) {
// This is where I need some help...
return (0,0); // hopefully a point of x and y from the angle argument
}
}
I think it would be best to use the following code.
I've made some minor modifications and supplements to your code.
The calculation part of 'theta' was slightly modified.
And, you can refer to the rotation algorithm from the following URL.
Rotation (mathematics)
struct Vector2D
{
public double x;
public double y;
public double theta;
public Vector2D(double x, double y)
{
(this.x, this.y) = (x, y);
theta = x != 0 ? Math.Atan(y / x) : Math.Sign(y) * Math.PI / 2.0;
}
public double Magnitude()
{
return Math.Sqrt(Math.Pow(x, 2) + Math.Pow(y, 2));
}
public (double x, double y) PointFromRotation(double angle)
{
double Sint = Math.Sin(angle);
double Cost = Math.Cos(angle);
double rX = x * Cost - y * Sint;
double rY = x * Sint + y * Cost;
return (rX, rY);
}
}
You can convert cartesian coordinates (x, y) into polar ones (R, fi),
add theta to fi and then convert back to cartesian:
// Rotate B around A by angle theta
private static (double x, double y) Rotate(
(double x, double y) A,
(double x, double y) B,
double theta) {
double fi = Math.Atan2(B.y - A.y, B.x - A.x) + theta;
double R = Math.Sqrt((A.y - B.y) * (A.y - B.y) + (A.x - B.x) * (A.x - B.x));
return (A.x + R * Math.Cos(fi), A.y + R * Math.Sin(fi));
}
the only possible difficulty is to compute fi which can be done with a help of Math.Atan2 method.
Another option:
// Rotate B around A by angle theta clockwise
private static (double x, double y) Rotate(
(double x, double y) A,
(double x, double y) B,
double theta)
{
double s = Math.Sin(theta);
double c = Math.Cos(theta);
// translate point back to origin:
B.x -= A.x;
B.y -= A.y;
// rotate point clockwise
double xnew = B.x * c - B.y * s;
double ynew = B.x * s + B.y * c;
// translate point back:
B.x = xnew + A.x;
B.y = ynew + A.y;
return B;
}
inspired by this answer

How to generate list of vectors within a sphere?

I'm trying to generate mining points within an asteroid for a game.
With that said, I have the asteroid center point, the radius of the asteroid, and the size of the ship I'm using.
Here is my method, but it's creating vectors outside of the range of the asteroid.
private void ExpandAreaOfControl()
{
int yStart = (int)TargetLocation.Y;
int xStart = (int)TargetLocation.X;
int zStart = (int)TargetLocation.Z;
int startGridSize = 20;
for (double y = startGridSize; y < Size / 2; y += startGridSize)
{
for (double x = startGridSize; x < Size / 2; x += startGridSize)
{
for (double z = startGridSize; z < Size / 2; z += startGridSize)
{
var point1 = new MineralPoint(new Vector3D(xStart + x, yStart + y, zStart + z));
mineralDeposits.Add(point1);
var point2 = new MineralPoint(new Vector3D(xStart + x, yStart + y, zStart - z));
mineralDeposits.Add(point2);
var point3 = new MineralPoint(new Vector3D(xStart + x, yStart - y, zStart - z));
mineralDeposits.Add(point3);
var point4 = new MineralPoint(new Vector3D(xStart + x, yStart - y, zStart + z));
mineralDeposits.Add(point4);
var point5 = new MineralPoint(new Vector3D(xStart - x, yStart + y, zStart + z));
mineralDeposits.Add(point5);
var point6 = new MineralPoint(new Vector3D(xStart - x, yStart - y, zStart + z));
mineralDeposits.Add(point6);
var point7 = new MineralPoint(new Vector3D(xStart - x, yStart + y, zStart - z));
mineralDeposits.Add(point7);
var point8 = new MineralPoint(new Vector3D(xStart - x, yStart - y, zStart - z));
mineralDeposits.Add(point8);
}
}
}
mineralDeposits = mineralDeposits.OrderBy(y => (y.Location - StartPosition).Length()).ToList();
}
What you want to do, if you want to keep you current code, is to get the vector that goes from the center of the asteroid to the mineral you've just created.
After obtaining that vector, divide it by its length, and multiply it by the radius of the asteroid - this will put the point right on its surface, but at the same angle it was at previously.
If you want the mineral to be inside the asteroid instead then multiply the vector by a number smaller than the radius.
If you want another technique - create a 3d vector with random values (make sure they're not all 0), and do the same as previously explained (starting from the 2nd paragraph).
Finally, after obtaining the result of your multiplied vector, add the asteroid's center position vector to it and you have your mineral position (relative to the coord origin used by the asteroid).

How to draw a spherical shell?

I'm trying to arrange points in a spherical shell in C#. I have code to arrange a bunch of points (I'm doing finite element analysis) in a spherical pattern with radius of double earthRadius. I can't figure out how to do the same for a spherical shell like the type pictured here. Ideas?
for (double x = -earthRadius; x < earthRadius; x += pointIncrement) //taken from http://stackoverflow.com/questions/8671385/sphere-drawing-in-java and slightly altered to make more efficient
{
for (double y = -earthRadius; y < earthRadius; y += pointIncrement)
{
for (double z = -earthRadius; z < earthRadius; z += pointIncrement)
{
if ((x * x) + (y * y) + (z * z) <= earthRadius * earthRadius)
{
earth.AddPoint(new Vector(x, y, z), 0);
totalPoints++;
}
}
}
}
I figured this out using just Cartesian coordinates. I elected not to use spherical coordinates because it was a suboptimal solution: it made the code less elegant and using Math.Sin() and Math.Cos() would have slowed it down. Here's the eventual solution I got:
for (double x = -earthRadius; x < earthRadius; x += pointIncrement) //taken from http://stackoverflow.com/questions/8671385/sphere-drawing-in-java and slightly altered to make more efficient
{
for (double y = -earthRadius; y < earthRadius; y += pointIncrement)
{
for (double z = -earthRadius; z < earthRadius; z += pointIncrement)
{
if ((x * x) + (y * y) + (z * z) <= earthRadius * earthRadius)
{
if ((x * x) + (y * y) + (z * z) >= (earthRadius - shellThickness) * (earthRadius - shellThickness))
{
earth.AddPoint(new Vector(x, y, z), 0); // need to figure out how to calculate masspoint
totalPoints++;
}
}
}
}
}
Notice that I simply added another if statement. I (foolishly) didn't use the && operator because I thought it reduced readability.

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.

Categories