org.scijava.java3d.utils.pickfast.PickIntersection Maven / Gradle / Ivy
Show all versions of j3dutils Show documentation
/*
* Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any
* kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
* WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
* EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
* NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
* USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
* ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
* CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
* REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
* INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed, licensed or
* intended for use in the design, construction, operation or
* maintenance of any nuclear facility.
*
*/
package org.scijava.java3d.utils.pickfast;
import org.scijava.java3d.Geometry;
import org.scijava.java3d.GeometryArray;
import org.scijava.java3d.IndexedGeometryArray;
import org.scijava.java3d.PickInfo;
import org.scijava.java3d.Transform3D;
import org.scijava.vecmath.Color3f;
import org.scijava.vecmath.Color4f;
import org.scijava.vecmath.Point3d;
import org.scijava.vecmath.TexCoord3f;
import org.scijava.vecmath.Vector3d;
import org.scijava.vecmath.Vector3f;
/**
* Holds information about an intersection of a PickShape with a Node
* as part of a PickInfo.IntersectionInfo. Information about
* the intersected geometry, intersected primitive, intersection point, and
* closest vertex can be inquired.
*
* The intersected primitive indicates which primitive out of the GeometryArray
* was intersected (where the primitive is a point, line, triangle or quad,
* not a
* org.scijava.java3d.utils.geometry.Primitive)
.
* For example, the intersection would indicate which triangle out of a
* triangle strip was intersected.
* The methods which return primitive data will have one value if the primitive
* is
* a point, two values if the primitive is a line, three values if the primitive
* is a triangle and four values if the primitive is quad.
*
* The primitive's VWorld coordinates are saved when then intersection is
* calculated. The local coordinates, normal, color and texture coordinates
* for the primitive can also be inquired if they are present and readable.
*
* The intersection point is the location on the primitive which intersects the
* pick shape closest to the center of the pick shape. The intersection point's
* location in VWorld coordinates is saved when the intersection is calculated.
* The local coordinates, normal, color and texture coordiantes of at the
* intersection can be interpolated if they are present and readable.
*
* The closest vertex is the vertex of the primitive closest to the intersection
* point. The vertex index, VWorld coordinates and local coordinates of the
* closest vertex can be inquired. The normal, color and texture coordinate
* of the closest vertex can be inquired from the geometry array:
*
* Vector3f getNormal(PickIntersection pi, int vertexIndex) {
* int index;
* Vector3d normal = new Vector3f();
* GeometryArray ga = pickIntersection.getGeometryArray();
* if (pickIntersection.geometryIsIndexed()) {
* index = ga.getNormalIndex(vertexIndex);
* } else {
* index = vertexIndex;
* }
* ga.getNormal(index, normal);
* return normal;
* }
*
*
* The color, normal
* and texture coordinate information for the intersected primitive and the
* intersection point
* can be inquired
* the geometry includes them and the corresponding READ capibility bits are
* set.
*/
public class PickIntersection {
/* The intersection point */
// Point3d getIntersectionPoint()
/* Distance between start point of pickShape and intersection point */
// double getDistance()
/* The vertex indices of the intersected primitive in the geometry */
// int[] getVertexIndices()
/*************************/
/** Weight factors for interpolation, values correspond to vertex indices,
* sum == 1
*/
private double[] interpWeights;
private static final boolean debug = false;
// Axis constants
private static final int X_AXIS = 1;
private static final int Y_AXIS = 2;
private static final int Z_AXIS = 3;
// Tolerance for numerical stability
static final double TOL = 1.0e-5;
/* The references to the intersectionInfo object */
private PickInfo.IntersectionInfo iInfo = null;
private Transform3D l2vw = null;
private Geometry geometry = null;
private boolean geometryIsIndexed = false;
private double distance;
private boolean hasColors;
private boolean hasNormals;
private boolean hasTexCoords;
// Primitive
/* indices for the different data types */
private int[] primitiveCoordinateIndices;
private int[] primitiveNormalIndices;
private int[] primitiveColorIndices;
private int[] primitiveTexCoordIndices;
/** Indices of the intersected primitive */
private int[] primitiveVertexIndices = null;
/* Local coordinates of the intersected primitive */
private Point3d[] primitiveCoordinates = null;
/** VWorld coordinates of intersected primitive */
private Point3d[] primitiveCoordinatesVW = null;
/* Normals of the intersected primitive */
private Vector3f[] primitiveNormals = null;
/* Colors of the intersected primitive */
private Color4f[] primitiveColors = null;
/* TextureCoordinates of the intersected primitive */
private TexCoord3f[] primitiveTexCoords = null;
// Intersection point
/** VWorld Coordinates of the intersection point */
private Point3d pointCoordinatesVW = null;
/** Local Coordinates of the intersection point */
private Point3d pointCoordinates = null;
/** Normal at the intersection point */
private Vector3f pointNormal = null;
/** Color at the intersection point */
private Color4f pointColor = null;
/** TexCoord at the intersection point */
private TexCoord3f pointTexCoord = null;
// Closest Vertex
/** Index of the closest vertex */
private int closestVertexIndex = -1;
/** Coordinates of the closest vertex */
private Point3d closestVertexCoordinates = null;
/** Coordinates of the closest vertex (World coordinates) */
private Point3d closestVertexCoordinatesVW = null;
/* =================== METHODS ======================= */
/**
* Constructor
* @param intersectionInfo The IntersectionInfo this intersection is part of.
*/
public PickIntersection (Transform3D localToVWorld,
PickInfo.IntersectionInfo intersectionInfo) {
// Should check and throw NPE if the following is null.
// localToVWorld can't be null.
l2vw = localToVWorld;
// intersectionInfo can't be null.
iInfo = intersectionInfo;
// geometry can't be null.
geometry = iInfo.getGeometry();
pointCoordinates = iInfo.getIntersectionPoint();
distance = iInfo.getDistance();
primitiveVertexIndices = iInfo.getVertexIndices();
if (geometry instanceof GeometryArray) {
int vertexFormat = ((GeometryArray)geometry).getVertexFormat();
hasColors = (0 != (vertexFormat &
(GeometryArray.COLOR_3 | GeometryArray.COLOR_4)));
hasNormals = (0 != (vertexFormat & GeometryArray.NORMALS));
hasTexCoords = (0 != (vertexFormat &
(GeometryArray.TEXTURE_COORDINATE_2 |
GeometryArray.TEXTURE_COORDINATE_3)));
if (geometry instanceof IndexedGeometryArray) {
geometryIsIndexed = true;
}
}
}
/**
* Returns true if the geometry is indexed
*
*/
public boolean geometryIsIndexed() {
return geometryIsIndexed;
}
/**
* Get coordinates of closest vertex (local)
* @return the coordinates of the vertex closest to the intersection point
*
*/
public Point3d getClosestVertexCoordinates() {
// System.out.println("PI.closestVertexCoordinates " + closestVertexCoordinates);
GeometryArray geom = (GeometryArray) geometry;
if (closestVertexCoordinates == null) {
int vertexIndex = getClosestVertexIndex();
int vformat = geom.getVertexFormat();
int val;
int[] indices = getPrimitiveCoordinateIndices();
if ((vformat & GeometryArray.BY_REFERENCE) == 0) {
closestVertexCoordinates = new Point3d();
geom.getCoordinate(indices[vertexIndex], closestVertexCoordinates);
// System.out.println("PI.closestVertexCoordinates " +
// closestVertexCoordinates + " vertexIndex " +
// vertexIndex);
}
else {
if ((vformat & GeometryArray.INTERLEAVED) == 0) {
double[] doubleData = geom.getCoordRefDouble();
// If data was set as float then ..
if (doubleData == null) {
float[] floatData = geom.getCoordRefFloat();
if (floatData == null) {
throw new UnsupportedOperationException("Deprecated : BY_REF - p3f and p3d");
}
else {
val = indices[vertexIndex] * 3; // for x,y,z
closestVertexCoordinates = new Point3d(floatData[val],
floatData[val+1],
floatData[val+2]);
}
}
else {
val = indices[vertexIndex] * 3; // for x,y,z
closestVertexCoordinates = new Point3d(doubleData[val],
doubleData[val+1],
doubleData[val+2]);
}
}
else {
float[] floatData = geom.getInterleavedVertices();
int offset = getInterleavedVertexOffset(geom);
int stride = offset + 3; // for the vertices .
val = stride * indices[vertexIndex]+offset;
closestVertexCoordinates = new Point3d(floatData[val],
floatData[val+1],
floatData[val+2]);
}
}
}
return closestVertexCoordinates;
}
/**
* Get coordinates of closest vertex (world)
* @return the coordinates of the vertex closest to the intersection point
*
*/
public Point3d getClosestVertexCoordinatesVW() {
if (closestVertexCoordinatesVW == null) {
int vertexIndex = getClosestVertexIndex();
Point3d[] coordinatesVW = getPrimitiveCoordinatesVW();
closestVertexCoordinatesVW = coordinatesVW[vertexIndex];
}
return closestVertexCoordinatesVW;
}
/**
* Get index of closest vertex
* @return the index of the closest vertex
*/
public int getClosestVertexIndex() {
if (closestVertexIndex == -1) {
double maxDist = Double.MAX_VALUE;
double curDist = Double.MAX_VALUE;
int closestIndex = -1;
primitiveCoordinates = getPrimitiveCoordinates();
assert(primitiveCoordinates != null);
// System.out.println("PI.getClosestVertexIndex : primitiveCoordinates.length " +
// primitiveCoordinates.length);
for (int i=0;i max) {
axis = Y_AXIS;
max = abs(delta.y);
}
if (abs(delta.z) > max) {
axis = Z_AXIS;
}
return axis;
}
/* Triangle interpolation. Basic idea:
* Map the verticies of the triangle to the form:
*
* L--------R
* \ /
* IL+--P-+IR
* \ /
* Base
*
where P is the intersection point Base, L and R and the triangle
points. IL and IR are the projections if P along the Base-L and Base-R
edges using an axis:
IL = leftFactor * L + (1- leftFactor) * Base
IR = rightFactor * R + (1-rightFactor) * Base
then find the interp factor, midFactor, for P between IL and IR. If
this is outside the range 0->1 then we have the wrong triangle of a
quad and we return false.
Else, the weighting is:
IP = midFactor * IL + (1 - midFactor) * IR;
Solving for weights for the formula:
IP = BaseWeight * Base + LeftWeight * L + RightWeight * R;
We get:
BaseWeight = 1 - midFactor * leftFactor
- rightFactor + midFactor * rightFactor;
LeftWeight = midFactor * leftFactor;
RightWeight = righFactor - midFactor * rightFactor;
As a check, note that the sum of the weights is 1.0.
*/
boolean
interpTriangle(int index0, int index1, int index2, Point3d[] coords,
Point3d intPt) {
// find the longest edge, we'll use that to pick the axis */
Vector3d delta0 = new Vector3d();
Vector3d delta1 = new Vector3d();
Vector3d delta2 = new Vector3d();
delta0.sub(coords[index1], coords[index0]);
delta1.sub(coords[index2], coords[index0]);
delta2.sub(coords[index2], coords[index1]);
double len0 = delta0.lengthSquared();
double len1 = delta1.lengthSquared();
double len2 = delta2.lengthSquared();
Vector3d longest = delta0;
double maxLen = len0;
if (len1 > maxLen) {
longest = delta1;
maxLen = len1;
}
if (len2 > maxLen) {
longest = delta2;
}
int mainAxis = maxAxis(longest);
/*
System.out.println("index0 = " + index0 + " index1 = " + index1 +
" index2 = " + index2);
System.out.println("coords[index0] = " + coords[index0]);
System.out.println("coords[index1] = " + coords[index1]);
System.out.println("coords[index2] = " + coords[index2]);
System.out.println("intPt = " + intPt);
System.out.println("delta0 = " + delta0 + " len0 " + len0);
System.out.println("delta1 = " + delta1 + " len1 " + len1);
System.out.println("delta2 = " + delta2 + " len2 " + len2);
*/
/* now project the intersection point along the axis onto the edges */
double[] factor = new double[3];
/* the factor is for the projection opposide the vertex 0 = 1->2, etc*/
factor[0] =
getInterpFactorForBase(intPt, coords[index1], coords[index2], mainAxis);
factor[1] =
getInterpFactorForBase(intPt, coords[index2], coords[index0], mainAxis);
factor[2] =
getInterpFactorForBase(intPt, coords[index0], coords[index1], mainAxis);
if (debug) {
System.out.println("intPt = " + intPt);
switch(mainAxis) {
case X_AXIS:
System.out.println("mainAxis = X_AXIS");
break;
case Y_AXIS:
System.out.println("mainAxis = Y_AXIS");
break;
case Z_AXIS:
System.out.println("mainAxis = Z_AXIS");
break;
}
System.out.println("factor[0] = " + factor[0]);
System.out.println("factor[1] = " + factor[1]);
System.out.println("factor[2] = " + factor[2]);
}
/* Find the factor that is out of range, it will tell us which
* vertex to use for base
*/
int base, left, right;
double leftFactor, rightFactor;
if ((factor[0] < 0.0) || (factor[0] > 1.0)) {
base = index0;
right = index1;
left = index2;
rightFactor = factor[2];
leftFactor = 1.0 - factor[1];
if (debug) {
System.out.println("base 0, rightFactor = " + rightFactor +
" leftFactor = " + leftFactor);
}
} else if ((factor[1] < 0.0) || (factor[1] > 1.0)) {
base = index1;
right = index2;
left = index0;
rightFactor = factor[0];
leftFactor = 1.0 - factor[2];
if (debug) {
System.out.println("base 1, rightFactor = " + rightFactor +
" leftFactor = " + leftFactor);
}
} else {
base = index2;
right = index0;
left = index1;
rightFactor = factor[1];
leftFactor = 1.0 - factor[0];
if (debug) {
System.out.println("base 2, rightFactor = " + rightFactor +
" leftFactor = " + leftFactor);
}
}
if (debug) {
System.out.println("base = " + coords[base]);
System.out.println("left = " + coords[left]);
System.out.println("right = " + coords[right]);
}
/* find iLeft and iRight */
Point3d iLeft = new Point3d(leftFactor * coords[left].x +
(1.0-leftFactor)*coords[base].x,
leftFactor * coords[left].y +
(1.0-leftFactor)*coords[base].y,
leftFactor * coords[left].z +
(1.0-leftFactor)*coords[base].z);
Point3d iRight = new Point3d(rightFactor * coords[right].x +
(1.0-rightFactor)*coords[base].x,
rightFactor * coords[right].y +
(1.0-rightFactor)*coords[base].y,
rightFactor * coords[right].z +
(1.0-rightFactor)*coords[base].z);
if (debug) {
System.out.println("iLeft = " + iLeft);
System.out.println("iRight = " + iRight);
}
/* now find an axis and solve for midFactor */
delta0.sub(iLeft, iRight);
int midAxis = maxAxis(delta0);
double midFactor = getInterpFactor(intPt, iRight, iLeft, midAxis);
if (debug) {
switch(midAxis) {
case X_AXIS:
System.out.println("midAxis = X_AXIS");
break;
case Y_AXIS:
System.out.println("midAxis = Y_AXIS");
break;
case Z_AXIS:
System.out.println("midAxis = Z_AXIS");
break;
}
System.out.println("midFactor = " + midFactor);
}
if (midFactor < 0.0) {
// System.out.println("midFactor = " + midFactor);
if ((midFactor + TOL) >= 0.0) {
// System.out.println("In Tol case : midFactor = " + midFactor);
midFactor = 0.0;
}
else {
/* int point is outside triangle */
return false;
}
}
else if (midFactor > 1.0) {
// System.out.println("midFactor = " + midFactor);
if ((midFactor-TOL) <= 1.0) {
// System.out.println("In Tol case : midFactor = " + midFactor);
midFactor = 1.0;
}
else {
/* int point is outside triangle */
return false;
}
}
// Assign the weights
interpWeights[base] = 1.0 - midFactor * leftFactor -
rightFactor + midFactor * rightFactor;
interpWeights[left] = midFactor * leftFactor;
interpWeights[right] = rightFactor - midFactor * rightFactor;
return true;
}
/* Get the interpolation weights for each of the verticies of the
* primitive.
*/
double[] getInterpWeights() {
Point3d pt = getPointCoordinates();
Point3d[] coordinates = getPrimitiveCoordinates();
double factor;
int axis;
if (interpWeights != null) {
return interpWeights;
}
interpWeights = new double[coordinates.length];
// Interpolate
switch (coordinates.length) {
case 1:
// Nothing to interpolate
interpWeights[0] = 1.0;
break;
case 2: // edge
Vector3d delta = new Vector3d();
delta.sub (coordinates[1], coordinates[0]);
axis = maxAxis(delta);
factor = getInterpFactor (pt, coordinates[1], coordinates[0], axis);
interpWeights[0] = factor;
interpWeights[1] = 1.0 - factor;
break;
case 3: // triangle
if (!interpTriangle(0, 1, 2, coordinates, pt)) {
throw new RuntimeException ("Interp point outside triangle");
}
break;
case 4: // quad
if (!interpTriangle(0, 1, 2, coordinates, pt)) {
if (!interpTriangle(0, 2, 3, coordinates, pt)) {
throw new RuntimeException ("Interp point outside quad");
}
}
break;
default:
throw new RuntimeException ("Unexpected number of points.");
}
return interpWeights;
}
/**
Calculate the interpolation factor for point p by projecting it along
an axis (x,y,z) onto the edge between p1 and p2. If the result is
in the 0->1 range, point is between p1 and p2 (0 = point is at p1,
1 => point is at p2).
*/
private static float getInterpFactor (Point3d p, Point3d p1, Point3d p2,
int axis) {
float t;
switch (axis) {
case X_AXIS:
if (p1.x == p2.x)
//t = Float.MAX_VALUE; // TODO: should be 0?
t = 0.0f;
else
t = (float) ((p1.x - p.x) / (p1.x - p2.x));
break;
case Y_AXIS:
if (p1.y == p2.y)
// t = Float.MAX_VALUE;
t = 0.0f;
else
t = (float) ((p1.y - p.y) / (p1.y - p2.y));
break;
case Z_AXIS:
if (p1.z == p2.z)
// t = Float.MAX_VALUE;
t = 0.0f;
else
t = (float)((p1.z - p.z) / (p1.z - p2.z));
break;
default:
throw new RuntimeException ("invalid axis parameter "+axis+" (must be 0-2)");
}
return t;
}
/**
Calculate the interpolation factor for point p by projecting it along
an axis (x,y,z) onto the edge between p1 and p2. If the result is
in the 0->1 range, point is between p1 and p2 (0 = point is at p1,
1 => point is at p2).
return MAX_VALUE if component of vertices are the same.
*/
private static float getInterpFactorForBase (Point3d p, Point3d p1, Point3d p2,
int axis) {
float t;
switch (axis) {
case X_AXIS:
if (p1.x == p2.x)
t = Float.MAX_VALUE;
else
t = (float) ((p1.x - p.x) / (p1.x - p2.x));
break;
case Y_AXIS:
if (p1.y == p2.y)
t = Float.MAX_VALUE;
else
t = (float) ((p1.y - p.y) / (p1.y - p2.y));
break;
case Z_AXIS:
if (p1.z == p2.z)
t = Float.MAX_VALUE;
else
t = (float)((p1.z - p.z) / (p1.z - p2.z));
break;
default:
throw new RuntimeException ("invalid axis parameter "+axis+" (must be 0-2)");
}
return t;
}
} // PickIntersection