/*
* Copyright (C) 2010-2014 Mathias Unberath
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/
package edu.stanford.rsl.conrad.geometry.shapes.mesh;
import java.util.Random;
import edu.stanford.rsl.conrad.numerics.SimpleMatrix;
import edu.stanford.rsl.conrad.numerics.SimpleVector;
public class AlexaEmbedding {
/**
* Dimension of the vertices.
*/
static final int dimension = 3;
/**
* Triangular connectivity is needed for Alexa embedding.
*/
static final int connectivity = 3;
/**
* Number of vertices in the mesh.
*/
private int nVertices;
/**
* Number of faces in the mesh.
*/
private int nFaces;
/**
* Vertices in the mesh.
*/
private SimpleMatrix vertices;
/**
* Faces in the mesh.
*/
private SimpleMatrix faces;
/**
* The normals of the faces.
*/
private SimpleMatrix normals;
/**
* The centers of the faces.
*/
private SimpleMatrix centers;
/**
* Number of knots in parametric u direction.
*/
private int nU;
/**
* Number of knots in parametric v direction.
*/
private int nV;
//==========================================================================================
// METHODS
//==========================================================================================
/**
* Construct object and set class member.
* @param v Vertices.
* @param f Faces.
* @param nKnotsU Number of knots in parametric u-direction.
* @param nKnotsV Number of knots in parametric v-direction.
*/
public AlexaEmbedding(SimpleMatrix v, SimpleMatrix f, int nKnotsU, int nKnotsV){
assert(v.getCols() == dimension) : new Exception("Alexa embedding not defined for meshes with dimension other than 3.");
assert(f.getCols() == connectivity) : new Exception("Alexa embedding not defined for non-triangular meshes.");
assert(nKnotsU > 0 && nKnotsV > 0) : new Exception("Number of knots in u and v direction must be greater than 0");
this.vertices = v;
this.nVertices = v.getRows();
this.faces = f;
this.nFaces = f.getRows();
this.nU = nKnotsU;
this.nV = nKnotsV;
}
/**
* Calculates the normals and centers of mass for all triangle faces of the mesh.
*/
private void getNormalsAndCenters(){
this.centers = new SimpleMatrix(nFaces, dimension);
this.normals = new SimpleMatrix(nFaces, dimension);
SimpleVector a;
SimpleVector b;
for(int i = 0; i < nFaces; i++){
// calculate two edges spanning the triangle and the normal of the triangle
a = vertices.getRow((int)faces.getElement(i, 1));
a.subtract(vertices.getRow((int)faces.getElement(i,0)));
b = vertices.getRow((int)faces.getElement(i, 2));
b.subtract(vertices.getRow((int)faces.getElement(i,0)));
normals.setRowValue(i, crossProduct(a,b));
// calculate the center of the triangle
centers.setRowValue(i, vertices.getRow((int)faces.getElement(i,0)));
centers.getRow(i).add(a.multipliedBy(1/connectivity),b.multipliedBy(1/connectivity));
}
}
/**
* Calculates the cross product of the two SimpleVectors a and b, c = a x b.
* Note: only defined in 3 dimensions.
* @param a Left vector.
* @param b Right vector.
* @return The cross product.
*/
private SimpleVector crossProduct(SimpleVector a, SimpleVector b){
assert(a.getLen() == 3 && a.getLen() == b.getLen()) : new Exception("Input not fulfilling the requirement dim(a) = dim(b) = 3.");
SimpleVector c = new SimpleVector(a.getLen());
c.setElementValue(0, + a.getElement(1)*b.getElement(2) - a.getElement(2)*b.getElement(1));
c.setElementValue(1, - a.getElement(0)*b.getElement(2) + a.getElement(2)*b.getElement(0));
c.setElementValue(2, + a.getElement(0)*b.getElement(1) - a.getElement(1)*b.getElement(0));
return c;
}
/**
* Calculates the smallest enclosing sphere of the mesh using Jack Ritter's approximative algorithm described in:
* Ritter, Jack. "An efficient bounding sphere." Graphics gems. Academic Press Professional, Inc., 1990.
* @return The parameters of the enclosing sphere where the first n-1 elements of the array contain the center and the last element the radius.
*/
private double[] computeSmallestEnclosingSphere(){
double[] radius = new double[dimension + 1];
Random rand = new Random();
int initIdx = rand.nextInt(nVertices);
int idx1 = initIdx;
int idx2 = initIdx;
// find point idx1 with largest distance from randomly drawn point
double dist = 0;
for(int i = 0; i < nVertices; i++){
double d = getDistance(initIdx,i);
if(d > dist){
dist = d;
idx1 = i;
}
}
// find point with largest distance to idx1
dist = 0;
for(int i = 0; i < nVertices; i++){
double d = getDistance(idx1,i);
if(d > dist){
dist = d;
idx2 = i;
}
}
// center of bounding sphere is middle of connecting line, radius is half of the distance
SimpleVector center = getCenter(idx1, idx2);
double r = getDistance(idx1, idx2) / 2;
// check if all points are inside the sphere
// if not, create new sphere containing both the old sphere and the point formerly outside
while(true){
initIdx = checkIfPointsOutside(center,r);
if(initIdx == -1){ // if all points inside break loop
break;
}else{ // calculate new center and radius
SimpleVector diff = vertices.getRow(initIdx);
diff.subtract(center);
center.add(diff.multipliedBy((diff.normL2() - r)/2));
r = getDistance(center,initIdx) / 2;
}
}
for(int i = 0; i < dimension; i++){
radius[i] = center.getElement(i);
}
radius[dimension] = r;
return radius;
}
/**
* Checks if any vertex is still outside of the sphere wither center v and radius r.
* @param v The sphere's center.
* @param r The sphere's radius.
* @return The index to the first point outside of the sphere or -1 if sphere is enclosing every point.
*/
private int checkIfPointsOutside(SimpleVector v, double r){
double d = 0;
int i = 0;
while( d < r || i < nVertices ){
d = getDistance(v, i);
i++;
}
if(i == nVertices){
return -1;
}else{
return (i - 1);
}
}
/**
* Calculate the center of the line connecting two vertices stored in the SimpleMatrix as rows i and j.
* @param i Vertex index.
* @param j Vertex index.
* @return The center of the connecting line.
*/
private SimpleVector getCenter(int i, int j){
SimpleVector v = new SimpleVector(dimension);
SimpleVector w = new SimpleVector(dimension);
v = vertices.getRow(i);
v.subtract(vertices.getRow(j));
w = vertices.getRow(i);
w.add(v.dividedBy(2));
return w;
}
/**
* Calculates the distance between a vertex stored in the SimpleMatrix as row j and the point in p.
* @param p Point.
* @param j Vertex index.
* @return The distance between the two vertices.
*/
private double getDistance(SimpleVector p, int j){
p.subtract(vertices.getRow(j));
return p.normL2();
}
/**
* Calculates the distance between two vertices stored in the SimpleMatrix as rows i and j.
* @param i Vertex index.
* @param j Vertex index.
* @return The distance between the two vertices.
*/
private double getDistance(int i, int j){
SimpleVector v = new SimpleVector(dimension);
v = vertices.getRow(i);
v.subtract(vertices.getRow(j));
return v.normL2();
}
}