package edu.stanford.rsl.conrad.geometry.shapes.simple;
import java.util.ArrayList;
import java.util.HashMap;
import edu.stanford.rsl.conrad.geometry.AbstractCurve;
import edu.stanford.rsl.conrad.geometry.AbstractShape;
import edu.stanford.rsl.conrad.geometry.General;
import edu.stanford.rsl.conrad.geometry.transforms.Transform;
import edu.stanford.rsl.conrad.numerics.SimpleOperators;
import edu.stanford.rsl.conrad.numerics.SimpleVector;
import edu.stanford.rsl.conrad.utils.Configuration;
import edu.stanford.rsl.conrad.utils.RegKeys;
/**
* Class to describe a triangle in 3D.
*
* @author akmaier
*
*/
public class Triangle extends Plane3D {
/**
*
*/
private static final long serialVersionUID = 3360323076084779150L;
protected double bUcoord, bVcoord;
protected double cUcoord, cVcoord;
protected double raytracingEpsilon; // used to allow a certain tolerance on the decision whether the intersection point of a ray with the 3-D plane lies within the triangle
/**
* Creates a new Triangle from the Points a, b, and c
* @param a
* @param b
* @param c
*/
public Triangle(PointND a, PointND b, PointND c){
super(a, SimpleOperators.subtract(b.getAbstractVector(), a.getAbstractVector()), SimpleOperators.subtract(c.getAbstractVector(), a.getAbstractVector()));
bUcoord = 1;
bVcoord = 0;
cUcoord = 0;
cVcoord = 1;
updateBounds(a, b, c);
setRaytracingEpsilonFromRegistry();
}
public Triangle(Triangle shape){
super(shape);
bUcoord = shape.bUcoord;
bVcoord = shape.bVcoord;
cUcoord = shape.cUcoord;
cVcoord = shape.cVcoord;
setRaytracingEpsilonFromRegistry();
}
/**
* Retrieve the raytracing epsilon from global registry.
* If there is no such entry, set the raytracing epsilon to zero.
*/
private void setRaytracingEpsilonFromRegistry() {
HashMap<String, String> registry = Configuration.getGlobalConfiguration().getRegistry();
if (registry.containsKey(RegKeys.PHANTOM_PROJECTOR_RAYTRACING_EPSILON)) {
raytracingEpsilon = Double.valueOf(Configuration.getGlobalConfiguration().getRegistry().get(RegKeys.PHANTOM_PROJECTOR_RAYTRACING_EPSILON));
} else {
raytracingEpsilon = 0.d;
}
}
@Override
public ArrayList<PointND> getHitsOnBoundingBox(AbstractCurve other){
return intersect(other);
}
/**
* Returns point a
* @return point a
*/
public PointND getA() {
return new PointND(pointP);
}
/**
* Returns point b
* @return point b
*/
public PointND getB() {
return evaluate(bUcoord, bVcoord);
}
/**
* Returns point c
* @return point c
*/
public PointND getC() {
return evaluate(cUcoord, cVcoord);
}
public PointND intersectWithHitOrientation(StraightLine other) {
PointND revan = super.intersect(other);
//System.out.println(revan);
if (isInTriangle(revan)){
// Compute the signum between the ray hitting the triangle and the triangle's orientation
double hitOrientation = SimpleOperators.multiplyInnerProd(normalN, other.direction);
// If the result is smaller than 0, the normal of the triangle is pointing into direction opposed to the ray's direction. It's likely that we enter the object
// If the result is greater than 0, the normal of the triangle is pointing into the same direction as the ray. It's likely that we just left the object.
// We put the computed orientation as another coordinate into the point of intersection
double[] coordinates = revan.getCoordinates();
double[] coordinatesHit = new double[coordinates.length + 1];
System.arraycopy(coordinates, 0, coordinatesHit, 0, coordinates.length);
coordinatesHit[coordinatesHit.length - 1] = hitOrientation;
revan = new PointND(coordinatesHit);
return revan;
} else {
return null;
}
}
@Override
public PointND intersect(StraightLine other) {
PointND revan = super.intersect(other);
//System.out.println(revan);
if (isInTriangle(revan)){
return revan;
} else {
return null;
}
}
@Override
public ArrayList<PointND> intersect(AbstractCurve other) {
if (other instanceof StraightLine) {
try {
ArrayList<PointND> list = new ArrayList<PointND>();
PointND p = intersect((StraightLine) other);
if (p!= null) list.add(p);
return list;
} catch (RuntimeException e){
if (e.getLocalizedMessage().equals("Line is parallel to plane")){
ArrayList<PointND> list = new ArrayList<PointND>();
Edge one = new Edge(getA(), getB());
PointND p = one.intersect((StraightLine) other);
if (p != null) list.add(p);
one = new Edge(getB(), getC());
p = one.intersect((StraightLine) other);
if (p != null) list.add(p);
one = new Edge(getA(), getC());
p = one.intersect((StraightLine) other);
if (p != null) list.add(p);
return list;
} else {
throw(e);
}
}
} else {
throw new RuntimeException("Not implemented yet!");
}
}
/**
* Copy of intersect method which calls intersectWithHitOrientation instead of intersect
*/
@Override
public ArrayList<PointND> intersectWithHitOrientation(AbstractCurve other) {
if (other instanceof StraightLine) {
try {
ArrayList<PointND> list = new ArrayList<PointND>();
PointND p = intersectWithHitOrientation((StraightLine) other);
if (p!= null) list.add(p);
return list;
} catch (RuntimeException e){
if (e.getLocalizedMessage().equals("Line is parallel to plane")){
ArrayList<PointND> list = new ArrayList<PointND>();
Edge one = new Edge(getA(), getB());
PointND p = one.intersect((StraightLine) other);
if (p != null) list.add(p);
one = new Edge(getB(), getC());
p = one.intersect((StraightLine) other);
if (p != null) list.add(p);
one = new Edge(getA(), getC());
p = one.intersect((StraightLine) other);
if (p != null) list.add(p);
return list;
} else {
throw(e);
}
}
} else {
throw new RuntimeException("Not implemented yet!");
}
}
/**
* Computes whether the given point is inside of the triangle. Implementation is based on barycentric coordinates.
* Allows a certain tolerance which is defined in the registry and retrieved in the triangle's constructor.
*
* @param p the point
* @return true if it is inside of the triangle.
*/
public boolean isInTriangle(PointND p){
// vectors
SimpleVector v0 = dirV;
SimpleVector v1 = dirU;
SimpleVector v2 = SimpleOperators.subtract(p.getAbstractVector(), pointP.getAbstractVector());
// Compute dot products
double dot00 = SimpleOperators.multiplyInnerProd(v0, v0);
double dot01 = SimpleOperators.multiplyInnerProd(v0, v1);
double dot02 = SimpleOperators.multiplyInnerProd(v0, v2);
double dot11 = SimpleOperators.multiplyInnerProd(v1, v1);
double dot12 = SimpleOperators.multiplyInnerProd(v1, v2);
// Compute barycentric coordinates
double invDenom = 1.0 / ((dot00 * dot11) - (dot01 * dot01));
double u = ((dot11 * dot02) - (dot01 * dot12)) * invDenom;
double v = ((dot00 * dot12) - (dot01 * dot02)) * invDenom;
return (u >= 0 - raytracingEpsilon) && (v >= 0 - raytracingEpsilon) && (u + v <= 1 + raytracingEpsilon);
}
public PointND[] getRasterPoints(int number){
Edge one = new Edge (getA(), getB());
Edge two = new Edge (getB(), getC());
Edge three = new Edge (getC(), getA());
//System.out.println(getA() + " " + getB() + " " + one.getLastInternalIndex());
PointND [] points1 = one.getRasterPoints(number /3);
//System.out.println("End");
//System.exit(-1);
PointND [] points2 = two.getRasterPoints(number /3);
PointND [] points3 = three.getRasterPoints(number /3);
PointND [] points = new PointND[points1.length + points2.length + points3.length];
System.arraycopy(points1, 0, points, 0, points1.length);
System.arraycopy(points2, 0, points, points1.length, points2.length);
System.arraycopy(points3, 0, points, points1.length + points2.length, points3.length);
return points;
}
@Override
protected void generateBoundingPlanes(){
// Nothing to do here.
}
@Override
public boolean isBounded(){
return true;
}
@Override
public void applyTransform(Transform t) {
SimpleVector buff = t.transform(normalN);
normalN = buff.dividedBy(buff.normL2());
pointP = t.transform(pointP);
offsetD = SimpleOperators.multiplyInnerProd(this.normalN, this.pointP.getAbstractVector());
dirU = t.transform(dirU);
dirV = t.transform(dirV);
updateBounds();
}
@Override
public String toString(){
return "Triangle " + this.getName() + ": " + getA() + " " + getB() + " " + getC();
}
private void updateBounds(){
updateBounds(getA(),getB(),getC());
}
private void updateBounds(PointND a, PointND b, PointND c){
min = new PointND(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
min.updateIfLower(a);
min.updateIfLower(b);
min.updateIfLower(c);
max = new PointND(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE);
max.updateIfHigher(a);
max.updateIfHigher(b);
max.updateIfHigher(c);
}
@Override
public AbstractShape clone() {
return new Triangle(this);
}
}
/*
* Copyright (C) 2010-2014 Andreas Maier, Rotimi X Ojo
* CONRAD is developed as an Open Source project under the GNU General Public License (GPL).
*/