org.scijava.java3d.utils.picking.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.picking;
import org.scijava.java3d.GeometryArray;
import org.scijava.java3d.IndexedGeometryArray;
import org.scijava.vecmath.Color3b;
import org.scijava.vecmath.Color3f;
import org.scijava.vecmath.Color4b;
import org.scijava.vecmath.Color4f;
import org.scijava.vecmath.Point3d;
import org.scijava.vecmath.Point3f;
import org.scijava.vecmath.TexCoord2f;
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 PickResult. Information about
* the intersected geometry, intersected primitive, intersection point, and
* closest vertex can be inquired.
*
* The intersected geometry is indicated by an index into the list of
* geometry arrays on the PickResult. It can also be inquired from this
* object.
*
* 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.
*
* PickTool.setCapabilties(Node, int)
* can be used to set the capability bits
* to allow this data to be inquired.
*/
public class PickIntersection {
/* OPEN ISSUES:
-- Tex coordinates always use texCoordSet == 0.
*/
/* =================== ATTRIBUTES ======================= */
// init by constructor:
/** PickResult for intersection is part of */
PickResult pickResult = null;
// init by intersection:
/** Distance between start point and intersection point (see comment above)*/
double distance = -1;
/** index of GeometryArray in PickResult */
int geomIndex = 0;
/** Indices of the intersected primitive */
int[] primitiveVertexIndices = null;
/** VWorld coordinates of intersected primitive */
Point3d[] primitiveCoordinatesVW = null;
/** VWorld Coordinates of the intersection point */
Point3d pointCoordinatesVW = null;
// Derived data
// Geometry
GeometryArray geom = null;
IndexedGeometryArray iGeom = null;
boolean hasNormals = false;
boolean hasColors = false;
boolean hasTexCoords = false;
// Primitive
/* indices for the different data types */
int[] primitiveCoordinateIndices;
int[] primitiveNormalIndices;
int[] primitiveColorIndices;
int[] primitiveTexCoordIndices;
/* Local coordinates of the intersected primitive */
Point3d[] primitiveCoordinates = null;
/* Normals of the intersected primitive */
Vector3f[] primitiveNormals = null;
/* Colors of the intersected primitive */
Color4f[] primitiveColors = null;
/* TextureCoordinates of the intersected primitive */
TexCoord3f[] primitiveTexCoords = null;
// Intersection point
/** Local Coordinates of the intersection point */
Point3d pointCoordinates = null;
/** Normal at the intersection point */
Vector3f pointNormal = null;
/** Color at the intersection point */
Color4f pointColor = null;
/** TexCoord at the intersection point */
TexCoord3f pointTexCoord = null;
// Closest Vertex
/** Index of the closest vertex */
int closestVertexIndex = -1;
/** Coordinates of the closest vertex */
Point3d closestVertexCoordinates = null;
/** Coordinates of the closest vertex (World coordinates) */
Point3d closestVertexCoordinatesVW = null;
/** Weight factors for interpolation, values correspond to vertex indices,
* sum == 1
*/
double[] interpWeights;
static final boolean debug = false;
// Axis constants
static final int X_AXIS = 1;
static final int Y_AXIS = 2;
static final int Z_AXIS = 3;
// Tolerance for numerical stability
static final double TOL = 1.0e-5;
/* =================== METHODS ======================= */
/** Constructor
@param pickResult The pickResult this intersection is part of.
*/
PickIntersection (PickResult pr, GeometryArray geomArr) {
// pr can't be null.
pickResult = pr;
geom = geomArr;
if (geom == null) {
GeometryArray[] ga = pickResult.getGeometryArrays();
geom = ga[geomIndex];
}
if (geom instanceof IndexedGeometryArray) {
iGeom = (IndexedGeometryArray)geom;
}
int vertexFormat = geom.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)));
}
/**
String representation of this object
*/
@Override
public String toString () {
String rt = new String ("PickIntersection: ");
rt += " pickResult = "+pickResult + "\n";
rt += " geomIndex = "+geomIndex + "\n";
if (distance != -1) rt += " dist:"+distance + "\n";
if (pointCoordinates != null) rt += " pt:" + pointCoordinates + "\n";
if (pointCoordinatesVW != null) rt += " ptVW:" + pointCoordinatesVW + "\n";
if (primitiveCoordinateIndices != null) {
rt += " prim coordinate ind:" + "\n";
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 = getPointCoordinatesVW();
Point3d[] coordinates = getPrimitiveCoordinatesVW();
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