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.


Surface triangulation of a 3D point cloud in Java

This is a very straight forward implementation of a surface triangulation of a cloud of points in 3D in Java.
( the inner points of the object are not considered )

The input data is a 3d array of double values ( double [ ][ ][ ] data ).
The algorithm for the surface triangulation involves three basic steps:

The obtained list of triangles can be displayed using GL4JAVA.

Example:
a sphere in a cubic data set 50*50*50

example

 

************************
** 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
	
    }
	
}