Diameter of a Convex polygon using Rotating Calipers - algorithm

I am trying to solve the problem of finding the diameter of a convex polygon i.e a pair of points that have maximum distance between them.
http://cgm.cs.mcgill.ca/~orm/diam.html
I have tried to implement the algorithm/pseudo-code mentioned here. But I am getting wrong answer for the polygon made using these points (-3,-4) (2,-3) (4,3) (0,5)
Clearly the diameter of polygon is (-3,-4) (4,3). But according to the pseudo-code mentioned here I get diameter as (-3,-4) (0,5)
struct vert
{
long long int x,y,idx;
double rad;
int next;
vert()
{}
vert(long long int _x,long long int _y)
{
x=_x;
y=_y;
rad=atan2(double(y),double(x));
}
};
long long int dist(vert a,vert b)
{
vert ab=b-a;
return (ab.x*ab.x+ab.y*ab.y);
}
int cross(vert a,vert b,vert c)
{
vert ab,ac;
ab=b-a;
ac=c-a;
return ab.x*ac.y-ab.y*ac.x;
}
double area(vert a,vert b,vert c)
{
double x=cross(a,b,c);
x=abs(x/2.00);
return x;
}
struct ret
{
vert a,b;
double dist;
};
ret comp(ret ans,vert a,vert b)
{
if(dist(a,b)>ans.dist)
{
ans.a=a;
ans.b=b;
ans.dist=dist(a,b);
}
return ans;
}
ret rc_diameter(vector<vert> &v)
{
int i,j,k,l,n;
n=v.size();
int a,b;
int p,q,p0,q0;
p0=p=0;
q=1;
ret ans;
ans.dist=0;
while(area(v[p],v[v[p].next],v[v[q].next])>area(v[p],v[v[p].next],v[q]))
{
q=v[q].next;
}
q0=q;
ans=comp(ans,v[p],v[q]);
while(q!=p0)
{
p=v[p].next;
ans=comp(ans,v[p],v[q]);
while(area(v[p],v[v[p].next],v[v[q].next])>area(v[p],v[v[p].next],v[q]))
{
q=v[q].next;
if(p!=q0&&q!=p0)
ans=comp(ans,v[p],v[q]);
else
return ans;
}
if(area(v[p],v[v[p].next],v[v[q].next])==area(v[p],v[v[p].next],v[q]))
{
if(p!=q0&&q!=p0)
ans=comp(ans,v[p],v[v[q].next]);
else
ans=comp(ans,v[v[p].next],v[q]);
}
}
return ans;
}
So can any when tell me if there is a problem in the pseudo-code or in my implementation
also when I applied this algorithm on the given set of points manually I am still getting (-3,-4) (0,5) as diameter.

It may be a bit late, but I saw that your initial value for p0 is incorrect. The p0 should be the last point in the list (pn) instead of the first point in the list (0). In other words, p0 is the previous point of the first point in polygon P, p is the first point in Polygon P, and q is the next point after p.

Related

How to implement range search in KD-Tree

I have built a d dimensional KD-Tree. I want to do range search on this tree. Wikipedia mentions range search in KD-Trees, but doesn't talk about implementation/algorithm in any way. Can someone please help me with this? If not for any arbitrary d, any help for at least for d = 2 and d = 3 would be great. Thanks!
There are multiple variants of kd-tree. The one I used had the following specs:
Each internal node has max two nodes.
Each leaf node can have max maxCapacity points.
No internal node stores any points.
Side note: there are also versions where each node (irrespective of whether its internal or leaf) stores exactly one point. The algorithm below can be tweaked for those too. Its mainly the buildTree where the key difference lies.
I wrote an algorithm for this some 2 years back, thanks to the resource pointed to by #9mat .
Suppose the task is to find the number of points which lie in a given hyper-rectangle ("d" dimensions). This task can also be to list all points OR all points which lie in given range and satisfy some other criteria etc, but that can be a straightforward change to my code.
Define a base node class as:
template <typename T> class kdNode{
public: kdNode(){}
virtual long rangeQuery(const T* q_min, const T* q_max) const{ return 0; }
};
Then, an internal node (non-leaf node) can look like this:
class internalNode:public kdNode<T>{
const kdNode<T> *left = nullptr, *right = nullptr; // left and right sub trees
int axis; // the axis on which split of points is being done
T value; // the value based on which points are being split
public: internalNode(){}
void buildTree(...){
// builds the tree recursively
}
// returns the number of points in this sub tree that lie inside the hyper rectangle formed by q_min and q_max
int rangeQuery(const T* q_min, const T* q_max) const{
// num of points that satisfy range query conditions
int rangeCount = 0;
// check for left node
if(q_min[axis] <= value) {
rangeCount += left->rangeQuery(q_min, q_max);
}
// check for right node
if(q_max[axis] >= value) {
rangeCount += right->rangeQuery(q_min, q_max);
}
return rangeCount;
}
};
Finally, the leaf node would look like:
class leaf:public kdNode<T>{
// maxCapacity is a hyper - param, the max num of points you allow a node to hold
array<T, d> points[maxCapacity];
int keyCount = 0; // this is the actual num of points in this leaf (keyCount <= maxCapacity)
public: leaf(){}
public: void addPoint(const T* p){
// add a point p to the leaf node
}
// check if points[index] lies inside the hyper rectangle formed by q_min and q_max
inline bool containsPoint(const int index, const T* q_min, const T* q_max) const{
for (int i=0; i<d; i++) {
if (points[index][i] > q_max[i] || points[index][i] < q_min[i]) {
return false;
}
}
return true;
}
// returns number of points in this leaf node that lie inside the hyper rectangle formed by q_min and q_max
int rangeQuery(const T* q_min, const T* q_max) const{
// num of points that satisfy range query conditions
int rangeCount = 0;
for(int i=0; i < this->keyCount; i++) {
if(containsPoint(i, q_min, q_max)) {
rangeCount++;
}
}
return rangeCount;
}
};
In the code for range query inside the leaf node, it is also possible to do a "binary search" inside of "linear search". Since the points will be sorted along on the axis axis, you can do a binary search do find l and r values using q_min and q_max, and then do a linear search from l to r instead of 0 to keyCount-1 (of course in the worst case it wont help, but practically, and especially if you have a capacity of pretty high values, this may help).
This is my solution for a KD-tree, where each node stores points (so not just the leafs). (Note that adapting for where points are stored only in the leafs is really easy).
I leaf some of the optimizations out and will explain them at the end, this to reduce the complexity of the solution.
The get_range function has varargs at the end, and can be called like,
x1, y1, x2, y2 or
x1, y1, z1, x2, y2, z2 etc. Where first the low values of the range are given and then the high values.
(You can use as many dimensions as you like).
static public <T> void get_range(K_D_Tree<T> tree, List<T> result, float... range) {
if (tree.root == null) return;
float[] node_region = new float[tree.DIMENSIONS * 2];
for (int i = 0; i < tree.DIMENSIONS; i++) {
node_region[i] = -Float.MAX_VALUE;
node_region[i+tree.DIMENSIONS] = Float.MAX_VALUE;
}
_get_range(tree, result, tree.root, node_region, 0, range);
}
The node_region represents the region of the node, we start as large as possible. Cause for all we know this could be the region we are dealing with.
Here the recursive _get_range implementation:
static public <T> void _get_range(K_D_Tree<T> tree, List<T> result, K_D_Tree_Node<T> node, float[] node_region, int dimension, float[] target_region) {
if (dimension == tree.DIMENSIONS) dimension = 0;
if (_contains_region(tree, node_region, target_region)) {
_add_whole_branch(node, result);
}
else {
float value = _value(tree, dimension, node);
if (node.left != null) {
float[] node_region_left = new float[tree.DIMENSIONS*2];
System.arraycopy(node_region, 0, node_region_left, 0, node_region.length);
node_region_left[dimension + tree.DIMENSIONS] = value;
if (_intersects_region(tree, node_region_left, target_region)){
_get_range(tree, result, node.left, node_region_left, dimension+1, target_region);
}
}
if (node.right != null) {
float[] node_region_right = new float[tree.DIMENSIONS*2];
System.arraycopy(node_region, 0, node_region_right, 0, node_region.length);
node_region_right[dimension] = value;
if (_intersects_region(tree, node_region_right, target_region)){
_get_range(tree, result, node.right, node_region_right, dimension+1, target_region);
}
}
if (_region_contains_node(tree, target_region, node)) {
result.add(node.point);
}
}
}
One important thing that the other answer does not provide is this part:
if (_contains_region(tree, node_region, target_region)) {
_add_whole_branch(node, result);
}
With a range search for a KD-Tree you have 3 options for a node's region, it's:
fully outside
it intersects
it's fully contained
Once you know a region is fully contained, then you can add the whole branch without doing any dimension checks.
To make it more clear, here is the _add_whole_branch:
static public <T> void _add_whole_branch(K_D_Tree_Node<T> node, List<T> result) {
result.add(node.point);
if (node.left != null) _add_whole_branch(node.left, result);
if (node.right != null) _add_whole_branch(node.right, result);
}
In this image, all the big white dots where added using _add_whole_branch and only for the red dots a check for all dimensions had to be done.
Optimization
1)
Instead of starting with the root node for the _get_range function, instead you can find the split node. This is the first node that has it's point within the query range. To find the split node you will still need to start at the root node, but the calculations are a bit cheaper (cause you go either left or right till).
2)
Now I create the float[] node_region_left and float[] node_region_right, and since this happens in a recursive function it can lead to quite some arrays. However, you can reuse the one for the left for the right. I didn't do it in this example for clarity reasons.
I can also imagine storing the region size in the node, but this takes quite some more memory and might lead to a lot of cache misses.

Number of distinct rectangles in which diagonal is passing in N squares

I'm solving CS problem and I need little help. I have number N, and I need to count the number of distinct rectangles in which diagonal is passing in N squares if the rectangle is splited on rectangles with size 1x1. This picture will help you understand.
This picture is showing all 4 combinations if N = 4, actually the rectangles in which the diagonal is passing in 4 squares are with sizes 1x4, 2x3, 4x2 and 4x4.
I found the formula if we have given the two sizes of the rectangles it is:
A + B - gcd(A,B)
since N<=10^6, i go up to 10^6 and check for each N the divisors of N, complexity of that is O(Nsqrt(N)), since the divisors of A is gcd(A,B)i solve the system of equations
q is divisor of A and q is gcd(A,B)
A+B-q=N and gcd(A,B)=q
I solved this in O(Nsqrt(N)*log(N))
where i assume that log(N) is the time to find gcd of two numbers.
Because the time limit is 3 seconds it fails on time. I need help on optimizing the solution.
Update: Here is my code:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a;
int gcd(int a, int b) {
if(b>a) swap(a,b);
if(b==0) return a;
return gcd(b, a%b);
}
bool valid(int n, int m, int gc, int a) {
if(n+m-gc==a) return true;
return false;
}
int main() {
cin>>a;
int counter=0;
for(int i=1;i<=a/2;i++) {
for(ll j=1;j<=sqrt(i);j++) {
if(i%j==0) {
if(j!=i/j) {
int m1 = a+j-i;
int div=i/j;
int m2 = a+div-i;
if(valid(i, m1, j, a)) {
if(gcd(i, m1)==j)
counter++;
}
if(valid(i, m2, i/j, a)) {
if(gcd(i,m2)==i/j)
counter++;
}
}
else {
int m1 = a+j-i;
if(valid(i, m1, j, a)) {
if(gcd(i, m1)==j)
counter++;
}
}
}
}
}
cout<<counter+1;
return 0;
}
Thanks in advance.
Although O(n*sqrt(n)*log(n)) sounds a bit much for n <= 10^6, and you likely need a slightly better algorithm, your code supports some optimizations:
int gcd(int a, int b) {
if(b>a) swap(a,b);
if(b==0) return a;
return gcd(b, a%b);
}
Get rid of the swap, it will work just fine without it.
While you're at it, get rid of the recursion too:
int gcd(int a, int b) {
while (b) {
int r = a % b;
a = b;
b = r;
}
return a;
}
Next:
for(int i=1;i<=a/2;i++) {
for(ll j=1;j<=sqrt(i);j++) {
Compute a/2 and sqrt(i) outside of their respective loops. There is no need to compute it at each iteration. The compiler may or may not be smart enough (or set up) to do this itself, but you shouldn't rely on it, especially in an online judge setting.
You can also precompute i / j further down so as to not recompute it every time. A lot of divisions can be slow.
Next, do you really need long long for j? i is an int, and j goes up to its square root. So you don't need long long for j, use int.

Find if a path exists in a grid covered with circles of same radius

I was asked this question in an interview and I couldn't solve it.
I would be really grateful if you could help me solve it.
The problem is as follows:-
You are given a rectangular region whose left and bottom-most co-ordinate is (0,0) and right-most and top-most co-ordinate is (x,y).
There are n circles with given center co-ordinates that exist inside the region all having the same radius 'r'.
You are currently standing at (0,0) and want to reach (x,y) without touching any circle.
You have to tell if it is possible or not.
He told me that you can move between 2 points freely and it is not necessary to move along the x or y-axis.
The solution I thought of involved taking a matrix of x*y dimension and for each circle, mark the points which lie inside it or touch it.
After that apply BFS starting from (0,0) to check if we can reach (x,y).
He told me BFS will be wrong which I couldn't figure out why.
I had assumed that circles are having integer radius and have integer co-ordinates.
He also asked me to solve the question without these assumptions.
I couldn't. When asked, he told me it's a standard problem and I should be able to find it on google.
I couldn't, again. Please help!
The interviewer is wrong on the part that BFS cannot be used. Whenever you step into a cell, check that if the cell is within a circle or not, by checking the cell's distance from every other circle centers available to you, if it is within dist<=R then that cell can't be reached from the present particular cell. I solved the similar question present in interviewbit -
https://www.interviewbit.com/problems/valid-path/
The code is simple -
public class Solution {
private class Pair
{
int x ; int y ;
}
ArrayList<Integer> xindex ; ArrayList<Integer> yindex ; int R ;int len ;
public String solve(int x, int y, int n, int r, ArrayList<Integer> xi, ArrayList<Integer> yi) {
int dp[][] = new int[x+1][y+1] ;
len = xi.size() ;
for(int i=0;i<=x;i++)
{
for(int j=0;j<=y;j++)
dp[i][j] = -1 ;
}
xindex = xi ; yindex = yi ;
dp[0][0] = 1 ; R = r*r ;
LinkedList<Pair> q = new LinkedList<Pair>() ;
Pair obj = new Pair() ;
obj.x = 0 ; obj.y = 0 ;
q.add(obj) ;
int arr1[] = {1,1,1,0,-1,-1,-1,0} ;
int arr2[] = {-1,0,1,1,1,0,-1,-1} ;
while(q.size()!=0)
{
Pair temp = q.poll() ;
int x1 = temp.x ;
int x2 = temp.y ;
for(int i=0;i<8;i++)
{
int t1 = x1+arr1[i] ; int t2 = x2+arr2[i] ;
if((t1>=0)&&(t1<=x)&&(t2>=0)&&(t2<=y))
{
if(dp[t1][t2]==-1)
{
boolean res = isValidIndex(t1,t2) ;
if(res==false)
dp[t1][t2] = 2 ;
else
{
dp[t1][t2] = 1 ;
Pair t = new Pair() ;
t.x = t1 ;
t.y = t2 ;
q.add(t) ;
}
}
}
}
if(dp[x][y]!=-1)
break ;
}
if(dp[x][y]==1)
return "YES" ;
return "NO" ;
}
public boolean isValidIndex(int x,int y)
{
for(int i=0;i<len;i++)
{
int x1 = xindex.get(i) ; int x2 = yindex.get(i) ;
if((x==x1)&&(y==x2))
return false ;
int n = (x1-x)*(x1-x) + (x2-y)*(x2-y) ;
if(n<=R)
return false ;
}
return true ;
}
}
If two circles are touching, draw a line segment between their centers. If a circle touches an edge of the rectangle, joint its center to its projection on the closest side. Then discard the circles. This doesn't modify the connexity of the obstacles and you have turned the problem to the more famliar one of a planar straight line subdivision.
An approach could be by decomposing the domain in slabs, i.e. drawing horizontal lines through every center to partition the plane in trapezoids. Then by a seed filling approach, one can determine the starting slab and extend accessibility to the slabs that have a common horizontal side with it, until either a closed region is filled or the exit slab is reached.
Below, an intermediate step of seed filling from the top-left slab.
If the circle centers are on a regular grid, standard seed filling can do.
I think 2 circles can only block the path if the distance between their centers is less than 2r. So it should be enough to build "islands" of overlapping circles and check if any island blocks the path from 0 to (x,y), i.e. if any circles in it intersect rectangle borders in such a way that a straight line between those intersection points would block the path.
Lazy approach:
Visit each ball in turn
If it does not touch any other ball, scrap it.
If it touches another one, add it and the other one to your favorite node graph system and create a connection between them (do some housekeeping to avoid doublettes).
Create the extra balls A, B, C, D
Create extra connections (lines in the pic) between A and all balls that touch the left edge, between B and all balls that touch the top edge, etc.
Ask your A* if it's willing to navigate you from A to C / A to D / B to C / B to D.
If yes to any of above, then the answer to the Q is no. Quick and dirty :-).
Best approach to solve this problem is using a dfs search. First lets consider some of the basic cases like if there exist a circle containing either of the two points (i.e 0,0 or x,y) but not both then answer is "NO" and if there exist a circle containing both of the points then we can remove it from our list of circles. Now we are left with circles that don't contain either of the two points now make a graph using centers of these circles as a vertex and join any two vertex if the distance between them is less than or equal to 2*R and also maintain an array of mask for all circles, mask store the sides cut by a particular circle i.e if we number left vertical side as 0 top horizontal side as 1 right vertical side as 2 and bottom horizontal side as 3 then in mask corresponding to a particular circle those bits would be active which correspond to sides cut by that circle.
Now just perform a dfs and see whether there exist a path in graph which shows whether it is possible to move from, side 0 to 2 or side 0 to 3 or side 1 to 2 or side 1 to 2, using edges of graph (we use mask to check that). If such a path exists then answer is "NO" else answer is always "YES".
I did this question in the exact manner as suggested by Anton. Used DSU to make so called islands. And then identified the following conditions : TB,LR,TR,LB (T: Top, B:Bottom, L:Left, R:Right ) intersections. If any of the islands made these intersections , they are sure to block the path and the answer will be "NO".
The question can be found at Interview Bit:
https://www.interviewbit.com/problems/valid-path/
And the corresponding solution is given here:
http://ideone.com/19iSOn
#include<bits/stdc++.h>
#include<unordered_set>
using namespace std;
class Solution{
public:
string solve(int A, int B, int C, int D, vector<int> &E, vector<int> &F);
};
#define pii pair<int,int>
int par[1000+5];
int rnk[1000+5];
bool vis[1000+5];
void initialise(){
for(int i=0;i<=1000;i++){
par[i]=i;
rnk[i]=1;
vis[i]=false;
}
}
int findPar(int node){
if(par[node]==node)return node;
return par[node]=findPar(par[node]);
}
void makeUnion(int a,int b){
int parA=findPar(a);
int parB=findPar(b);
if(parA==parB)return;
if(rnk[parA]<rnk[parB])par[parB]=parA;
else if(rnk[parB]<rnk[parA])par[parA]=parB;
else{
rnk[parA]++;
par[parB]=parA;
}
}
bool findBlockage(int root,int X,int Y,int N,int R,vector<pair<int,pii>>vec){
int top=0;
int bottom=INT_MAX;
int left=INT_MAX;
int right=0;
for(int i=0;i<N;i++){
if(par[vec[i].first]==root){
int x=vec[i].second.first;
int y=vec[i].second.second;
top=max(top,y+R);
bottom=min(bottom,y-R);
left=min(left,x-R);
right=max(right,x+R);
}
}
if(top>=Y and bottom<=0)return true;
if(right>=X and left<=0)return true;
if(top>=Y and right>=X)return true;
if(left<=0 and bottom<=0)return true;
return false;
}
string Solution::solve(int X, int Y, int N, int R, vector<int> &E, vector<int> &F) {
vector<pair<int,pii>> vec;
int id=0;
for(int i=0;i<N;i++){
vec.push_back({id,{E[i],F[i]}});
id++;
}
initialise();
for(int i=0;i<N;i++){
for(int j=i;j<N;j++){
if(i==j)continue;
int x1=vec[i].second.first;
int x2=vec[j].second.first;
int y1=vec[i].second.second;
int y2=vec[j].second.second;
if(((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)) <= (4*R*R)){
makeUnion(vec[i].first,vec[j].first);
}
}
}
for(int i=0;i<N;i++){
if(!vis[par[vec[i].first]]){
vis[par[vec[i].first]]=1;
bool ret = findBlockage(par[vec[i].first],X,Y,N,R,vec);
if(ret)return "NO";
}
}
return "YES";
}
int main(){
int n,x,y,r;
cin>>x>>y>>n>>r;
vector<int>X(n);
vector<int>Y(n);
for(int i=0;i<n;i++)cin>>X[i];
for(int i=0;i<n;i++)cin>>Y[i];
Solution sol;
cout<<sol.solve(n,x,y,r,X,Y)<<endl;
}
A recursive solution to this problem (find path from [0,0] to [x,y] with circles as obstructions), using simple DP concepts:
public class Solution {
public long radiusSquare;
public int xDest, yDest;
boolean vis[][];
public final static int[] xDelta = new int[]{0, 1, -1, 0, 1, -1, -1, 1};
public final static int[] yDelta = new int[]{1, 0, 0, -1, 1, -1, 1, -1};
public String solve(int x, int y, int radius, ArrayList<Integer> X, ArrayList<Integer> Y) {
xDest = x; yDest = y;
radiusSquare = radius*radius;
int dp[][] = new int[x+1][y+1]; // = 0 (not visited), = 1 (a valid point), = 2 (invalid)
vis = new boolean[x+1][y+1];
if (recur(0, 0, X, Y, dp)) return "YES";
return "NO";
}
public boolean recur(int xi, int yi, ArrayList<Integer> X,
ArrayList<Integer> Y, int dp[][]){
if (xi == xDest && yi == yDest) return true;
if(vis[xi][yi]) return false; // already processed coordinate
vis[xi][yi] = true;
for (int i =0; i < xDelta.length; i++){
int xArg = xi+xDelta[i];
int yArg = yi+yDelta[i];
if (validCoordinates(xArg, yArg, X, Y, dp) && recur(xArg, yArg, X, Y, dp))
return true;
}
return false;
}
public boolean validCoordinates(int x, int y, ArrayList<Integer> X, ArrayList<Integer> Y, int dp[][]){
if (x < 0 || y < 0 || x > xDest || y > yDest) return false;
if (dp[x][y] != 0){
if (dp[x][y] == 1) return true; // valid coord
if (dp[x][y] == 2) return false;
}
for (int i = 0; i < X.size(); i++){ // loop through each circle.
long sumOfSquare = ((x-X.get(i))*(x-X.get(i))) + ((y-Y.get(i))*(y-Y.get(i)));
if (sumOfSquare <= radiusSquare){
dp[x][y] = 2; // comes on or inside circle
return false;
}
}
dp[x][y] = 1;
return true;
}
}

Determine Minimum Number of Line Segments to Solve a Maze

I have a problem where I need to define a polygon with the minimum number of vertices that intersects or contains every pixel in an image that is not transparent (Let N be the number of pixels in the image). My only assumptions are that the image cannot contain transparent pixels within its boundary (holes), and that at least two pixels are non-transparent. As an example, lets say I have the following image:
My idea for an algorithm is thus:
1) Determine the edge pixels.This is done in O(N) time by iterating through each pixel, and determining whether any neighbors (out of the four pixels left, above, right, and below it) are empty. Store the pixel and which neighbors were non-transparent, keyed by linear index into the array of pixels. Let there be P edge pixels, shown in orange below.
2) Get an adjacency list of the edge pixels.This is done in O(P) time by selecting one of the edge pixels, and chosing a direction based on empty neighbors. For example, if a pixel has a bottom and right neighbor, then the next pixel will be either one in the upper-right corner, or the pixel immediately to the right. Select the next edge pixel from the dictionary of remaining edge pixels. Append that pixel to the list until the algorithm returns to the starting pixel. There are 27 edge pixels in the example image below (some are an edge pixel more than once).
3) Draw a maze that all edges must lie between.This is done in O(P) time by iterating through the adjacency list, and adding an edge to all edges on those pixels without a neighbor. In addition, an edge is added to the interior of the shape based on the direction to the next edge pixel. If the pixel represents a peninsula with single pixel width, the inner edge is added to the middle of the edge instead of the pixel vertex. The interior of the maze is shown in red. Note that the maze boundary is a super-set of all the edge pixels found in step 2.
4) Find a polygon with almost minimal edges that does not touch the border of the maze.This is the part I need help with. Does anyone have a suggestion of how you would go from step #3 to a solution such as the following?
I have no background in image processing, but I came across the Ramer–Douglas–Peucker algorithm yesterday and I think it might be helpful.
From my quick scan of the Wikipedia article, it reduces the number of point in a curve, so I would run this algorithm on each line where the points of the line are the end points you have and also set as points the borders of squares that the line crosses.
I marked out in this image two lines you could run the algorithm on and I think it would work.
How to find each line and when to stop - not 100% sure, but I hope this was useful.
See find holes in 2D point set
it is very similar problem
just invert the map and use midpoint of each grid square as point
that will lead to this:
as you want the inner polygon then there are 2 choices
shrink shape by 1 cell before applying this (can loose some detail in shape)
change the H/V lines so they are 1 cell shorter (half from both sides)
that will lead to something like this:
after some changes in code I can use 2x multi-sampling now so result is:
now you can join connected lines with the same slope
and apply something like ear clipping on the found corners to get more close to your desired polygon
Here the inverted source code in C++ (there may be some hole comments left):
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
/* usage:
int i;
pntcloud_polygons h;
pnt2D point[points];
h.scann_beg(); for (i=0;i<points;i++) { p=point[i]; h.scann_pnt(p.x,p.y); } h.scann_end();
h.cell_size(2.5); // or (h.x1-h.x0)*0.01 ... cell size >> avg point distance
h.holes_beg(); for (i=0;i<points;i++) { p=point[i]; h.holes_pnt(p.x,p.y); } h.holes_end();
*/
//---------------------------------------------------------------------------
class pntcloud_polygons
{
public:
int xs,ys,n; // cell grid x,y - size and points count
int **map; // points density map[xs][ys]
// i=(x-x0)*g2l; x=x0+(i*l2g);
// j=(y-y0)*g2l; y=y0+(j*l2g);
double mg2l,ml2g; // scale to/from global/map space (x,y) <-> map[i][j]
double x0,x1,y0,y1; // used area (bounding box)
struct _line
{
int id; // id of hole for segmentation/polygonize
float i0,i1,j0,j1; // index in map[][]
_line(){}; _line(_line& a){ *this=a; }; ~_line(){}; _line* operator = (const _line *a) { *this=*a; return this; }; /*_line* operator = (const _line &a) { ...copy... return this; };*/
};
List<_line> lin;
int lin_i0; // start index for perimeter lines (smaller indexes are the H,V lines inside hole)
struct _point
{
int i,j; // index in map[][]
int p0,p1; // previous next point
int used;
_point(){}; _point(_point& a){ *this=a; }; ~_point(){}; _point* operator = (const _point *a) { *this=*a; return this; }; /*_point* operator = (const _point &a) { ...copy... return this; };*/
};
List<_point> pnt;
// class init and internal stuff
pntcloud_polygons() { xs=0; ys=0; n=0; map=NULL; mg2l=1.0; ml2g=1.0; x0=0.0; y0=0.0; x1=0.0; y1=0.0; lin_i0=0; };
pntcloud_polygons(pntcloud_polygons& a){ *this=a; };
~pntcloud_polygons() { _free(); };
pntcloud_polygons* operator = (const pntcloud_polygons *a) { *this=*a; return this; };
pntcloud_polygons* operator = (const pntcloud_polygons &a)
{
xs=0; ys=0; n=a.n; map=NULL;
mg2l=a.mg2l; x0=a.x0; x1=a.x1;
ml2g=a.ml2g; y0=a.y0; y1=a.y1;
_alloc(a.xs,a.ys);
for (int i=0;i<xs;i++)
for (int j=0;j<ys;j++) map[i][j]=a.map[i][j];
return this;
}
void _free() { if (map) { for (int i=0;i<xs;i++) if (map[i]) delete[] map[i]; delete[] map; } xs=0; ys=0; }
void _alloc(int _xs,int _ys) { int i=0; _free(); xs=_xs; ys=_ys; map=new int*[xs]; if (map) for (i=0;i<xs;i++) { map[i]=new int[ys]; if (map[i]==NULL) { i=-1; break; } } else i=-1; if (i<0) _free(); }
// scann boundary box interface
void scann_beg();
void scann_pnt(double x,double y);
void scann_end();
// dynamic allocations
void cell_size(double sz); // compute/allocate grid from grid cell size = sz x sz
// scann pntcloud_polygons interface
void holes_beg();
void holes_pnt(double x,double y);
void holes_end();
// global(x,y) <- local map[i][j] + half cell offset
inline void l2g(double &x,double &y,int i,int j) { x=x0+((double(i)+0.5)*ml2g); y=y0+((double(j)+0.5)*ml2g); }
inline void l2g(double &x,double &y,float i,float j) { x=x0+((double(i)+0.5)*ml2g); y=y0+((double(j)+0.5)*ml2g); }
// local map[i][j] <- global(x,y)
inline void g2l(int &i,int &j,double x,double y) { i= double((x-x0) *mg2l); j= double((y-y0) *mg2l); }
};
//---------------------------------------------------------------------------
void pntcloud_polygons::scann_beg()
{
x0=0.0; y0=0.0; x1=0.0; y1=0.0; n=0;
}
//---------------------------------------------------------------------------
void pntcloud_polygons::scann_pnt(double x,double y)
{
if (!n) { x0=x; y0=y; x1=x; y1=y; }
if (n<0x7FFFFFFF) n++; // avoid overflow
if (x0>x) x0=x; if (x1<x) x1=x;
if (y0>y) y0=y; if (y1<y) y1=y;
}
//---------------------------------------------------------------------------
void pntcloud_polygons::scann_end()
{
}
//---------------------------------------------------------------------------
void pntcloud_polygons::cell_size(double sz)
{
int x,y;
if (sz<1e-6) sz=1e-6;
x=ceil((x1-x0)/sz);
y=ceil((y1-y0)/sz);
_alloc(x,y);
ml2g=sz; mg2l=1.0/sz;
}
//---------------------------------------------------------------------------
void pntcloud_polygons::holes_beg()
{
int i,j;
for (i=0;i<xs;i++)
for (j=0;j<ys;j++)
map[i][j]=0;
}
//---------------------------------------------------------------------------
void pntcloud_polygons::holes_pnt(double x,double y)
{
int i,j;
g2l(i,j,x,y);
if ((i>=0)&&(i<xs))
if ((j>=0)&&(j<ys))
if (map[i][j]<0x7FFFFFFF) map[i][j]++; // avoid overflow
}
//---------------------------------------------------------------------------
void pntcloud_polygons::holes_end()
{
int i,j,e,i0,i1;
List<int> ix; // hole lines start/stop indexes for speed up the polygonization
_line *a,*b,l;
_point *aa,*bb,p;
lin.num=0; lin_i0=0;// clear lines
ix.num=0; // clear indexes
// find pntcloud_polygons (map[i][j].cnt!=0) or (map[i][j].cnt>=treshold)
// and create lin[] list of H,V lines covering pntcloud_polygons
for (j=0;j<ys;j++) // search lines
for (i=0;i<xs;)
{
int i0,i1;
for (;i<xs;i++) if (map[i][j]!=0) break; i0=i-1; // find start of polygon
for (;i<xs;i++) if (map[i][j]==0) break; i1=i; // find end of polygon
if (i0< 0) continue; // skip bad circumstances (edges or no hole found)
if (i1>=xs) continue;
if (map[i0][j]!=0) continue;
if (map[i1][j]!=0) continue;
l.i0=i0+0.5;
l.i1=i1-0.5;
l.j0=j ;
l.j1=j ;
l.id=-1;
lin.add(l);
}
for (i=0;i<xs;i++) // search columns
for (j=0;j<ys;)
{
int j0,j1;
for (;j<ys;j++) if (map[i][j]!=0) break; j0=j-1; // find start of polygon
for (;j<ys;j++) if (map[i][j]==0) break; j1=j ; // find end of polygon
if (j0< 0) continue; // skip bad circumstances (edges or no hole found)
if (j1>=ys) continue;
if (map[i][j0]!=0) continue;
if (map[i][j1]!=0) continue;
l.i0=i ;
l.i1=i ;
l.j0=j0+0.5;
l.j1=j1-0.5;
l.id=-1;
lin.add(l);
}
// segmentate lin[] ... group lines of the same hole together by lin[].id
// segmentation based on vector lines data
// you can also segmentate the map[][] directly as bitmap during hole detection
for (i=0;i<lin.num;i++) lin[i].id=i; // all lines are separate
for (;;) // join what you can
{
for (e=0,a=lin.dat,i=0;i<lin.num;i++,a++)
{
for (b=a,j=i;j<lin.num;j++,b++)
if (a->id!=b->id)
{
// if a,b not intersecting or neighbouring
if (a->i0>b->i1) continue;
if (b->i0>a->i1) continue;
if (a->j0>b->j1) continue;
if (b->j0>a->j1) continue;
// if they do mark e for join groups
e=1; break;
}
if (e) break; // join found ... stop searching
}
if (!e) break; // no join found ... stop segmentation
i0=a->id; // joid ids ... rename i1 to i0
i1=b->id;
for (a=lin.dat,i=0;i<lin.num;i++,a++)
if (a->id==i1)
a->id=i0;
}
// sort lin[] by id
for (e=1;e;) for (e=0,a=&lin[0],b=&lin[1],i=1;i<lin.num;i++,a++,b++)
if (a->id>b->id) { l=*a; *a=*b; *b=l; e=1; }
// re id lin[] and prepare start/stop indexes
for (i0=-1,i1=-1,a=&lin[0],i=0;i<lin.num;i++,a++)
if (a->id==i1) a->id=i0;
else { i0++; i1=a->id; a->id=i0; ix.add(i); }
ix.add(lin.num);
// polygonize
lin_i0=lin.num;
for (j=1;j<ix.num;j++) // process hole
{
i0=ix[j-1]; i1=ix[j];
// create border pnt[] list (unique points only)
pnt.num=0; p.used=0; p.p0=-1; p.p1=-1;
for (a=&lin[i0],i=i0;i<i1;i++,a++)
{
p.i=a->i0;
p.j=a->j0;
map[p.i][p.j]=0;
for (aa=&pnt[0],e=0;e<pnt.num;e++,aa++)
if ((aa->i==p.i)&&(aa->j==p.j)) { e=-1; break; }
if (e>=0) pnt.add(p);
p.i=a->i1;
p.j=a->j1;
map[p.i][p.j]=0;
for (aa=&pnt[0],e=0;e<pnt.num;e++,aa++)
if ((aa->i==p.i)&&(aa->j==p.j)) { e=-1; break; }
if (e>=0) pnt.add(p);
}
// mark not border points
for (aa=&pnt[0],i=0;i<pnt.num;i++,aa++)
if (!aa->used) // ignore marked points
if ((aa->i>0)&&(aa->i<xs-1)) // ignore map[][] border points
if ((aa->j>0)&&(aa->j<ys-1))
{ // ignore if any non hole cell around
if (map[aa->i-1][aa->j-1]>0) continue;
if (map[aa->i-1][aa->j ]>0) continue;
if (map[aa->i-1][aa->j+1]>0) continue;
if (map[aa->i ][aa->j-1]>0) continue;
if (map[aa->i ][aa->j+1]>0) continue;
if (map[aa->i+1][aa->j-1]>0) continue;
if (map[aa->i+1][aa->j ]>0) continue;
if (map[aa->i+1][aa->j+1]>0) continue;
aa->used=1;
}
// delete marked points
for (aa=&pnt[0],e=0,i=0;i<pnt.num;i++,aa++)
if (!aa->used) { pnt[e]=*aa; e++; } pnt.num=e;
// connect neighbouring points distance=1
for (i0= 0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
if (aa->used<2)
for (i1=i0+1,bb=&pnt[i1];i1<pnt.num;i1++,bb++)
if (bb->used<2)
{
i=aa->i-bb->i; if (i<0) i=-i; e =i;
i=aa->j-bb->j; if (i<0) i=-i; e+=i;
if (e!=1) continue;
aa->used++; if (aa->p0<0) aa->p0=i1; else aa->p1=i1;
bb->used++; if (bb->p0<0) bb->p0=i0; else bb->p1=i0;
}
// try to connect neighbouring points distance=sqrt(2)
for (i0= 0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
if (aa->used<2)
for (i1=i0+1,bb=&pnt[i1];i1<pnt.num;i1++,bb++)
if (bb->used<2)
if ((aa->p0!=i1)&&(aa->p1!=i1))
if ((bb->p0!=i0)&&(bb->p1!=i0))
{
if ((aa->used)&&(aa->p0==bb->p0)) continue; // avoid small losed loops
i=aa->i-bb->i; if (i<0) i=-i; e =i*i;
i=aa->j-bb->j; if (i<0) i=-i; e+=i*i;
if (e!=2) continue;
aa->used++; if (aa->p0<0) aa->p0=i1; else aa->p1=i1;
bb->used++; if (bb->p0<0) bb->p0=i0; else bb->p1=i0;
}
// try to connect to closest point
int ii,dd;
for (i0= 0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
if (aa->used<2)
{
for (ii=-1,i1=i0+1,bb=&pnt[i1];i1<pnt.num;i1++,bb++)
if (bb->used<2)
if ((aa->p0!=i1)&&(aa->p1!=i1))
if ((bb->p0!=i0)&&(bb->p1!=i0))
{
i=aa->i-bb->i; if (i<0) i=-i; e =i*i;
i=aa->j-bb->j; if (i<0) i=-i; e+=i*i;
if ((ii<0)||(e<dd)) { ii=i1; dd=e; }
}
if (ii<0) continue;
i1=ii; bb=&pnt[i1];
aa->used++; if (aa->p0<0) aa->p0=i1; else aa->p1=i1;
bb->used++; if (bb->p0<0) bb->p0=i0; else bb->p1=i0;
}
// add connected points to lin[] ... this is hole perimeter !!!
// lines are 2 x duplicated so some additional code for sort the order of line swill be good idea
l.id=lin[ix[j-1]].id;
// add index of points instead points
int lin_i1=lin.num;
for (i0=0,aa=&pnt[i0];i0<pnt.num;i0++,aa++)
{
l.i0=i0;
if (aa->p0>i0) { l.i1=aa->p0; lin.add(l); }
if (aa->p1>i0) { l.i1=aa->p1; lin.add(l); }
}
// reorder perimeter lines
for (i0=lin_i1,a=&lin[i0];i0<lin.num-1;i0++,a++)
for (i1=i0+1 ,b=&lin[i1];i1<lin.num ;i1++,b++)
{
if (a->i1==b->i0) { a++; l=*a; *a=*b; *b=l; a--; break; }
if (a->i1==b->i1) { a++; l=*a; *a=*b; *b=l; i=a->i0; a->i0=a->i1; a->i1=i; a--; break; }
}
// convert point indexes to points
for (i0=lin_i1,a=&lin[i0];i0<lin.num;i0++,a++)
{
bb=&pnt[a->i0]; a->i0=bb->i; a->j0=bb->j;
bb=&pnt[a->i1]; a->i1=bb->i; a->j1=bb->j;
}
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
it is the same as the code in holes link above
just the map[][] conditions inverted to search polygons instead of holes
and found HV lines are shrinked by half of cell from each side
_lin coordinates are float now so o need for 4x multisampling
the best results are with 2x multi-sampling (to avoid polygon width=1)
so each cell in your map add as 2x2 points in the cell area
I added also 2 corner points to better align map[][] and your image

How to determine whether a point is inside a 2D convex polygon in faster than N time

I know the standard ray casting algorithm for finding whether a point is inside any polygon. However, is there a faster method if you limit yourself to just a convex polygon?
Yes, you can use binary search. You do this by recursively cutting the polygon into a fraction of its size (i.e. half) and checking on which side you are. For example, you can start by checking whether you are on the positive or negative side on the line going through vertex 0 and vertex n/2. Once you have 3 vertices, you simply test versus the remaining two sides, completing the test versus that triangle.
Here's some pseudo-code, that will hopefully make this easier to understand:
function TestConvexPolygon(point, polygon)
if polygon.size == 3 then
return TestTriangle(point, polygon) // constant time
if (TestLine(point, polygon[0], polygon[polygon.size/2]) > 0)
return TestConvexPolygon(point, new polygon from polygon.size/2 to polygon.size-1 and 0)
else
return TestConvexPolygon(point, new polygon from 0 to polygon.size/2)
Another way to visualize the idea is that you can view the polygon as a triangle-fan. You then start by testing your point versus the median interior edge. That will eliminate half of the possible triangles from the fan. Since half a triangle fan is still a triangle fan, you can do this recursively until you only have one triangle left in your fan, which you then test explicitly.
A real implementation needs some index juggling, but is otherwise easy and robust.
As the answer stated, the algorithm is recursive. On each step you cut off the part of the polygon in which the point cannot be. Here is a C++ code:
#include "stdafx.h"
#include <vector>
#include <iostream>
struct vec2d {
double x, y;
vec2d(double _x, double _y) : x(_x), y(_y) {}
};
// Finds the cross product of the vectors: AB x BC
double crossProduct(vec2d pointA, vec2d pointB, vec2d pointC) {
vec2d vectorAB = vec2d(pointB.x - pointA.x, pointB.y - pointA.y);
vec2d vectorBC = vec2d(pointC.x - pointB.x, pointC.y - pointB.y);
return vectorAB.x * vectorBC.y - vectorBC.x * vectorAB.y;
}
// Finds area for the triangle ABC
double S(vec2d A, vec2d B, vec2d C) {
return crossProduct(A, B, C) / 2;
}
bool isPointInsideTriangle(vec2d A, vec2d B, vec2d C, vec2d point)
{
return S(A, B, point) >= 0 && S(B, C, point) >= 0 && S(C, A, point) >= 0;
}
bool isPointAboveLine(vec2d A, vec2d B, vec2d point)
{
return S(A, B, point) >= 0;
}
// O(logN), works only for convex polygons
bool isPointInsidePolygon(std::vector<vec2d> polygon, vec2d point) {
if (polygon.size() == 3) {
return isPointInsideTriangle(polygon[0], polygon[1], polygon[2], point);
}
if (isPointAboveLine(polygon[0], polygon[polygon.size() / 2], point)) {
std::vector<vec2d> polygonAbove(polygon.begin() + polygon.size() / 2, polygon.end());
polygonAbove.emplace(polygonAbove.begin(), polygon[0]);
return isPointInsidePolygon(polygonAbove, point);
}
else {
std::vector<vec2d> polygonBelow(polygon.begin(), polygon.begin() + polygon.size() / 2 + 1);
return isPointInsidePolygon(polygonBelow, point);
}
}
int main()
{
std::vector<vec2d> convexPolygon;
convexPolygon.push_back(vec2d(0, 2));
convexPolygon.push_back(vec2d(2, 0));
convexPolygon.push_back(vec2d(4, 1));
convexPolygon.push_back(vec2d(6, 3));
convexPolygon.push_back(vec2d(6, 4));
convexPolygon.push_back(vec2d(5, 6));
convexPolygon.push_back(vec2d(2, 6));
convexPolygon.push_back(vec2d(1, 4));
std::cout << isPointInsidePolygon(convexPolygon, vec2d(2, 5));
return 0;
}

Resources