3D Line-of-Sight algorithm - algorithm

I'm currently implementing a line-of-sight algorithm that will tell me the points I can and cannot see along a line. So, if I am standing atop some hilly terrain, I can know where I can and cannot see.
The line that is generated from point A to point B includes a number of points evenly spaced between A and B. Starting from A, I see what the angle of elevation is between A and B. I note that as my alphaAngle.
Next, for each point between A and B, I get the angle of elevation between A and that point. If that point is the highest angle of elevation so far(excluding alphaAngle), then I mark it as such. Otherwise, it has a lower angle of elevation and therefor I should not be able to see it, and mark that point as having hasLOS = false.
Here are some object definitions I use:
struct TerrainProfilPnt
{
double m_x_lon; //lon
double m_y_lat; //lat
double m_z_elev; //elev
bool m_hasLOS; //Does this point have line of sight from another point?
};
class TerrainProfilePartitioner; // Holds a collection of points that make up the line
Here is my algorithm written out, however it doesn't return the correct results. Either it claims it has LOS when it shouldn't (like going from behind one hill to the opposite side of another hill, I shouldn't be able to see that).
Or it claims that I cannot see the end point when I should (Top of a hill to the valley beneath it). So, I am suspecting that either my implementation of finding the line-of-sight is incorrect, or I am implementing it wrong in code.
using Point = TerrainProfilePnt;
auto pointsInLine = terrainProfilePartitioner->GetPoints();
auto& pointsVec = pointsInLine->GetVector();
std::vector<Point> terrainProfileVec;
terrainProfileVec.reserve(pointsVec.size());
double start_xlon = 0.0f;
double start_ylat = 0.0f;
double start_zelev = 0.0f;
double end_xlon = 0.0f;
double end_ylat = 0.0f;
double end_zelev = 0.0f;
//The angle between the starting point and the ending point
double initialElevAngle = 0.0f;
//Assemble the first and last points
start_xlon = pointsVec.front().x();
start_ylat = pointsVec.front().y();
GetPointElevation(start_xlon, start_ylat, start_zelev);
end_xlon = pointsVec.back().x();
end_ylat = pointsVec.back().y();
GetPointElevation(end_xlon, end_ylat, end_zelev);
//Calculate the angle between the beginning and ending points
initialElevAngle = atan2(start_zelev, end_zelev) * 180 / M_PI;
Point initialPoint;
initialPoint.m_x_lon = start_xlon;
initialPoint.m_y_lat = start_ylat;
initialPoint.m_z_elev = start_zelev;
initialPoint.m_hasLOS = true;
//pointsVec.push_back(initialPoint);
terrainProfileVec.push_back(initialPoint);
double oldAngle = 0.0f;;
bool hasOldAngle = false;
for (std::size_t i = 1; i < pointsVec.size(); ++i)
{
Point p;
p.m_x_lon = pointsVec.at(i).x();
p.m_y_lat = pointsVec.at(i).y();
GetPointElevation(p.m_x_lon, p.m_y_lat, p.m_z_elev);
double newAngle = atan2(start_zelev, p.m_z_elev) * 180 / M_PI;
if (!hasOldAngle)
{
hasOldAngle = true;
oldAngle = newAngle;
p.m_hasLOS = true;
}
else
{
if (newAngle < oldAngle)
{
p.m_hasLOS = false;
}
else
{
oldAngle = newAngle;
p.m_hasLOS = true;
}
}
}
auto terrainPrfileSeq = new Common::ObjectRandomAccessSequence<TerrainProfilePnt>(terrainProfileVec);
return terrainPrfile

atan2(start_zelev, p.m_z_elev) is meaningless. You need
atan2(distance, p.m_z_elev - start_zelev);
where distance is horizontal distance between p and initialPoint.

I see few problems: You are not sorting the points by distance from A ascending and use only elevation. Also using goniometrics for each map location which is probably slow. I would use different approach instead:
set LOS=false for whole map
write a DDA line interpolation
P(t)=A+(B-A).t
where (B-A) is your viewing direction and t is parameter incrementing by step smaller then your grid size (so you do not miss anything). This would give you long,lat without any slow goniometrics.
Then find the object on your map. If it is topology sorted then this is either O(1) or O(log(n).log(m))
test each actual location P(t)
for visibility. If true set LOS=true and loop #2 otherwise stop the whole thing.
btw this is sometimes called ray-casting.

Related

How can I generate echo path for concave polygon

I need an algorithm to draw echo path for an arbitrary polygon. If a polygon is convex problem is quite easy to solve. To understand what I mean please look at the picture bellow where in black is original polygon and in red color echoed polygons generated from original one.
d is distance between echo paths which is given
β angle is easy to calculate knowing coordinates of vertices which we have
So as you can see for each vertex we can calculate L and thus have new vertices for next echo path.
The problem is when we have concave polygon at some point we will get an ugly picture of self crossing polygon. Please take a look to this picture.
What I want to do is generate echo polygon without self crossing part i.e. without part that is with dash lines. An algorithm or java code would be very helpful. Thanks.
Edit
Just adding piece of code that generates echo path for convex polygon as it was asked in comment.
public List<MyPath> createEchoCoCentral( List<Point> pointsOriginal, float encoderEchoDistance, int appliqueEchoCount){
List<Point> contourPoints = pointsOriginal;
List<MyPath> echoPaths = new ArrayList<>();
for (int round = 0; round < appliqueEchoCount; round++) {
List<Point> echoedPoints = new ArrayList<>();
int size = contourPoints.size()+1;//+1 because we connect end to start
Point previousPoint = contourPoints.get(contourPoints.size() - 1);
for (int i = 0; i < size; i++) {
Point currentPoint;
if (i == contourPoints.size()) {
currentPoint = new Point(contourPoints.get(0));
} else {
currentPoint = contourPoints.get(i);
}
final Point nextPoint;
if (i + 1 == contourPoints.size()) {
nextPoint = contourPoints.get(0);
} else if (i == contourPoints.size()) {
nextPoint = contourPoints.get(1);
} else {
nextPoint = contourPoints.get(i + 1);
}
if (currentPoint.x == previousPoint.x && currentPoint.y == previousPoint.y) continue;
if (currentPoint.x == nextPoint.x && currentPoint.y == nextPoint.y) continue;
// signs needed o determine to which side of polygon new point will go
float currentSlope = (float) (Math.atan((previousPoint.y - currentPoint.y) / (previousPoint.x - currentPoint.x)));
float signX = Math.signum((previousPoint.x - currentPoint.x));
float signY = Math.signum((previousPoint.y - currentPoint.y));
signX = signX == 0 ? 1 : signX;
signY = signY == 0 ? 1 : signY;
float nextSignX = Math.signum((currentPoint.x - nextPoint.x));
float nextSignY = Math.signum((currentPoint.y - nextPoint.y));
nextSignX = nextSignX == 0 ? 1 : nextSignX;
nextSignY = nextSignY == 0 ? 1 : nextSignY;
float nextSlope = (float) (Math.atan((currentPoint.y - nextPoint.y) / (currentPoint.x - nextPoint.x)));
float nextSlopeD = (float) Math.toDegrees(nextSlope);
//calculateMidAngle - is a bit tricky function that calculates angle between two adjacent edges
double S = calculateMidAngle(currentSlope, nextSlope, signX, signY, nextSignX, nextSignY);
Point p2 = new Point();
double ew = encoderEchoDistance / Math.cos(S - (Math.PI / 2));
p2.x = (int) (currentPoint.x + (Math.cos(currentSlope - S)) * ew * signX);
p2.y = (int) (currentPoint.y + (Math.sin(currentSlope - S)) * ew * signX);
echoedPoints.add(p2);
previousPoint = currentPoint;
}
//createPathFromPoints just creates MyPath objects from given Poins set
echoPaths.add(createPathFromPoints(echoedPoints));
//remove last point since it was just to connect end to first point
echoedPoints.remove(echoedPoints.size() - 1);
contourPoints = echoedPoints;
}
return echoPaths;
}
You are looking for the straight skeleton:
(Image from Wikipedia.)
This problem is called computing polygon offsetting. There are two common ways to solve this problem:
1) the most effective way is to compute offset polygon by computing winding numbers ( As I understood, this algorithm is used by Clipper library)
2) computing straight skeleton graph, which help you to build offset polygon
Interesting articles on this topic :
Chen, polygon offsetting by computing winding numbers
Felkel's algorithm to compute straight skeleton
Ok, found a library that can do what I need. It called Clipper
There is also java implementation for it here if anybody interested.
With Java library couple lines of code do the trick
Path originalPath = new Path();
for (PointF areaPoint:pointsOriginal){
originalPath.add(new LongPoint((long)areaPoint.x, (long)areaPoint.y));
}
final ClipperOffset clo = new ClipperOffset();
Paths clips = new Paths();
Paths solution = new Paths();
clips.add(originalPath);
clo.addPaths( clips, Clipper.JoinType.SQUARE, Clipper.EndType.CLOSED_LINE );
float encoderEchoDistance = (float) UnitUtils.convertInchOrMmUnitsToEncoderUnits(this, inchOrMm, appliqueEchoDistance);
clo.execute( solution, encoderEchoDistance );
// Now solution.get(0) will contain path that has offset from original path
// and what is most important it will not have self intersections.
It is open source so I will dive in for implementation details. Thanks everyone who tried to help.

XNA Vector2 path contained inside rectangle

Hello I am new to XNA and trying to develop a game prototype where the character moves from one location to another using mouse clicks.
I have a Rectangle representing the current position. I get the target location as a Vector2 using player mouse input. I extract the direction vector from the source to the target by Vector2 subtraction.
//the cursor's coordinates should be the center of the target position
float x = mouseState.X - this.position.Width / 2;
float y = mouseState.Y - this.position.Height / 2;
Vector2 targetVector = new Vector2(x, y);
Vector2 dir = (targetVector - this.Center); //vector from source center to target
//center
I represent the world using a tile map, every cell is 32x32 pixels.
int tileMap[,];
What I want to do is check whether the direction vector above passes through any blue tiles on the map. A blue tile is equal 1 on the map.
I am not sure how to do this. I thought about using linear line equation and trigonometric formulas but I'm finding it hard to implement. I've tried normalizing the vector and multiplying by 32 to get 32 pixel length intervals along the path of the vector but it doesn't seem to work. Can anyone tell me if there's anything wrong in it, or another way to solve this problem? Thanks
//collision with blue wall. Returns point of impact
private bool CheckCollisionWithBlue(Vector2 dir)
{
int num = Worldmap.size; //32
int i = 0;
int intervals = (int)(dir.Length() / num + 1); //the number of 32-pixel length
//inervals on the vector, with an edge
Vector2 unit = Vector2.Normalize(dir) * num; //a vector of length 32 in the same
//direction as dir.
Vector2 v = unit;
while (i <= intervals & false)
{
int x = (int)(v.X / num);
int y = (int)(v.Y / num);
int type = Worldmap.getType(y, x);
if (type == 1) //blue tile
{
return true;
}
else
{
i++;
v = unit * i;
}
}
return false;
}
You need the initial postion too, not only direction
Maybe you need more resolution
¿what? remove the "false" evaluation
The calcs for next pos are a bit complicated
private bool CheckCollisionWithBlue(Vector2 source, Vector2 dir)
{
int num = 8; // pixel blocks of 8
int i = 0;
int intervals = (int)(dir.Length() / num);
Vector2 step = Vector2.Normalize(dir)*num;
while (i <= intervals)
{
int x = (int)(source.X);
int y = (int)(source.Y);
int type = Worldmap.getType(y, x);
if (type == 1) //blue tile
{
return true;
}
else
{
i++;
source+=step;
}
}
return false;
}
This will improve something your code, but maybe innacurate... it depends on what are you trying to do...
You maybe can find interesting the bresenham's line algorithm http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
You should realize that you are not doing a volume collision but a line collision, if the ship or character or whatever that is at source position maybe you have to add more calcs

Ray-box Intersection Theory

I wish to determine the intersection point between a ray and a box. The box is defined by its min 3D coordinate and max 3D coordinate and the ray is defined by its origin and the direction to which it points.
Currently, I am forming a plane for each face of the box and I'm intersecting the ray with the plane. If the ray intersects the plane, then I check whether or not the intersection point is actually on the surface of the box. If so, I check whether it is the closest intersection for this ray and I return the closest intersection.
The way I check whether the plane-intersection point is on the box surface itself is through a function
bool PointOnBoxFace(R3Point point, R3Point corner1, R3Point corner2)
{
double min_x = min(corner1.X(), corner2.X());
double max_x = max(corner1.X(), corner2.X());
double min_y = min(corner1.Y(), corner2.Y());
double max_y = max(corner1.Y(), corner2.Y());
double min_z = min(corner1.Z(), corner2.Z());
double max_z = max(corner1.Z(), corner2.Z());
if(point.X() >= min_x && point.X() <= max_x &&
point.Y() >= min_y && point.Y() <= max_y &&
point.Z() >= min_z && point.Z() <= max_z)
return true;
return false;
}
where corner1 is one corner of the rectangle for that box face and corner2 is the opposite corner. My implementation works most of the time but sometimes it gives me the wrong intersection. Please see image:
The image shows rays coming from the camera's eye and hitting the box surface. The other rays are the normals to the box surface. It can be seen that the one ray in particular (it's actually the normal that is seen) comes out from the "back" of the box, whereas the normal should be coming up from the top of the box. This seems to be strange since there are multiple other rays that hit the top of the box correctly.
I was wondering if the way I'm checking whether the intersection point is on the box is correct or if I should use some other algorithm.
Thanks.
Increasing things by epsilon is not actually a great way to do this, as you now have a border of size epsilon at the edge of your box through which rays can pass. So you'll get rid of this (relatively common) weird set of errors, and end up with another (rarer) set of weird errors.
I assume that you're already envisioning that your ray is traveling at some speed along its vector and find the time of intersection with each plane. So, for example, if you are intersecting the plane at x=x0, and your ray is going in direction (rx,ry,rz) from (0,0,0), then the time of intersection is t = x0/rx. If t is negative, ignore it--you're going the other way. If t is zero, you have to decide how to handle that special case--if you're in a plane already, do you bounce off it, or pass through it? You may also want to handle rx==0 as a special case (so that you can hit the edge of the box).
Anyway, now you have exactly the coordinates where you struck that plane: they are (t*rx , t*ry , t*rz). Now you can just read off whether t*ry and t*rz are within the rectangle they need to be in (i.e. between the min and max for the cube along those axes). You don't test the x coordinate because you already know that you hit it Again, you have to decide whether/how to handle hitting corners as a special case. Furthermore, now you can order your collisions with the various surfaces by time and pick the first one as your collision point.
This allows you to compute, without resorting to arbitrary epsilon-factors, whether and where your ray intersects your cube, to the accuracy possible with floating point arithmetic.
So you just need three functions like the one you've already got: one for testing whether you hit within yz assuming you hit x, and the corresponding ones for xz and xy assuming that you hit y and z respectively.
Edit: code added to (verbosely) show how to do the tests differently for each axis:
#define X_FACE 0
#define Y_FACE 1
#define Z_FACE 2
#define MAX_FACE 4
// true if we hit a box face, false otherwise
bool hit_face(double uhit,double vhit,
double umin,double umax,double vmin,double vmax)
{
return (umin <= uhit && uhit <= umax && vmin <= vhit && vhit <= vmax);
}
// 0.0 if we missed, the time of impact otherwise
double hit_box(double rx,double ry, double rz,
double min_x,double min_y,double min_z,
double max_x,double max_y,double max_z)
{
double times[6];
bool hits[6];
int faces[6];
double t;
if (rx==0) { times[0] = times[1] = 0.0; }
else {
t = min_x/rx;
times[0] = t; faces[0] = X_FACE;
hits[0] = hit_box(t*ry , t*rz , min_y , max_y , min_z , max_z);
t = max_x/rx;
times[1] = t; faces[1] = X_FACE + MAX_FACE;
hits[1] = hit_box(t*ry , t*rz , min_y , max_y , min_z , max_z);
}
if (ry==0) { times[2] = times[3] = 0.0; }
else {
t = min_y/ry;
times[2] = t; faces[2] = Y_FACE;
hits[2] = hit_box(t*rx , t*rz , min_x , max_x , min_z , max_z);
t = max_y/ry;
times[3] = t; faces[3] = Y_FACE + MAX_FACE;
hits[3] = hit_box(t*rx , t*rz , min_x , max_x , min_z , max_z);
}
if (rz==0) { times[4] = times[5] = 0.0; }
else {
t = min_z/rz;
times[4] = t; faces[4] = Z_FACE;
hits[4] = hit_box(t*rx , t*ry , min_x , max_x , min_y , max_y);
t = max_z/rz;
times[5] = t; faces[5] = Z_FACE + MAX_FACE;
hits[5] = hit_box(t*rx , t*ry , min_x , max_x , min_y , max_y);
}
int first = 6;
t = 0.0;
for (int i=0 ; i<6 ; i++) {
if (times[i] > 0.0 && (times[i]<t || t==0.0)) {
first = i;
t = times[i];
}
}
if (first>5) return 0.0; // Found nothing
else return times[first]; // Probably want hits[first] and faces[first] also....
}
(I just typed this, didn't compile it, so beware of bugs.)
(Edit: just corrected an i -> first.)
Anyway, the point is that you treat the three directions separately, and test to see whether the impact has occurred within the right box in (u,v) coordinates, where (u,v) are either (x,y), (x,z), or (y,z) depending on which plane you hit.
PointOnBoxFace should be a two-dimensional check instead of three-dimensional. For example, if you're testing against the z = z_min plane, then you should only need to compare x and y to their respective boundaries. You've already figured out that z coordinate is correct. Floating point precision is likely tripping you up as you "re-check" the third coordinate.
For example, if z_min is 1.0, you first test against that plane. You find an intersection point of (x, y, 0.999999999). Now, even though x and y are within bounds, the z isn't quite right.
Code looks fine. Try to find this particular ray and debug it.
Could it be that that ray ends up passing exactly through the edge of the box? Floating point roundoff errors might cause it to be missed by both the right and the back face.
EDIT: Ignore this answer (see comments below where I am quite convincingly shown the error of my ways).
You are testing whether the point is inside the volume, but the point is on the periphery of that volume, so you may find that it is an "infinitesimal" distance outside the volume. Try growing the box by some small epsilon, e.g.:
double epsilon = 1e-10; // Depends the scale of things in your code.
double min_x = min(corner1.X(), corner2.X()) - epsilon;
double max_x = max(corner1.X(), corner2.X()) + epsilon;
double min_y = min(corner1.Y(), corner2.Y()) - epsilon;
...
Technically, the correct way to compare almost-equal numbers is to cast their bit representations to ints and compare the integers for some small offset, e.g., in C:
#define EPSILON 10 /* some small int; tune to suit */
int almostequal(double a, double b) {
return llabs(*(long long*)&a - *(long long*)&b) < EPSILON;
}
Of course, this isn't so easy in C#, but perhaps unsafe semantics can achieve the same effect. (Thanks to #Novox for his comments, which lead me to a nice page explaining this technique in detail.)

How can I make this Processing.org sketch more efficient?

I have a simple sketch (in Processing), basically a bunch of dots wander around, if they come into contact with each other they fight (each has a strength value, increased each time they win, if it's equal the winner is randomly chosen)
It works well with about 5000 12-pixel "zombies" (there's a slight slowdown for a half a second, while the zombies initially collide with each other), the problem is when the zombies are made smaller, they don't collide with each other as quick, and the slowdown can last much longer..
The code is really simple - basically each zombie is a class, which has an X/Y coordinate. Each frame all the zombies are nudged one pixel, randomly turning lurching degrees (or not). I think the biggest cause of slowness is the collision detection - each zombie checks every other one (so zombie 1 checks 2-5000, zombie 2 checks 1,3-5000 etc..)
I'd like to keep everything simple, and "plain Processing" (not using external libraries, which might be more efficient and easy, but I don't find it very useful for learning)
int numZombies = 5000;
Zombie[] zombies = new Zombie[numZombies];
void setup(){
size(512, 512);
noStroke();
for(int i = 0; i < numZombies; i++){
zombies[i] = new Zombie(i, random(width), random(height), random(360), zombies);
}
}
void draw(){
background(0);
for(int i = 0; i < numZombies; i++){
zombies[i].move();
zombies[i].display();
}
}
class Zombie{
int id; // the index of this zombie
float x, y; // current location
float angle; // angle of zombies movement
float lurching = 10; // Amount angle can change
float strength = 2;
boolean dead = false; // true means zombie is dead
float diameter = 12; // How big the zombie is
float velocity = 1.0; // How fast zombie moves
Zombie[] others; // Stores the other zombies
Zombie(int inid, float xin, float yin, float inangle, Zombie[] oin){
id = inid;
x = xin;
y = yin;
angle = inangle;
others = oin;
}
void move(){
if(dead) return;
float vx = velocity * sin(radians(180-angle));
float vy = velocity * cos(radians(180-angle));
x = x + vx;
y = y + vy;
if(x + vx < 0 || x + vx > width || y + vy < 0 || y + vy > height){
// Collided with wall
angle = angle + 180;
}
float adecide = random(3);
if(adecide < 1){
// Move left
angle=angle - lurching;
}
else if(adecide > 1 && adecide < 2){
// Don't move x
}
else if(adecide > 2){
// Move right
angle = angle + lurching;
}
checkFights();
}
void checkFights(){
for (int i=0; i < numZombies; i++) {
if (i == id || dead || others[i].dead){
continue;
}
float dx = others[i].x - x;
float dy = others[i].y - y;
float distance = sqrt(dx*dx + dy*dy);
if (distance < diameter){
fight(i);
}
}
}
void fight(int oid){
Zombie o = others[oid];
//println("Zombie " + id + "(s: "+ strength +") fighting " + oid + "(s: "+ o.strength +")");
if(strength < o.strength){
kill();
o.strength++;
}
else if (strength == o.strength){
if(random(1) > 0.5){
kill();
o.strength++;
}
else{
o.kill();
strength++;
}
}
}
void kill(){
dead = true;
}
void display(){
if(dead) return;
ellipse(x, y, diameter, diameter);
}
}
You got yourself O(n^2) complexity, and that's killing your algorithm. It's correct that each zombie that moves has to check with all the others if they collided which brings you to quadratic complexity.
One direction might be to create a matrix representing your screen, and instead of iterating over all the other zombies, simply update the current zombie's location on the matrix, and check there if another zombie is already occupying that same cell.
Like 1800 INFORMATION says, somehow you need to reduce the number of comparisons.
Splitting the playing area into zones is a good idea. I would imagine the time it takes to compare current location against zone boundaries and add/remove zombies from the appropriate collections is worth it. Assuming they generally will go in straight lines, they shouldn't be changing zones too frequently.
We have the problem though of possible collisions between zones. To piggyback on the idea, you could divide the screen into 4 zones then 9 zones again. Think a tic-tac-toe board overlaid on a cross. This is a bad drawing, but:
| ! |
| ! |
----+--!-+----
| ! |
====|==x=|====
----+--!-+----
| ! |
| ! |
This way each zombie is in two zones at once and every border in one scheme is covered by another zone. You wouldn't even have to check all the same zombies again because either we'd be dead or they would. So the only double-processing is a single others[i].dead check.
Another thing I can see quickly is you still loop through the rest of the elements even though you're dead:
if (i == id || dead || others[i].dead){
continue;
}
It might not save a lot of processing, but it can certainly cut some instructions if you:
if (dead) return;
instead.
Also as a side note, do you want to be checking the diameter or the radius against the distance?
Your basic collision detection algorithm has O(n^2) complexity.
You need some approach which will reduce the number of comparisons.
One approach already mentioned, is to divide the playing field into zones/regions, and
only check for collision when a zombie is in the same zone/region. This is an attempt
to sort the entities topologically (by distance). What you want is to separate these
zombies not simply by geography, but to sort them so that they are only compared when
they are 'close' to one another. And you want to ignore empty regions.
Consider a tree structure to your regions. When a region has more than some number N of zombies, you could split the region smaller, until the region radius approaches your collision distance. Use a map to lookup region, and check all zombies in a given region (and any 'close enough' region).
You probably want N to be <= log(n)...
Maybe you should split the playing field up into zones and only check for collisions between zombies that are in the same zone. You need to reduce the number of comparisons.
It reminds me of this thread: No idea what could be the problem!!. And collision detection help where I point to Wikipedia's Collision detection article.
Quadtrees seem to be often used for 2D partitioning.

Finding closest non-black pixel in an image fast

I have a 2D image randomly and sparsely scattered with pixels.
given a point on the image, I need to find the distance to the closest pixel that is not in the background color (black).
What is the fastest way to do this?
The only method I could come up with is building a kd-tree for the pixels. but I would really want to avoid such expensive preprocessing. also, it seems that a kd-tree gives me more than I need. I only need the distance to something and I don't care about what this something is.
Personally, I'd ignore MusiGenesis' suggestion of a lookup table.
Calculating the distance between pixels is not expensive, particularly as for this initial test you don't need the actual distance so there's no need to take the square root. You can work with distance^2, i.e:
r^2 = dx^2 + dy^2
Also, if you're going outwards one pixel at a time remember that:
(n + 1)^2 = n^2 + 2n + 1
or if nx is the current value and ox is the previous value:
nx^2 = ox^2 + 2ox + 1
= ox^2 + 2(nx - 1) + 1
= ox^2 + 2nx - 1
=> nx^2 += 2nx - 1
It's easy to see how this works:
1^2 = 0 + 2*1 - 1 = 1
2^2 = 1 + 2*2 - 1 = 4
3^2 = 4 + 2*3 - 1 = 9
4^2 = 9 + 2*4 - 1 = 16
5^2 = 16 + 2*5 - 1 = 25
etc...
So, in each iteration you therefore need only retain some intermediate variables thus:
int dx2 = 0, dy2, r2;
for (dx = 1; dx < w; ++dx) { // ignoring bounds checks
dx2 += (dx << 1) - 1;
dy2 = 0;
for (dy = 1; dy < h; ++dy) {
dy2 += (dy << 1) - 1;
r2 = dx2 + dy2;
// do tests here
}
}
Tada! r^2 calculation with only bit shifts, adds and subtracts :)
Of course, on any decent modern CPU calculating r^2 = dx*dx + dy*dy might be just as fast as this...
As Pyro says, search the perimeter of a square that you keep moving out one pixel at a time from your original point (i.e. increasing the width and height by two pixels at a time). When you hit a non-black pixel, you calculate the distance (this is your first expensive calculation) and then continue searching outwards until the width of your box is twice the distance to the first found point (any points beyond this cannot possibly be closer than your original found pixel). Save any non-black points you find during this part, and then calculate each of their distances to see if any of them are closer than your original point.
In an ideal find, you only have to make one expensive distance calculation.
Update: Because you're calculating pixel-to-pixel distances here (instead of arbitrary precision floating point locations), you can speed up this algorithm substantially by using a pre-calculated lookup table (just a height-by-width array) to give you distance as a function of x and y. A 100x100 array costs you essentially 40K of memory and covers a 200x200 square around the original point, and spares you the cost of doing an expensive distance calculation (whether Pythagorean or matrix algebra) for every colored pixel you find. This array could even be pre-calculated and embedded in your app as a resource, to spare you the initial calculation time (this is probably serious overkill).
Update 2: Also, there are ways to optimize searching the square perimeter. Your search should start at the four points that intersect the axes and move one pixel at a time towards the corners (you have 8 moving search points, which could easily make this more trouble than it's worth, depending on your application's requirements). As soon as you locate a colored pixel, there is no need to continue towards the corners, as the remaining points are all further from the origin.
After the first found pixel, you can further restrict the additional search area required to the minimum by using the lookup table to ensure that each searched point is closer than the found point (again starting at the axes, and stopping when the distance limit is reached). This second optimization would probably be much too expensive to employ if you had to calculate each distance on the fly.
If the nearest pixel is within the 200x200 box (or whatever size works for your data), you will only search within a circle bounded by the pixel, doing only lookups and <>comparisons.
You didn't specify how you want to measure distance. I'll assume L1 (rectilinear) because it's easier; possibly these ideas could be modified for L2 (Euclidean).
If you're only doing this for relatively few pixels, then just search outward from the source pixel in a spiral until you hit a nonblack one.
If you're doing this for many/all of them, how about this: Build a 2-D array the size of the image, where each cell stores the distance to the nearest nonblack pixel (and if necessary, the coordinates of that pixel). Do four line sweeps: left to right, right to left, bottom to top, and top to bottom. Consider the left to right sweep; as you sweep, keep a 1-D column containing the last nonblack pixel seen in each row, and mark each cell in the 2-D array with the distance to and/or coordinates of that pixel. O(n^2).
Alternatively, a k-d tree is overkill; you could use a quadtree. Only a little more difficult to code than my line sweep, a little more memory (but less than twice as much), and possibly faster.
Search "Nearest neighbor search", first two links in Google should help you.
If you are only doing this for 1 pixel per image, I think your best bet is just a linear search, 1 pixel width box at time outwards. You can't take the first point you find, if your search box is square. You have to be careful
Yes, the Nearest neighbor search is good, but does not guarantee you'll find the 'nearest'. Moving one pixel out each time will produce a square search - the diagonals will be farther away than the horizontal / vertical. If this is important, you'll want to verify - continue expanding until the absolute horizontal has a distance greater than the 'found' pixel, and then calculate distances on all non-black pixels that were located.
Ok, this sounds interesting.
I made a c++ version of a soulution, I don't know if this helps you. I think it works fast enough as it's almost instant on a 800*600 matrix. If you have any questions just ask.
Sorry for any mistakes I've made, it's a 10min code...
This is a iterative version (I was planing on making a recursive one too, but I've changed my mind).
The algorithm could be improved by not adding any point to the points array that is to a larger distance from the starting point then the min_dist, but this involves calculating for each pixel (despite it's color) the distance from the starting point.
Hope that helps
//(c++ version)
#include<iostream>
#include<cmath>
#include<ctime>
using namespace std;
//ITERATIVE VERSION
//picture witdh&height
#define width 800
#define height 600
//indexex
int i,j;
//initial point coordinates
int x,y;
//variables to work with the array
int p,u;
//minimum dist
double min_dist=2000000000;
//array for memorising the points added
struct point{
int x;
int y;
} points[width*height];
double dist;
bool viz[width][height];
// direction vectors, used for adding adjacent points in the "points" array.
int dx[8]={1,1,0,-1,-1,-1,0,1};
int dy[8]={0,1,1,1,0,-1,-1,-1};
int k,nX,nY;
//we will generate an image with white&black pixels (0&1)
bool image[width-1][height-1];
int main(){
srand(time(0));
//generate the random pic
for(i=1;i<=width-1;i++)
for(j=1;j<=height-1;j++)
if(rand()%10001<=9999) //9999/10000 chances of generating a black pixel
image[i][j]=0;
else image[i][j]=1;
//random coordinates for starting x&y
x=rand()%width;
y=rand()%height;
p=1;u=1;
points[1].x=x;
points[1].y=y;
while(p<=u){
for(k=0;k<=7;k++){
nX=points[p].x+dx[k];
nY=points[p].y+dy[k];
//nX&nY are the coordinates for the next point
//if we haven't added the point yet
//also check if the point is valid
if(nX>0&&nY>0&&nX<width&&nY<height)
if(viz[nX][nY] == 0 ){
//mark it as added
viz[nX][nY]=1;
//add it in the array
u++;
points[u].x=nX;
points[u].y=nY;
//if it's not black
if(image[nX][nY]!=0){
//calculate the distance
dist=(x-nX)*(x-nX) + (y-nY)*(y-nY);
dist=sqrt(dist);
//if the dist is shorter than the minimum, we save it
if(dist<min_dist)
min_dist=dist;
//you could save the coordinates of the point that has
//the minimum distance too, like sX=nX;, sY=nY;
}
}
}
p++;
}
cout<<"Minimum dist:"<<min_dist<<"\n";
return 0;
}
I'm sure this could be done better but here's some code that searches the perimeter of a square around the centre pixel, examining the centre first and moving toward the corners. If a pixel isn't found the perimeter (radius) is expanded until either the radius limit is reached or a pixel is found. The first implementation was a loop doing a simple spiral around the centre point but as noted that doesn't find the absolute closest pixel. SomeBigObjCStruct's creation inside the loop was very slow - removing it from the loop made it good enough and the spiral approach is what got used. But here's this implementation anyway - beware, little to no testing done.
It is all done with integer addition and subtraction.
- (SomeBigObjCStruct *)nearestWalkablePoint:(SomeBigObjCStruct)point {
typedef struct _testPoint { // using the IYMapPoint object here is very slow
int x;
int y;
} testPoint;
// see if the point supplied is walkable
testPoint centre;
centre.x = point.x;
centre.y = point.y;
NSMutableData *map = [self getWalkingMapDataForLevelId:point.levelId];
// check point for walkable (case radius = 0)
if(testThePoint(centre.x, centre.y, map) != 0) // bullseye
return point;
// radius is the distance from the location of point. A square is checked on each iteration, radius units from point.
// The point with y=0 or x=0 distance is checked first, i.e. the centre of the side of the square. A cursor variable
// is used to move along the side of the square looking for a walkable point. This proceeds until a walkable point
// is found or the side is exhausted. Sides are checked until radius is exhausted at which point the search fails.
int radius = 1;
BOOL leftWithinMap = YES, rightWithinMap = YES, upWithinMap = YES, downWithinMap = YES;
testPoint leftCentre, upCentre, rightCentre, downCentre;
testPoint leftUp, leftDown, rightUp, rightDown;
testPoint upLeft, upRight, downLeft, downRight;
leftCentre = rightCentre = upCentre = downCentre = centre;
int foundX = -1;
int foundY = -1;
while(radius < 1000) {
// radius increases. move centres outward
if(leftWithinMap == YES) {
leftCentre.x -= 1; // move left
if(leftCentre.x < 0) {
leftWithinMap = NO;
}
}
if(rightWithinMap == YES) {
rightCentre.x += 1; // move right
if(!(rightCentre.x < kIYMapWidth)) {
rightWithinMap = NO;
}
}
if(upWithinMap == YES) {
upCentre.y -= 1; // move up
if(upCentre.y < 0) {
upWithinMap = NO;
}
}
if(downWithinMap == YES) {
downCentre.y += 1; // move down
if(!(downCentre.y < kIYMapHeight)) {
downWithinMap = NO;
}
}
// set up cursor values for checking along the sides of the square
leftUp = leftDown = leftCentre;
leftUp.y -= 1;
leftDown.y += 1;
rightUp = rightDown = rightCentre;
rightUp.y -= 1;
rightDown.y += 1;
upRight = upLeft = upCentre;
upRight.x += 1;
upLeft.x -= 1;
downRight = downLeft = downCentre;
downRight.x += 1;
downLeft.x -= 1;
// check centres
if(testThePoint(leftCentre.x, leftCentre.y, map) != 0) {
foundX = leftCentre.x;
foundY = leftCentre.y;
break;
}
if(testThePoint(rightCentre.x, rightCentre.y, map) != 0) {
foundX = rightCentre.x;
foundY = rightCentre.y;
break;
}
if(testThePoint(upCentre.x, upCentre.y, map) != 0) {
foundX = upCentre.x;
foundY = upCentre.y;
break;
}
if(testThePoint(downCentre.x, downCentre.y, map) != 0) {
foundX = downCentre.x;
foundY = downCentre.y;
break;
}
int i;
for(i = 0; i < radius; i++) {
if(leftWithinMap == YES) {
// LEFT Side - stop short of top/bottom rows because up/down horizontal cursors check that line
// if cursor position is within map
if(i < radius - 1) {
if(leftUp.y > 0) {
// check it
if(testThePoint(leftUp.x, leftUp.y, map) != 0) {
foundX = leftUp.x;
foundY = leftUp.y;
break;
}
leftUp.y -= 1; // moving up
}
if(leftDown.y < kIYMapHeight) {
// check it
if(testThePoint(leftDown.x, leftDown.y, map) != 0) {
foundX = leftDown.x;
foundY = leftDown.y;
break;
}
leftDown.y += 1; // moving down
}
}
}
if(rightWithinMap == YES) {
// RIGHT Side
if(i < radius - 1) {
if(rightUp.y > 0) {
if(testThePoint(rightUp.x, rightUp.y, map) != 0) {
foundX = rightUp.x;
foundY = rightUp.y;
break;
}
rightUp.y -= 1; // moving up
}
if(rightDown.y < kIYMapHeight) {
if(testThePoint(rightDown.x, rightDown.y, map) != 0) {
foundX = rightDown.x;
foundY = rightDown.y;
break;
}
rightDown.y += 1; // moving down
}
}
}
if(upWithinMap == YES) {
// UP Side
if(upRight.x < kIYMapWidth) {
if(testThePoint(upRight.x, upRight.y, map) != 0) {
foundX = upRight.x;
foundY = upRight.y;
break;
}
upRight.x += 1; // moving right
}
if(upLeft.x > 0) {
if(testThePoint(upLeft.x, upLeft.y, map) != 0) {
foundX = upLeft.x;
foundY = upLeft.y;
break;
}
upLeft.y -= 1; // moving left
}
}
if(downWithinMap == YES) {
// DOWN Side
if(downRight.x < kIYMapWidth) {
if(testThePoint(downRight.x, downRight.y, map) != 0) {
foundX = downRight.x;
foundY = downRight.y;
break;
}
downRight.x += 1; // moving right
}
if(downLeft.x > 0) {
if(testThePoint(upLeft.x, upLeft.y, map) != 0) {
foundX = downLeft.x;
foundY = downLeft.y;
break;
}
downLeft.y -= 1; // moving left
}
}
}
if(foundX != -1 && foundY != -1) {
break;
}
radius++;
}
// build the return object
if(foundX != -1 && foundY != -1) {
SomeBigObjCStruct *foundPoint = [SomeBigObjCStruct mapPointWithX:foundX Y:foundY levelId:point.levelId];
foundPoint.z = [self zWithLevelId:point.levelId];
return foundPoint;
}
return nil;
}
You can combine many ways to speed it up.
A way to accelerate the pixel lookup is to use what I call a spatial lookup map. It is basically a downsampled map (say of 8x8 pixels, but its a tradeoff) of the pixels in that block. Values can be "no pixels set" "partial pixels set" "all pixels set". This way one read can tell if a block/cell is either full, partially full or empty.
scanning a box/rectangle around the center may not be ideal because there are many pixels/cells which are far far away. I use a circle drawing algorithm (Bresenham) to reduce the overhead.
reading the raw pixel values can happen in horizontal batches, for example a byte (for a cell size of 8x8 or multiples of it), dword or long. This should give you a serious speedup again.
you can also use multiple levels of "spatial lookup maps", its again a tradeoff.
For the distance calculatation the mentioned lookup table can be used, but its a (cache)bandwidth vs calculation speed tradeoff (I dunno how it performs on a GPU for example).
Another approach I have investigated and likely will stick to: Utilizing the Bresenham circle algorithm.
It is surprisingly fast as it saves you any sort of distance comparisons!
You effectively just draw bigger and bigger circles around your target point so that when the first time you encounter a non-black pixel you automatically know it is the closest, saving any further checks.
What I have not verified yet is whether the bresenham circle will catch every single pixel but that wasn't a concern for my case as my pixels will occur in blobs of some sort.
I would do a simple lookup table - for every pixel, precalculate distance to the closest non-black pixel and store the value in the same offset as the corresponding pixel. Of course, this way you will need more memory.

Resources