I will start off with some background for this problem. It is an extension of several easier problems. Strangely the final extension leads to it being almost impossible to solve.
Given a matrix of integers with 1s, and 0s. 1s representing land and 0s representing ocean count the amount of islands in the matrix which is clusters of 1s separated from each other.
Extend the above problem to donuts. Given the same thing as above only count donuts. That means clusters of 1s with one or more holes of "water" or 0s in it. There can be donuts within donuts.
Extend the above problem to 3D. When you extend the problem above to 3D it becomes hard enough that I will move the question away from "counting donuts" and more towards "is donut?" So in other words, instead of counting clusters of 1s, now you are told that in this 3D grid space there is one and only one cluster of voxels. That cluster is either a donut or it is not a donut. Which means it has a hole (or several holes) going through it or it does not. Write an algorithm to identify this.
Each question is a more challenging extension of the other. With (1.) being the simplest and (3.) being the hardest.
The first 2 questions are quite straight forward. 1. is a classic interview question. (2.) is an extension of that; simply color all separate bodies of water via flood fill with a separate "color" (aka number) and all "islands" touching 2 or more colors is a donut (the hole touches one body of water, and the outside touches a different body of water).
However the 3rd question is challenging. I cannot come up with a way. My coworkers at 2 jobs... nobody could find a way. So I post it here. The isDonut algorithm question.
from typing import List
#3D_space is gaunteed to have one and only one cluster of pixels in it.
def isDonut(3d_space: List[List[List[int]]]) -> bool:
#implement this code
There are many solutions that seem correct but actually fail under specific circumstances. Be careful if you decide to answer.
Edit: For clarity I will define what a voxel donut is:
The above is a voxel donut. A cluster of voxels with a hole going through it. I can only define it in high level english terms. You know it when you see it.
A formal definition of what this is in terms of voxels is the solution to this problem and is described in terms of a programming algorithm. I therefore am unable to describe it, as it's basically my question. Essentially you can think of this question as isomorphic to this one: "what is the formal definition of a voxel donut"?
Edit 2: I'm getting some vague answers and people using advanced math that are hard to understand. Let me put it this way. If you have a straightforward answer you should be able to finish off that python function signature above. You do that the answer is correct, whether or not anyone fully understands your reasoning.
Consider the boundary of your shape: that is, all faces that lie between a filled voxel and an empty one.
Let V be the number of vertices on the boundary, E the number of edges on the boundary, and F the number of faces on the boundary. These are easy to compute; just be careful not to count edges & vertices that belong to multiple boundary faces more than once.
A shape is a donut if and only if (1) the boundary faces are connected, and (2) V-E+F=0.
For more information on this strange and magical second condition, see Euler characteristic.
Edit: This definition does not work. See Rawling's comment below.
Here is an attempt to define a donut, first in a continuous world, through a few observations:
A convex set cannot be a donut. Let S be potential donut, and let conv(S) be the convex hull of S. We define the hole to be H := S \ conv(S). Then S is a donut if H has exactly two disjoint contact surfaces with R^3 \ conv(S). (See below for definitions of "conv()" and "".)
Now, in a discrete voxel world. We can do pretty much the same, except that there are some ambiguities. However, since "donut" is rather informal, they can be resolved according to your personal preferences.
We first need to compute conv(S). There are multiple valid answers here. For example, voxels that partially intersect the continuous conv(S) could be considered part or not part of the discrete convex hull. The construction of H is straightforward, and so are the contact surfaces. The second ambiguity concerns the two disjoint surfaces, specifically what constitutes contiguous voxel faces. A restrictive definition would count 12 neighbors for each voxel face (must have a cube edge in common). But this can be extended to many more if adjacent cube vertices are considered enough.
Note that here I considered that if H is shaped like a Y, then S is not a donut. But this could be up for discussion too.
Disclaimer: not a topologist, my vocabulary may be off. Links to definitions:
Convex hull conv(S): https://en.wikipedia.org/wiki/Convex_hull
S \ conv(S): "set complement" / "Boolean subtraction": https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement / https://en.wikipedia.org/wiki/Constructive_solid_geometry
Edit: Here is an illustration. Yellow: donut. Blue: convex hull. Green: hole. Red: surfaces. Generated with https://evanw.github.io/csg.js/
As you already know #1,#2 then lets focus on #3 (detect if 3D voxel cluster has one ore more holes). After some thinking I revised the original algo a bit:
mark border voxels
so any voxel equal to 1 set to 2 if its neigbors any voxel with 0. After this 0 is empty space, 1 is interior, 2 is surface.
use growth fill to create SDR map of your object
so mark all voxels which are set to 1 to 3 if they neighboring voxel set to 2. Then mark with 4 those which neighbors 3 and so on until no voxel set to 1 is left. This will create something like SDR map (distance to surface).
find and count number of local maximums
for objects without holes there should be just one local max however with holes there would be more of them. In edge case few local max voxels could group to small voxel so count those as one.
Here small C++/OpenGL/VCL example:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "gl_simple.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
const int n=40; // voxel map resolution
int map[n][n][n]; // voxel map color
int max_pos[n][3]; // position of local max
int max_cnt=0; // number of local max
int max_dis=0; // number of distinct local max
int pal[32]= // 0xAABBGGRR
{
0x00808080,
0x00707070,
0x00606060,
0x00505050,
0x00404040,
0x00303030,
0x00202020,
0x00101010,
0x00800000,
0x00700000,
0x00600000,
0x00500000,
0x00400000,
0x00300000,
0x00200000,
0x00100000,
0x00008000,
0x00007000,
0x00006000,
0x00005000,
0x00004000,
0x00003000,
0x00002000,
0x00001000,
0x00000080,
0x00000070,
0x00000060,
0x00000050,
0x00000040,
0x00000030,
0x00000020,
0x00000010,
};
//---------------------------------------------------------------------------
void TForm1::draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_COLOR_MATERIAL);
// center the view around map[][][]
float a;
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0,0.0,-80.0);
a=-0.4*float(n); glTranslatef(a,a,a);
a=16.0/float(n); glScalef(a,a,a);
// glRotatef( 15.0,1.0,0.0,0.0);
// render map[][][] as cubes (very slow old api version for simplicity)
int x,y,z,i,j;
for (x=0;x<n;x++)
for (y=0;y<n;y++)
for (z=0;z<n;z++)
if (map[x][y][z])
{
glPushMatrix();
glTranslatef(x+x,y+y,z+z);
glColor4ubv((BYTE*)&(pal[map[x][y][z]&31]));
glBegin(GL_QUADS);
for (i=0;i<3*24;i+=3)
{
glNormal3fv(vao_nor+i);
glVertex3fv(vao_pos+i);
}
glEnd();
glPopMatrix();
}
// local max
glDisable(GL_DEPTH_TEST);
glColor4f(0.9,0.2,0.1,1.0);
for (j=0;j<max_cnt;j++)
{
x=max_pos[j][0];
y=max_pos[j][1];
z=max_pos[j][2];
glPushMatrix();
glTranslatef(x+x,y+y,z+z);
glBegin(GL_QUADS);
for (i=0;i<3*24;i+=3)
{
glNormal3fv(vao_nor+i);
glVertex3fv(vao_pos+i);
}
glEnd();
glPopMatrix();
}
glFlush();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
gl_init(Handle);
// init map[][][]
int x,y,z,xx,yy,zz,c0,c1,c2,e;
int x0=n/2,y0=n/2,z0=n/2,rr0=(n/2)-3; rr0*=rr0; // ball
int x1=n/3,y1=n/2,rr1=(n/5); rr1*=rr1; // cylinder hole
for (x=0;x<n;x++)
for (y=0;y<n;y++)
for (z=0;z<n;z++)
{
// clear map
map[x][y][z]=0;
// ball
xx=x-x0; xx*=xx;
yy=y-y0; yy*=yy;
zz=z-z0; zz*=zz;
if (xx+yy+zz<=rr0) map[x][y][z]=1;
// hole
xx=x-x1; xx*=xx;
yy=y-y1; yy*=yy;
if (xx+yy<=rr1) map[x][y][z]=0;
}
// palette
// for (x=0;(x<n)&&(x<32);x++) map[x][n-1][n-1]=x;
// SDR growth fill
c0=0; // what to neighbor
c1=1; // what to fill
c2=2; // recolor to
for (e=1,c0=0,c1=1,c2=2;e;c0=c2,c2++)
for (e=0,x=1;x<n-1;x++)
for (y=1;y<n-1;y++)
for (z=1;z<n-1;z++)
if (map[x][y][z]==c1)
if ((map[x-1][y][z]==c0)
||(map[x+1][y][z]==c0)
||(map[x][y-1][z]==c0)
||(map[x][y+1][z]==c0)
||(map[x][y][z-1]==c0)
||(map[x][y][z+1]==c0)){ map[x][y][z]=c2; e=1; }
// find local max
max_cnt=0;
max_dis=0;
for (x=1;x<n-1;x++)
for (y=1;y<n-1;y++)
for (z=1;z<n-1;z++)
{
// is local max?
c0=map[x][y][z];
if (map[x-1][y][z]>=c0) continue;
if (map[x+1][y][z]>=c0) continue;
if (map[x][y-1][z]>=c0) continue;
if (map[x][y+1][z]>=c0) continue;
if (map[x][y][z-1]>=c0) continue;
if (map[x][y][z+1]>=c0) continue;
// is connected to another local max?
for (e=0;e<max_cnt;e++)
if (abs(max_pos[e][0]-x)+abs(max_pos[e][1]-y)+abs(max_pos[e][2]-z)==1)
{ e=-1; break; }
if (e>=0) max_dis++;
// add position to list
max_pos[max_cnt][0]=x;
max_pos[max_cnt][1]=y;
max_pos[max_cnt][2]=z;
max_cnt++;
}
Caption=AnsiString().sprintf("local max: %i / %i",max_dis,max_cnt);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
gl_exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
gl_resize(ClientWidth,ClientHeight);
draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
draw();
}
//---------------------------------------------------------------------------
Just ignore the VCL and OpenGL stuff (they are not important) and focus on the stuff marked with // SDR growth fill and // find local max comments...
Here preview for ball without and with hole:
The local max are rendered without depth testing in orange color and their count (distinct / all) are printed in window Caption...
I have a lot of points on the surface of the sphere.
How can I calculate the area/spot of the sphere that has the largest point density?
I need this to be done very fast. If this was a square for example I guess I could create a grid and then let the points vote which part of the grid is the best.
I have tried with transforming the points to spherical coordinates and then do a grid, both this did not work well since points around north pole are close on the sphere but distant after the transform.
Thanks
There is in fact no real reason to partition the sphere into a regular non-overlapping mesh, try this:
partition your sphere into semi-overlapping circles
see here for generating uniformly distributed points (your circle centers)
Dispersing n points uniformly on a sphere
you can identify the points in each circle very fast by a simple dot product..it really doesn't matter if some points are double counted, the circle with the most points still represents the highest density
mathematica implementation
this takes 12 seconds to analyze 5000 points. (and took about 10 minutes to write )
testcircles = { RandomReal[ {0, 1}, {3}] // Normalize};
Do[While[ (test = RandomReal[ {-1, 1}, {3}] // Normalize ;
Select[testcircles , #.test > .9 & , 1] ) == {} ];
AppendTo[testcircles, test];, {2000}];
vmax = testcircles[[First#
Ordering[-Table[
Count[ (testcircles[[i]].#) & /# points , x_ /; x > .98 ] ,
{i, Length[testcircles]}], 1]]];
To add some other, alternative schemes to the mix: it's possible to define a number of (almost) regular grids on sphere-like geometries by refining an inscribed polyhedron.
The first option is called an icosahedral grid, which is a triangulation of the spherical surface. By joining the centres of the triangles about each vertex, you can also create a dual hexagonal grid based on the underlying triangulation:
Another option, if you dislike triangles (and/or hexagons) is the cubed-sphere grid, formed by subdividing the faces of an inscribed cube and projecting the result onto the spherical surface:
In either case, the important point is that the resulting grids are almost regular -- so to evaluate the region of highest density on the sphere you can simply perform a histogram-style analysis, counting the number of samples per grid cell.
As a number of commenters have pointed out, to account for the slight irregularity in the grid it's possible to normalise the histogram counts by dividing through by the area of each grid cell. The resulting density is then given as a "per unit area" measure. To calculate the area of each grid cell there are two options: (i) you could calculate the "flat" area of each cell, by assuming that the edges are straight lines -- such an approximation is probably pretty good when the grid is sufficiently dense, or (ii) you can calculate the "true" surface areas by evaluating the necessary surface integrals.
If you are interested in performing the requisite "point-in-cell" queries efficiently, one approach is to construct the grid as a quadtree -- starting with a coarse inscribed polyhedron and refining it's faces into a tree of sub-faces. To locate the enclosing cell you can simply traverse the tree from the root, which is typically an O(log(n)) operation.
You can get some additional information regarding these grid types here.
Treating points on a sphere as 3D points might not be so bad.
Try either:
Select k, do approximate k-NN search in 3D for each point in the data or selected point of interest, then weight the result by their distance to the query point. Complexity may vary for different approximate k-NN algorithms.
Build a space-partitioning data structure like k-d Tree, then do approximate (or exact) range counting query with a ball range centered at each point in the data or selected point of interest. Complexity is O(log(n) + epsilon^(-3)) or O(epsilon^(-3)*log(n)) for each approximate range query with state of the art algorithms, where epsilon is the range error threshold w.r.t. the size of the querying ball. For exact range query, the complexity is O(n^(2/3)) for each query.
Partition the sphere into equal-area regions (bounded by parallels and meridians) as described in my answer there and count the points in each region.
The aspect ratio of the regions will not be uniform (the equatorial regions will be more "squarish" when N~M, while the polar regions will be more elongated).
This is not a problem because the diameters of the regions go to 0 as N and M increase.
The computational simplicity of this method trumps the better uniformity of domains in the other excellent answers which contain beautiful pictures.
One simple modification would be to add two "polar cap" regions to the N*M regions described in the linked answer to improve the numeric stability (when the point is very close to a pole, its longitude is not well defined). This way the aspect ratio of the regions is bounded.
You can use the Peters projection, which preserves the areas.
This will allow you to efficiently count the points in a grid, but also in a sliding window (box Parzen window) by using the integral image trick.
If I understand correctly, you are trying to find the densepoint on sphere.
if points are denser at some point
Consider Cartesian coordinates and find the mean X,Y,Z of points
Find closest point to mean X,Y,Z that is on sphere (you may consider using spherical coordinates, just extend the radius to original radius).
Constraints
If distance between mean X,Y,Z and the center is less than r/2, then this algorithm may not work as desired.
I am not master of mathematics but may be it can solve by analytical way as:
1.Short the coordinate
2.R=(Σ(n=0. n=max)(Σ(m=0. M=n)(1/A^diff_in_consecative))*angle)/Σangle
A=may any constant
This is really just an inverse of this answer of mine
just invert the equations of equidistant sphere surface vertexes to surface cell index. Don't even try to visualize the cell different then circle or you go mad. But if someone actually do it then please post the result here (and let me now)
Now just create 2D cell map and do the density computation in O(N) (like histograms are done) similar to what Darren Engwirda propose in his answer
This is how the code looks like in C++
//---------------------------------------------------------------------------
const int na=16; // sphere slices
int nb[na]; // cells per slice
const int na2=na<<1;
int map[na][na2]; // surface cells
const double da=M_PI/double(na-1); // latitude angle step
double db[na]; // longitude angle step per slice
// sherical -> orthonormal
void abr2xyz(double &x,double &y,double &z,double a,double b,double R)
{
double r;
r=R*cos(a);
z=R*sin(a);
y=r*sin(b);
x=r*cos(b);
}
// sherical -> surface cell
void ab2ij(int &i,int &j,double a,double b)
{
i=double(((a+(0.5*M_PI))/da)+0.5);
if (i>=na) i=na-1;
if (i< 0) i=0;
j=double(( b /db[i])+0.5);
while (j< 0) j+=nb[i];
while (j>=nb[i]) j-=nb[i];
}
// sherical <- surface cell
void ij2ab(double &a,double &b,int i,int j)
{
if (i>=na) i=na-1;
if (i< 0) i=0;
a=-(0.5*M_PI)+(double(i)*da);
b= double(j)*db[i];
}
// init variables and clear map
void ij_init()
{
int i,j;
double a;
for (a=-0.5*M_PI,i=0;i<na;i++,a+=da)
{
nb[i]=ceil(2.0*M_PI*cos(a)/da); // compute actual circle cell count
if (nb[i]<=0) nb[i]=1;
db[i]=2.0*M_PI/double(nb[i]); // longitude angle step
if ((i==0)||(i==na-1)) { nb[i]=1; db[i]=1.0; }
for (j=0;j<na2;j++) map[i][j]=0; // clear cell map
}
}
//---------------------------------------------------------------------------
// this just draws circle from point x0,y0,z0 with normal nx,ny,nz and radius r
// need some vector stuff of mine so i did not copy the body here (it is not important)
void glCircle3D(double x0,double y0,double z0,double nx,double ny,double nz,double r,bool _fill);
//---------------------------------------------------------------------------
void analyse()
{
// n is number of points and r is just visual radius of sphere for rendering
int i,j,ii,jj,n=1000;
double x,y,z,a,b,c,cm=1.0/10.0,r=1.0;
// init
ij_init(); // init variables and map[][]
RandSeed=10; // just to have the same random points generated every frame (do not need to store them)
// generate draw and process some random surface points
for (i=0;i<n;i++)
{
a=M_PI*(Random()-0.5);
b=M_PI* Random()*2.0 ;
ab2ij(ii,jj,a,b); // cell corrds
abr2xyz(x,y,z,a,b,r); // 3D orthonormal coords
map[ii][jj]++; // update cell density
// this just draw the point (x,y,z) as line in OpenGL so you can ignore this
double w=1.1; // w-1.0 is rendered line size factor
glBegin(GL_LINES);
glColor3f(1.0,1.0,1.0); glVertex3d(x,y,z);
glColor3f(0.0,0.0,0.0); glVertex3d(w*x,w*y,w*z);
glEnd();
}
// draw cell grid (color is function of density)
for (i=0;i<na;i++)
for (j=0;j<nb[i];j++)
{
ij2ab(a,b,i,j); abr2xyz(x,y,z,a,b,r);
c=map[i][j]; c=0.1+(c*cm); if (c>1.0) c=1.0;
glColor3f(0.2,0.2,0.2); glCircle3D(x,y,z,x,y,z,0.45*da,0); // outline
glColor3f(0.1,0.1,c ); glCircle3D(x,y,z,x,y,z,0.45*da,1); // filled by bluish color the more dense the cell the more bright it is
}
}
//---------------------------------------------------------------------------
The result looks like this:
so now just see what is in the map[][] array you can find the global/local min/max of density or whatever you need... Just do not forget that the size is map[na][nb[i]] where i is the first index in array. The grid size is controlled by na constant and cm is just density to color scale ...
[edit1] got the Quad grid which is far more accurate representation of used mapping
this is with na=16 the worst rounding errors are on poles. If you want to be precise then you can weight density by cell surface size. For all non pole cells it is simple quad. For poles its triangle fan (regular polygon)
This is the grid draw code:
// draw cell quad grid (color is function of density)
int i,j,ii,jj;
double x,y,z,a,b,c,cm=1.0/10.0,mm=0.49,r=1.0;
double dx=mm*da,dy;
for (i=1;i<na-1;i++) // ignore poles
for (j=0;j<nb[i];j++)
{
dy=mm*db[i];
ij2ab(a,b,i,j);
c=map[i][j]; c=0.1+(c*cm); if (c>1.0) c=1.0;
glColor3f(0.2,0.2,0.2);
glBegin(GL_LINE_LOOP);
abr2xyz(x,y,z,a-dx,b-dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a-dx,b+dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a+dx,b+dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a+dx,b-dy,r); glVertex3d(x,y,z);
glEnd();
glColor3f(0.1,0.1,c );
glBegin(GL_QUADS);
abr2xyz(x,y,z,a-dx,b-dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a-dx,b+dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a+dx,b+dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a+dx,b-dy,r); glVertex3d(x,y,z);
glEnd();
}
i=0; j=0; ii=i+1; dy=mm*db[ii];
ij2ab(a,b,i,j); c=map[i][j]; c=0.1+(c*cm); if (c>1.0) c=1.0;
glColor3f(0.2,0.2,0.2);
glBegin(GL_LINE_LOOP);
for (j=0;j<nb[ii];j++) { ij2ab(a,b,ii,j); abr2xyz(x,y,z,a-dx,b-dy,r); glVertex3d(x,y,z); }
glEnd();
glColor3f(0.1,0.1,c );
glBegin(GL_TRIANGLE_FAN); abr2xyz(x,y,z,a ,b ,r); glVertex3d(x,y,z);
for (j=0;j<nb[ii];j++) { ij2ab(a,b,ii,j); abr2xyz(x,y,z,a-dx,b-dy,r); glVertex3d(x,y,z); }
glEnd();
i=na-1; j=0; ii=i-1; dy=mm*db[ii];
ij2ab(a,b,i,j); c=map[i][j]; c=0.1+(c*cm); if (c>1.0) c=1.0;
glColor3f(0.2,0.2,0.2);
glBegin(GL_LINE_LOOP);
for (j=0;j<nb[ii];j++) { ij2ab(a,b,ii,j); abr2xyz(x,y,z,a-dx,b+dy,r); glVertex3d(x,y,z); }
glEnd();
glColor3f(0.1,0.1,c );
glBegin(GL_TRIANGLE_FAN); abr2xyz(x,y,z,a ,b ,r); glVertex3d(x,y,z);
for (j=0;j<nb[ii];j++) { ij2ab(a,b,ii,j); abr2xyz(x,y,z,a-dx,b+dy,r); glVertex3d(x,y,z); }
glEnd();
the mm is the grid cell size mm=0.5 is full cell size , less creates a space between cells
If you want a radial region of the greatest density, this is the robust disk covering problem with k = 1 and dist(a, b) = great circle distance (a, b) (see https://en.wikipedia.org/wiki/Great-circle_distance)
https://www4.comp.polyu.edu.hk/~csbxiao/paper/2003%20and%20before/PDCS2003.pdf
Consider using a geographic method to solve this. GIS tools, geography data types in SQL, etc. all handle curvature of a spheroid. You might have to find a coordinate system that uses a pure sphere instead of an earthlike spheroid if you are not actually modelling something on Earth.
For speed, if you have large numbers of points and want the densest location of them, a raster heatmap type solution might work well. You could create low resolution rasters, then zoom to areas of high density and create higher resolution only cells that you care about.
Actually, I can detect border or edges of a convex triangular mesh by checking which edge of the triangle does not have any neighbor. So, if a mesh has some holes, then we can highlight that part easily because we have edge vertices.
But the issue is, if we just have the edge vertices or borders, how can we know that the mesh has some holes ? and how many holes the mesh has ?
I thought enough about this issue, but unable to get it, any idea ? What should be the condition or check for hole detection ?
After detection of a hole I want to fill it. But first thing is to detect it ?
Thanks.
Assuming that the mesh is connected and you can highlight all the boundaries. You are left with all the holes + one additional boundary which is that of mesh itself. You can just discard the boundary with the biggest length of them and get all the holes.
A triangular mesh derived from a scanner (eg. Kinect) can have small fragments (isolated patches) as well as small holes. I suggest a hole can generally be detected by counting the number of vertices that neighbor the vertices on the boundary. It is not a hole if there are fewer neighboring vertices than boundary vertices.
My answer will only work for a closed mesh, but it will handle the case of concave and convex holes.
For the sake of explanation, lets imagine a 2D mesh.
Calculate a bounding box for the mesh. In our example the bounding box needs to store min and max values for the X and Y axis, and a corresponding vertex index for each value:
struct BoundingBox
{
float minX,maxX,minY,maxY;
int vminX,vmaxX,vminY,vmaxY;
}
Iterate over every vertex in the mesh, growing the bounding box as you add each point. When a vertex is responsible for changing one of the min/max values, store or overwrite the corresponding vmin/vmax value with the vertex mesh index.
E.g.
BoundingBox bounds;
bounds.minX = verts[0].X;
bounds.maxX = verts[0].X;
bounds.minY = verts[0].Y;
bounds.maxY = verts[0].Y;
bounds.vminX = bounds.vmaxX = bounds.vminY = bounds.vmaxY = 0;
for (int i = 1; i < numVerts; i++)
{
Vertex v = verts[i];
if (v.X < bounds.minX) { bounds.minX = v.X; bounds.vminX = i; }
if (v.X > bounds.maxX) { bounds.maxX = v.X; bounds.vmaxX = i; }
if (v.Y < bounds.minY) { bounds.minY = v.Y; bounds.vminY = i; }
if (v.Y > bounds.maxY) { bounds.maxY = v.Y; bounds.vmaxY = i; }
}
Now iterate over your boundaries until you find one which contains ALL the vertices you gathered in the bounding box. This is your outer boundary. The remaining boundaries are holes within the mesh.
Indeed, a hole in triangular mesh is a closed loop of adjacent oriented boundary edges, each of which does not have a triangle from one side (e.g. from left).
So the algorithm to enumerate all holes can be as follows
Find all boundary edges (the edges without left triangle) and compose a set with them.
Take any boundary edge from the set, then search for the next edge in the set originating in the vertex where the previous edge terminates, remove it from the set as well and continue until edge loop is closed. This will give you one of the holes.
Repeat step 2. until the set is empty.
Some operations here can be optimized by using specialized data structures for mesh representation, e.g. half-edges.
The animation below demonstrates how starting from an arbitrary boundary edge (under the cursor), the whole hole can be found and highlighted:
(captured in MeshInspector application)
I'm looking to return the coordinates of the points bounding the area of overlap between 2 arbitrary rectangles in 2D. Whats the best way to approach this that would take care of all the special cases eg:
Rectangles intersecting only on a single vertex ? (the program would have to return the lone vertex)
Rectangles which share whole or part of a side ? (the program would have to return the endpoints of the common line segment)
To add to the complexity, it has to order the points in either clockwise/anticlockwise order. As such, I can use a convex hull algorithm to order them before reporting, but if there's an algorithm that can figure out the bounding points in order directly, that'll be the best !!
Any ideas of what avenues I should be looking at ? I'm not looking for code projects etc, only a general idea of a generic algorithm for which I don't have to keep a lot of
if "special case" then "do this" kind of code.
EDIT: The rectangles are arbitrary - i.e. they may/may not be parallel to X/Y axis...
Just use the general convex polygon intersection method. Look up intersect convex polygons rotating calipers.
Alright, we'll try this again...
Consider a union of the two, made up of areas defined by drawing a line from every vertex in ABCD (in black) to EFGH (in red):
The hard part here is coming up with all of the shapes defined by these lines (both the gray lines and the original lines coming from the ABCD and EFGH rectangles.)
Once you figure that out, create a linked list of these shapes, and assume every one of these shapes exists within the intersection.
Step 1. Translate & rotate everything so that ABCD has one vertex on 0,0 and its lines are parallel/perpendicular to the x and y axes.
Step 1A. Find the lowest y-value vertex in ABCD, and then subtract it from all other verts in the scene. Let's assume for the purposes of demonstration that that vertex is C. By subtracting C from every vertex in the scene, we will effectively move the origin (0,0) to C, making rotation around C easy.
for all shapes in linked list {
for all vertices in shape {
vertex -= C;
}
}
Step 1B. Rotate everything about the origin by an angle equal to the angle between the C->B vector and the x-axis, so that B lands on the x-axis:
// see http://en.wikipedia.org/wiki/Atan2
float rotRadians = atan2(B.x, B.y); // assuming ABCD vertices are labelled clockwise
for all shapes in linked list {
for all vertices in shape {
rot(thisVert, rotRadians);
}
}
// ...
// function declaration
void rot(theVertex, theta) {
tempX = theVertex.x;
tempY = theVertex.y;
theVertex.x = cos(theta) * tempX + sin(theta) * tempY;
theVertex.y = cos(theta) * tempY - sin(theta) * tempX;
}
If ABCD vertices were labelled clockwise, the ABCD vertices should now look like this:
A = ABCDx , ABCDy
B = ABCDx , 0
C = 0 , 0
D = 0 , ABCDy
(If they were not labeled clockwise, then the "lies within" check in Step 2 won't work, so make sure the vert used in the atan2(...) call is the vertex counterclockwise from the lowest vertex.)
Step 2. Now we can easily analyze whether or not a shape lies within the ABCD rectangle, e.g. if (thisVert.x >= 0 && thisVert.y >= 0 && thisVert.x <= ABCDx && thisVert.y <= ABCDy). Traverse the linked list of shapes, and check to make sure each vertex of each shape lies within ABCD. If one vertex of a shape does not lie within ABCD, then that shape is not part of the ABCD/EFGH intersection. Mark it as not part of the intersection and skip to the next shape.
Step 3. Undo the rotation from Step 1B, then undo the translation from Step 1A.
Step 4. Repeat Steps 1-3 with EFGH instead of ABCD, and you will have your intersection set.
If the only intersection between the two sets is a line, then the above will return nothing as an intersection. If the intersection == NULL, then check for lines that intersect.
If the intersection is still NULL, then check for intersecting points.
This is probably really rough but:
object rectangle {
pos { x, y } // top-left position
size { height, width } // rectangle-size
}
collision::check (rectangle rect) {
// collision-detection logic
collision->order_coords(coords); // order-coords clockwise;
return collision_result_object; // return collided vertices, ordered clockwise, or 0 if rect hit nothing
}
collision::order_rects (rectangle *rect, opt angle) {
return clockwise_rects; // returns rectangles ordered clockwise
}
collision::order_coords (coordinate *coord, opt angle) {
return ordered_coords; // recieves coordinates and returns ordered clockwise
}
rectangle rects; // bunch of rectangles
ordered_rects = collision->order_rects (rects); // order rects starting at 12PM
loop {
foreach ordered_rects as i {
if (collision->check(i)) {
array_of_collision_result_objects[i] = collision->check(i); // start checking rects starting at 12PM, if collision found, return ordered vertexes
}
}
}
Find all the intersections of segments of rectangles. The result consists of some of them and some of initial vertices. To find them just check for every point it lies in both rectangles. Remove unnecessary points (if there are 3 or more on one line). The result is convex and no point you get is strictly inside it, so (if there are at least 3 of them) sort points from some inner point by angle and enjoy the result.
I've come up with a reasonable method that should cover all possible cases:
All we need is basically 3 steps :
Step 1:
for each side Si of R1
for each side Sj of R2
Check if Si and Sj intersect. If they do, push the point in results array
(This also has to take care of the case in case Si and Sj overlap, which is
basically checking if they have the same equation or not - if so, push in
the points of overlap. This also takes care of the case where a vertex of
R2 lies on Si).
next
next
Step 2:
for each vertex Vi of R1
Check if Vi lies inside R2, If so, push it in the results array.
next
Step 3:
for each vertex Vi of R2
Check if Vi lies inside R1, If so, push it in the results array.
next
Now, order the results array, and return
For step 2 & 3 (how to find if a point lies inside a rectangle) - I'd use this excellent article (the last algorithm stated there).