Remark:
The algorithm on this page was an early development of a surface
visualisation for an array of points. The algorithm did its job, but generated
much more triangles than actually needed. I can't remember where to change the
algorithm, and I never got around to do so as I use a different approach nowadays.
Recently, I use the marching cubes volume rendering technique to visualise
point clouds and scalar value data sets. Some examples can be found
here.
However, if you want to have a look through the old algorithm, have fun.
The input data is a 3d array of double values
( double
[ ][ ][ ] data ).
The algorithm for the surface triangulation involves three basic steps:
Example:
a sphere in a cubic data set 50*50*50
************************
** java source code **
************************
import java.util.*; /** * A very simple implementation of a point in 3D space. * * @see java.Object * @author Heiko Enderling */ class Point3D extends Object{ int x, y, z; public Point3D(int _x, int _y, int _z){ x=_x; y=_y; z=_z; } /** * Checks if Point3D p1 equals this */ public boolean equals(Point3D p1){ return ( x==p1.x && y==p1.y && z==p1.z ); } /** * Checks if a Point3D p1 is a neighbour * of <i>this</i>. * Neighbouring points are at most 1 step away * in each direction. */ public boolean isNeighbour(Point3D p1){ if (this.equals(p1))return false; int _x =(x>p1.x)?(x-p1.x):(p1.x-x); int _y =(y>p1.y)?(y-p1.y):(p1.y-y); int _z =(z>p1.z)?(z-p1.z):(p1.z-z); return ( _x<=1 && _y<=1 && _z<=1 ); } } /** * A very simple implementation of a triangle in 3D space. * * @see java.Object * @author Heiko Enderling */ class Triangle extends Object{ Point3D p1, p2, p3; public Triangle(Point3D _p1, Point3D _p2, Point3D _p3) { p1=_p1; p2=_p2; p3=_p3; } /** * Checks if Triangle p1 equals this * The triangles are not ordered, * so check all possible points */ public boolean similarTo (Triangle tr ){ return ( p1==tr.p1 && p2==tr.p2 && p3==tr.p3 || p1==tr.p1 && p2==tr.p3 && p3==tr.p2 || p1==tr.p2 && p2==tr.p1 && p3==tr.p3 || p1==tr.p2 && p2==tr.p3 && p3==tr.p1 || p1==tr.p3 && p2==tr.p2 && p3==tr.p1 || p1==tr.p3 && p2==tr.p1 && p3==tr.p2 ); } } /** * A collection of useful functions in 3D space * * @author Heiko Enderling */ class Function{ /** * Checks if the voxel at (x,y,z) in dataset d * is background ( P(x,y,z)<threshold ) * treshold in this example is 0.05 */ public static boolean isBackground(double[][][]d, int x, int y, int z) { try { return (d[x][y][z]<0.05); } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } return false; } /** * Checks if the voxel at (x,y,z) in dataset d * is a boundary voxel * * P(x,y,z) is boundary <- : * * - P(x,y,z) != background && * - at least on neighbour is background */ public static boolean isBoundary (double[][][]d, int x, int y, int z) { if (isBackground(d,x,y,z)) return false; try { return ( isBackground(d,x-1,y-1,z-1) || isBackground(d,x-1,y-1,z ) || isBackground(d,x-1,y-1,z+1) || isBackground(d,x-1,y ,z-1) || isBackground(d,x-1,y ,z ) || isBackground(d,x-1,y ,z+1) || isBackground(d,x-1,y+1,z-1) || isBackground(d,x-1,y+1,z ) || isBackground(d,x-1,y+1,z+1) || isBackground(d,x ,y-1,z-1) || isBackground(d,x ,y-1,z ) || isBackground(d,x ,y-1,z+1) || isBackground(d,x ,y ,z-1) || isBackground(d,x ,y ,z+1) || isBackground(d,x ,y+1,z-1) || isBackground(d,x ,y+1,z ) || isBackground(d,x ,y+1,z+1) || isBackground(d,x+1,y-1,z-1) || isBackground(d,x+1,y-1,z ) || isBackground(d,x+1,y-1,z+1) || isBackground(d,x+1,y ,z-1) || isBackground(d,x+1,y ,z ) || isBackground(d,x+1,y ,z+1) || isBackground(d,x+1,y+1,z-1) || isBackground(d,x+1,y+1,z ) || isBackground(d,x+1,y+1,z+1) ); } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); return false; } } /** * triangulation of the surfaces given in the dataset data * * - find all boundary points in the dataset * * - determine for each boundary point p1 all neighbouring * boundary points * * - check if one of the neighbouring boundary points of p1 * has a neighbouring boundary point which is a neighbour * of p1, too * -> save the found triangle */ public static void triangulation (double[][][] data, int width, int height, int depth, Vector t) { int x,y,z; // a vector to save all boundary points found in data Vector boundaryPoints = new Vector(0); for ( z=1; z<depth-1; z++ ) for ( y=1; y<height-1; y++ ) for ( x=1; x<width-1; x++ ) if (Function.isBoundary(data, x, y, z) ) boundaryPoints.add(new Point3D(x,y,z)); if (t.size()>0)t.clear(); // neighbours_vector is a vector wich should // hold a vector off all neighbour points // -> all neighbours of the fifth point // in the points vector will be stored in // a vector at the 5th position in the // neighbours_vector // (n.b.: only points wich are at a later position // in the points vector to avoid redundancy ) Vector neighbours_vector = new Vector(boundaryPoints.size()); Vector neighbours; Point3D p1; Point3D p2; // create list of neighbours for each point for ( int i=0;i<boundaryPoints.size(); i++ ) { neighbours=new Vector(0); p1=(Point3D)boundaryPoints.elementAt(i); for ( int j=i+1;j<boundaryPoints.size(); j++ ) { p2=(Point3D)boundaryPoints.elementAt(j); if(p1.isNeighbour(p2)) neighbours.add((Point3D)boundaryPoints.elementAt(j)); } neighbours_vector.add(i,neighbours); } // alright, we have a vector points with // all boundary points // we have a vecotr neighbours_vector // with a vector of all neighbours of points[i] // position neighbours_vector[i] Point3D p3; Vector tmp; Vector tmp2; boolean similar; int i,j,k; // go through the entired list of boundary points. // the observed point shall be p1 for ( i=0;i<boundaryPoints.size(); i++ ) { p1=(Point3D)boundaryPoints.elementAt(i); // go through the list of neighbours of p1 tmp=(Vector)neighbours_vector.elementAt(i); for (j=0; j<tmp.size(); j++ ){ p2 = (Point3D)tmp.elementAt(j); // go through the list of neighbours of each // neighbour p2 and check if it is neighbour // of p1, too // if it is a neighbour then create a new triangle tmp2=(Vector)neighbours_vector.elementAt(boundaryPoints.indexOf(p2)); for ( k=0; k<tmp2.size(); k++) if (p1.isNeighbour((Point3D)tmp2.elementAt(k))) t.add(new Triangle (p1, p2, (Point3D)tmp2.elementAt(k) ) ); } //endfor j=0 } //endfor i=0 } }