class MeshPoint {
public float x;
public float y;
public float z;
public float size;
public MeshPoint direction;
public MeshPoint() {
x = 0;
y = 0;
z = 0;
size = 0;
direction = new MeshPoint( true );
}
public MeshPoint( boolean nodirection ) {
x = 0;
y = 0;
z = 0;
size = 0;
}
public MeshPoint( float x, float y, float z ) {
this.x = x;
this.y = y;
this.z = z;
size = 0;
direction = new MeshPoint( true );
}
public void set( float x, float y, float z ) {
this.x = x;
this.y = y;
this.z = z;
}
public void set( MeshPoint mp ) {
x = mp.x;
y = mp.y;
z = mp.z;
}
public void plus( float x, float y, float z ) {
this.x += x;
this.y += y;
this.z += z;
}
public void plus( MeshPoint mp ) {
x += mp.x;
y += mp.y;
z += mp.z;
}
public void minus( float x, float y, float z ) {
this.x -= x;
this.y -= y;
this.z -= z;
}
public void minus( MeshPoint mp ) {
x -= mp.x;
y -= mp.y;
z -= mp.z;
}
public void multiply( float m ) {
x *= m;
y *= m;
z *= m;
}
public float dist( MeshPoint mp ) {
MeshPoint d = new MeshPoint( true );
d.plus( mp );
d.minus( this );
return d.len();
}
public float len() {
return sqrt( pow( x,2 ) + pow( y,2 ) + pow( z,2 ) );
}
public void normalise() {
float l = len();
l = 1/l;
x *= l;
y *= l;
z *= l;
}
}
// copied and adapted from http://code.google.com/p/webgltimer/source/browse/src/net/icapsid/counter/client/Icosahedron.java?r=170e4fcc41bf20700dcb6dc67272073af112c65c
import java.util.Iterator;
import java.util.List;
public class Icosahedron {
/*
public List< MeshPoint > pts = new ArrayList< MeshPoint >();
public List< Integer[] > faces = new ArrayList< Integer[] >();
public List< MeshPoint > faceNormals = new ArrayList< MeshPoint >();
*/
public MeshPoint[] pts;
public int[][] faces;
public MeshPoint[] faceNormals;
// public List<Float> vertexNormalsList = new ArrayList<Float>();
// public List<Float> vertexList = new ArrayList<Float>();
private final float X = 0.525731112119133606f;
private final float Z = 0.850650808352039932f;
private final float vdata[][] = {
{ -X, 0.0f, Z },
{ X, 0.0f, Z },
{ -X, 0.0f, -Z },
{ X, 0.0f, -Z },
{ 0.0f, Z, X },
{ 0.0f, Z, -X },
{ 0.0f, -Z, X },
{ 0.0f, -Z, -X },
{ Z, X, 0.0f },
{ -Z, X, 0.0f },
{ Z, -X, 0.0f },
{ -Z, -X, 0.0f }
};
private final int tindices[][] = {
{ 0, 4, 1 }, { 0, 9, 4 }, { 9, 5, 4 }, { 4, 5, 8 },
{ 4, 8, 1 }, { 8, 10, 1 }, { 8, 3, 10 }, { 5, 3, 8 },
{ 5, 2, 3 }, { 2, 7, 3 }, { 7, 10, 3 }, { 7, 6, 10 },
{ 7, 11, 6 }, { 11, 0, 6 }, { 0, 1, 6 }, { 6, 1, 10 },
{ 9, 0, 11 }, { 9, 11, 2 }, { 9, 2, 5 }, { 7, 2, 11 } };
int divisions;
int ptsNum;
int ptsCurrentIndex;
int facesNum;
int facesCurrentIndex;
public Icosahedron( int divisions ) {
this.divisions = divisions;
ptsCurrentIndex = 0;
ptsNum = renderPtsNum();
facesCurrentIndex = 0;
facesNum = renderFacesNum();
// println( "divisions: " + divisions + " => ptsNum: "+ptsNum +" / facesNum: "+ facesNum );
pts = new MeshPoint[ ptsNum ];
faces = new int[ facesNum ][ 3 ];
faceNormals = new MeshPoint[ facesNum ];
// Iterate over points
for (int i = 0; i < 20; ++i) {
subdivide(
vdata[tindices[i][0]],
vdata[tindices[i][1]],
vdata[tindices[i][2]], divisions );
}
renderNormals();
}
public int renderPtsNum() {
int output = 12;
int d = divisions;
while( d > 0 ) {
output = ( ( output - 2 ) * 4 ) + 2;
d--;
}
return output;
}
public int renderFacesNum() {
return (int) (20 * pow( 4, divisions ));
}
public void renderNormals() {
float third = 1.f / 3;
int i = 0;
while ( i < facesNum ) {
int[] li = faces[i];
MeshPoint mp = faceNormals[i];
mp.set( 0,0,0 );
mp.direction.set( 0,0,0 );
MeshPoint ref = pts[ li[0] ];
mp.plus( ref.x,ref.y,ref.z );
mp.direction.plus( ref.direction.x,ref.direction.y,ref.direction.z );
ref = pts[ li[1] ];
mp.plus( ref.x,ref.y,ref.z );
mp.direction.plus( ref.direction.x,ref.direction.y,ref.direction.z );
ref = pts[ li[2] ];
mp.plus( ref.x,ref.y,ref.z );
mp.direction.plus( ref.direction.x,ref.direction.y,ref.direction.z );
mp.multiply( third );
mp.direction.multiply( third );
i++;
}
}
private void norm(float v[]){
float len = 0;
for(int i = 0; i < 3; ++i){
len += v[i] * v[i];
}
len = (float) Math.sqrt(len);
for(int i = 0; i < 3; ++i){
v[i] /= len;
}
}
private int add(float v[]){
int found = -1;
for ( int i = 0; i < ptsCurrentIndex; i++ ) {
MeshPoint mp = pts[i];
if ( mp.x == v[0] && mp.y == v[1] && mp.z == v[2] ) {
found = i;
break;
}
}
if ( found == -1 ) {
pts[ptsCurrentIndex] = new MeshPoint();
pts[ptsCurrentIndex].set( v[0], v[1], v[2] );
pts[ptsCurrentIndex].direction.set( v[0], v[1], v[2] );
found = ptsCurrentIndex;
ptsCurrentIndex++;
}
return found;
}
private void subdivide(float v1[], float v2[], float v3[], int depth) {
if (depth == 0) {
int f1 = add(v1);
int f2 = add(v2);
int f3 = add(v3);
faces[facesCurrentIndex] = new int[] { f1,f2,f3 };
faceNormals[facesCurrentIndex] = new MeshPoint();
facesCurrentIndex++;
return;
}
float v12[] = new float[3];
float v23[] = new float[3];
float v31[] = new float[3];
for (int i = 0; i < 3; ++i) {
v12[i] = (v1[i] + v2[i]) / 2f;
v23[i] = (v2[i] + v3[i]) / 2f;
v31[i] = (v3[i] + v1[i]) / 2f;
}
norm(v12);
norm(v23);
norm(v31);
subdivide(v1, v12, v31, depth - 1);
subdivide(v2, v23, v12, depth - 1);
subdivide(v3, v31, v23, depth - 1);
subdivide(v12, v23, v31, depth - 1);
}
}
float margins = 150;
float biggestdist = 0;
MeshPoint barycenter;
MeshPoint boundaryMin;
MeshPoint boundaryMax;
public ArrayList < MeshPoint > refpoints;
Icosahedron ico;
float icosize = 300;
static int ICOSUBDIVISION = 2;
boolean fillbag = true;
void setup() {
size( 800,600, P3D );
lights();
barycenter = new MeshPoint( true );
ico = new Icosahedron( ICOSUBDIVISION );
boundaryMin = new MeshPoint( true );
boundaryMin.set( -( (width-margins) * 0.5f ), -( (height-margins) * 0.5f ), -( (height-margins) * 0.5f ) );
boundaryMax = new MeshPoint( true );
boundaryMax.set( ( (width-margins) * 0.5f ), ( (height-margins) * 0.5f ), ( (height-margins) * 0.5f ) );
refpoints = new ArrayList < MeshPoint >();
for ( int i = 0; i < 20; i++ ) {
MeshPoint mp = new MeshPoint(
random( boundaryMin.x, boundaryMax.x ),
random( boundaryMin.y, boundaryMax.y ),
random( boundaryMin.z, boundaryMax.z ) );
/*
MeshPoint mp = new MeshPoint(
random( boundaryMin.x, boundaryMax.x ),
random( boundaryMin.y, boundaryMax.y ),
random( boundaryMin.z, boundaryMax.z ) );
*/
mp.size = 30 + i * 10;
mp.direction.set( random( -1,1 ), random( -1,1 ), random( -1,1 ) );
// mp.direction.set( random( -1,1 ), random( -1,1 ), 0 );
mp.direction.normalise();
mp.direction.multiply( 1.f );
refpoints.add( mp );
}
noStroke();
fill( 255,0,0 );
}
void update() {
for ( int i = 0; i < refpoints.size(); i++ ) {
MeshPoint pt = refpoints.get(i);
pt.plus( pt.direction );
if ( pt.x + pt.direction.x < boundaryMin.x || pt.x + pt.direction.x > boundaryMax.x )
pt.direction.x *= -1;
if ( pt.y + pt.direction.y < boundaryMin.y || pt.y + pt.direction.y > boundaryMax.y )
pt.direction.y *= -1;
if ( pt.z + pt.direction.z < boundaryMin.z || pt.z + pt.direction.z > boundaryMax.z )
pt.direction.z *= -1;
}
float totalsize = 0;
for ( int i = 0; i < refpoints.size(); i++ ) { totalsize += refpoints.get(i).size; }
barycenter.set( 0,0,0 );
MeshPoint tmp = new MeshPoint( true );
for ( int i = 0; i < refpoints.size(); i++ ) {
tmp.set( refpoints.get(i) );
tmp.multiply( refpoints.get(i).size / totalsize );
barycenter.plus( tmp );
}
biggestdist = 0;
for ( int i = 0; i < refpoints.size(); i++ ) {
float tmd = barycenter.dist( refpoints.get(i) ) + ( refpoints.get(i).size * 0.5f ) + 10;
if ( tmd > biggestdist ) {
biggestdist = tmd;
}
}
icosize = biggestdist;
int previousclosest = 0;
MeshPoint tm = new MeshPoint();
MeshPoint m = new MeshPoint();
for ( int i = 0; i < ico.ptsNum; i++ ) {
tm.set( ico.pts[i] );
tm.multiply( biggestdist );
m.set( barycenter );
m.plus( tm );
// closest point
int closest = 0;
float d = 0;
for ( int p = 0; p < refpoints.size(); p++ ) {
MeshPoint pt = refpoints.get(p);
if ( p == 0 ) {
d = m.dist( pt ) - pt.size * 0.5f;
} else {
float tmd = m.dist( pt ) - pt.size * 0.5f;
if ( d > tmd ) {
closest = p;
d = tmd;
}
}
}
// setting the direction towards the closest point:
m.direction.set( refpoints.get( closest ) );
m.direction.minus( m );
float tml = m.direction.len();
m.direction.normalise();
m.direction.multiply( tml - refpoints.get( closest ).size * 0.5f );
// smooth gaps
/*
if ( i > 0 && previousclosest != closest ) {
// the current mesh point points to a different point then the prvious one!
// creating a point in the middle
MeshPoint prevmp = mesh.get( i-1 );
MeshPoint middle = new MeshPoint( true );
middle.set( prevmp.x + prevmp.direction.x, prevmp.y + prevmp.direction.y, prevmp.z + prevmp.direction.z );
middle.minus( m.x + m.direction.x, m.y + m.direction.y, m.z + m.direction.z );
middle.multiply( 1.f / 3 );
m.direction.plus( middle );
prevmp.direction.minus( middle );
}
*/
ico.pts[i].direction.set( m.direction );
previousclosest = closest;
}
}
void draw() {
float rx = frameCount / 700.f;
float ry = frameCount / 320.f;
// float rx = 0;
// float ry = 0;
update();
background( 255 );
ambientLight(30, 30, 30);
pushMatrix();
translate( width * 0.5, height * 0.5, 0 );
// rotateX( rx * 3 );
directionalLight(255, 255, 255, 0, -0.3, -1);
popMatrix();
PVector f1 = new PVector();
PVector f2 = new PVector();
PVector f3 = new PVector();
pushMatrix();
translate( width * 0.5, height * 0.5, -width * 0.25 );
rotateX( rx );
rotateY( ry );
pushMatrix();
translate( barycenter.x, barycenter.y, barycenter.z );
/*
noFill();
stroke( 255,0,0 );
for ( int i = 0; i < ico.facesNum; i++ ) {
beginShape();
for ( int k = 0; k < 3; k++ )
vertex( ico.pts[ ico.faces[i][k] ].x * icosize, ico.pts[ ico.faces[i][k] ].y * icosize, ico.pts[ ico.faces[i][k] ].z * icosize );
endShape( CLOSE );
}
*/
noStroke();
fill( 0,120,255 );
for ( int i = 0; i < ico.ptsNum; i++ ) {
MeshPoint mp = ico.pts[i];
pushMatrix();
translate( mp.x * icosize, mp.y * icosize, mp.z * icosize );
rotateY( -ry );
rotateX( -rx );
noStroke();
ellipse( 0,0, 5,5 );
popMatrix();
pushMatrix();
translate( mp.x * icosize, mp.y * icosize, mp.z * icosize );
stroke( 0,255,255 );
line( 0,0,0, mp.direction.x, mp.direction.y, mp.direction.z );
popMatrix();
}
if( fillbag ) {
noStroke();
fill( 255,0,0 );
} else {
noFill();
stroke( 255,0,0 );
strokeWeight( 2 );
}
for ( int i = 0; i < ico.facesNum; i++ ) {
beginShape();
for ( int k = 0; k < 3; k++ )
vertex(
(ico.pts[ ico.faces[i][k] ].x * icosize) + ico.pts[ ico.faces[i][k] ].direction.x,
(ico.pts[ ico.faces[i][k] ].y * icosize) + ico.pts[ ico.faces[i][k] ].direction.y,
(ico.pts[ ico.faces[i][k] ].z * icosize) + ico.pts[ ico.faces[i][k] ].direction.z );
endShape( CLOSE );
}
strokeWeight( 1 );
/*
for ( int i = 0; i < ico.vertexList.size(); i+=9 ) {
f1.x = ico.vertexList.get(i) * icosize;
f1.y = ico.vertexList.get(i+1) * icosize;
f1.z = ico.vertexList.get(i+2) * icosize;
f2.x = ico.vertexList.get(i+3) * icosize;
f2.y = ico.vertexList.get(i+4) * icosize;
f2.z = ico.vertexList.get(i+5) * icosize;
f3.x = ico.vertexList.get(i+6) * icosize;
f3.y = ico.vertexList.get(i+7) * icosize;
f3.z = ico.vertexList.get(i+8) * icosize;
beginShape();
vertex( f1.x, f1.y, f1.z );
vertex( f2.x, f2.y, f2.z );
vertex( f3.x, f3.y, f3.z );
endShape( CLOSE );
}
fill( 0,255,255 );
for ( int i = 0; i < ico.pts.size(); i++ ) {
MeshPoint mp = ico.pts.get(i);
pushMatrix();
translate( mp.x * icosize, mp.y * icosize, mp.z * icosize );
rotateY( -ry );
rotateX( -rx );
noStroke();
ellipse( 0,0, 10,10 );
popMatrix();
pushMatrix();
translate( mp.x * icosize, mp.y * icosize, mp.z * icosize );
stroke( 0,255,255 );
line( 0,0,0, mp.direction.x, mp.direction.y, mp.direction.z );
popMatrix();
pushMatrix();
// rotateX( rx );
// rotateY( ry );
// stroke( 0,255,255 );
translate(
mp.x * icosize + mp.direction.x,
mp.y * icosize + mp.direction.y,
mp.z * icosize + mp.direction.z );
rotateY( -ry );
rotateX( -rx );
ellipse( 0,0, 10,10 );
// line( 0,0,0, mp.direction.x, mp.direction.y, mp.direction.z );
popMatrix();
}
*/
popMatrix();
// drawing the balls
noFill();
strokeWeight( 2 );
for ( int i = 0; i < refpoints.size(); i++ ) {
MeshPoint pt = refpoints.get(i);
pushMatrix();
translate( pt.x, pt.y, pt.z );
rotateY( -ry );
rotateX( -rx );
stroke( 0,0,0 );
line( -5,0, 5,0 );
line( 0,-5, 0,5 );
// stroke( 0,0,0 );
ellipse( 0,0, pt.size,pt.size );
popMatrix();
}
strokeWeight( 1 );
popMatrix();
/*
fill( 0,0,0 );
text("icosahedron division: "+int( ICOSUBDIVISION ),20,40);
text("points: "+int( ico.ptsNum ),20,60);
text("faces: "+int( ico.facesNum ),20,80);
text("fps: "+int(frameRate),20,100);
*/
}
void keyPressed() {
if ( keyCode == 38 ) { // [up]
if ( ICOSUBDIVISION == 5 )
return;
ICOSUBDIVISION++;
ico = new Icosahedron( ICOSUBDIVISION );
} else if ( keyCode == 40 ) { // [down]
if ( ICOSUBDIVISION == 0 )
return;
ICOSUBDIVISION--;
ico = new Icosahedron( ICOSUBDIVISION );
} else if ( keyCode == 70 ) { // 'f'
fillbag = !fillbag;
} else {
println( keyCode );
}
}
Same process as the bag of points in 2D.
A sub-divided icosahedron is used to create a mesh around the points.
Press [up] and [down] to change the mesh definition.
Press 'f' to fill or not the mesh.