/**
* Copyright (c) 2003-2009, Xith3D Project Group all rights reserved.
*
* Portions based on the Java3D interface, Copyright by Sun Microsystems.
* Many thanks to the developers of Java3D and Sun Microsystems for their
* innovation and design.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions 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 the 'Xith3D Project Group' nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A
* RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE
*/
package org.xith3d.picking;
import org.openmali.FastMath;
import org.openmali.spatial.LineContainer;
import org.openmali.spatial.TriangleContainer;
import org.openmali.spatial.polygons.Triangle;
import org.openmali.vecmath2.Matrix3f;
import org.openmali.vecmath2.Matrix4f;
import org.openmali.vecmath2.Point3f;
import org.openmali.vecmath2.Ray3f;
import org.openmali.vecmath2.Vector3f;
import org.xith3d.render.Canvas3D;
import org.xith3d.scenegraph.BranchGroup;
import org.xith3d.scenegraph.Geometry;
import org.xith3d.scenegraph.IndexedTriangleStripArray;
import org.xith3d.scenegraph.Leaf;
import org.xith3d.scenegraph.LineAttributes;
import org.xith3d.scenegraph.PointArray;
import org.xith3d.scenegraph.PointAttributes;
import org.xith3d.scenegraph.SceneGraph;
import org.xith3d.scenegraph.Shape3D;
import org.xith3d.scenegraph.TriangleStripArray;
import org.xith3d.scenegraph.View;
import org.xith3d.scenegraph.View.ProjectionPolicy;
/**
* Geometry-ray intersection test is accessed by PickingLibrary trough an
* interface instead of an internal method to make it replaceable.
* This is the default implementation.
*
* @author Arne Mueller
* @author Amos Wenger (aka BlueSky)
* @author Marvin Froehlich (aka Qudus)
* @author Mathias Henze (aka cylab)
*/
public class DefaultGeometryPickTester implements GeometryPickTester
{
private static final int[] TMP_ARRAY = new int[ 1 ];
private static final float testTriangle( PickResult pr, int faceIndex, Triangle triang, Ray3f pickRay, float closestIntersection )
{
float f = triang.intersects( pickRay, closestIntersection );
if ( f >= 0.0f )
{ // there is an intersection!
f = f / pickRay.length();
if ( f < closestIntersection )
{
pr.setMinimumDistance( f );
pr.setFaceIndex( faceIndex );
return ( f );
}
return ( -1.0f );
}
return ( -1.0f );
}
/**
* {@inheritDoc}
*/
public float testGeometryIntersection( PickResult pickCandidate, Ray3f pickRay, float closestIntersection, Triangle bufferTriangle )
{
float result = -1.0f;
Geometry geom = pickCandidate.getGeometry();
// now find intersection points with the geometry
if ( geom instanceof TriangleContainer )
{
TriangleContainer triangCnt = (TriangleContainer)geom;
/*
* If the GeometryArray is a strip, we will do the
* triangle-loop on our own.
* Simply using the getTriangle( int, Triangle ) would also work,
* but is slower, since the vertex indices must be calculated
* by looping over the strips, which is a little more expensive
* (but not extremely).
*/
if ( geom instanceof TriangleStripArray )
{
TriangleStripArray tsa = (TriangleStripArray)geom;
final int[] stripLengths;
if ( tsa.getStripVertexCounts() == null )
{
stripLengths = TMP_ARRAY;
stripLengths[ 0 ] = tsa.getVertexCount();
}
else
{
stripLengths = tsa.getStripVertexCounts();
}
int offset = 0;
for ( int i = 0; i < stripLengths.length; i++ )
{
for ( int j = 2; j < stripLengths[ i ]; j++ )
{
final int idx0 = offset + j - 2;
final int idx1 = offset + j - 1;
final int idx2 = offset + j - 0;
tsa.getTriangle( idx0, idx1, idx2, bufferTriangle );
final float f = testTriangle( pickCandidate, idx0, bufferTriangle, pickRay, closestIntersection );
if ( f >= 0.0f )
{
closestIntersection = f;
result = f;
}
}
offset += stripLengths[ i ] - 2;
}
}
else if ( geom instanceof IndexedTriangleStripArray )
{
IndexedTriangleStripArray itsa = (IndexedTriangleStripArray)geom;
final int[] stripLengths;
if ( itsa.getStripVertexCounts() == null )
{
stripLengths = TMP_ARRAY;
stripLengths[ 0 ] = itsa.getVertexCount();
}
else
{
stripLengths = itsa.getStripVertexCounts();
}
final int[] index = itsa.getIndex();
int offset = 0;
for ( int i = 0; i < stripLengths.length; i++ )
{
for ( int j = 2; j < stripLengths[ i ]; j++ )
{
final int idx0 = index[ offset + j - 2 ];
final int idx1 = index[ offset + j - 1 ];
final int idx2 = index[ offset + j - 0 ];
itsa.getTriangle( idx0, idx1, idx2, bufferTriangle );
final float f = testTriangle( pickCandidate, idx0, bufferTriangle, pickRay, closestIntersection );
if ( f >= 0.0f )
{
closestIntersection = f;
result = f;
}
}
offset += stripLengths[ i ] - 2;
}
}
else
{
final int nTrian = triangCnt.getTriangleCount();
for ( int i = 0; i < nTrian; i++ )
{
triangCnt.getTriangle( i, bufferTriangle );
final float f = testTriangle( pickCandidate, i, bufferTriangle, pickRay, closestIntersection );
if ( f >= 0.0f )
{
closestIntersection = f;
result = f;
}
}
}
}
else if ( geom instanceof PointArray )
{
final PointArray points = (PointArray)geom;
if ( !( pickCandidate.getNode() instanceof Shape3D ) )
return ( result );
final Shape3D shape = (Shape3D)pickCandidate.getNode();
if ( shape.getAppearance() == null )
return ( result );
final PointAttributes pointAttribs = shape.getAppearance().getPointAttributes();
if ( pointAttribs == null )
return ( result );
/*
float size = pointAttribs.getPointSize();
Vector3f tmpVec = Vector3f.fromPool();
float s = 2.0f - 2.0f * ( 300 + size ) / 600 - 1.0f;
tmpVec.set( s, s, s );
viewMatrix.transform( tmpVec );
size = ( tmpVec.getX() + tmpVec.getY() + tmpVec.getZ() ) / 3f;
System.out.println( size );
Vector3f.toPool( tmpVec );
*/
float size = pointAttribs.getPointSize();
// get the view, canvas and projection policy
final Leaf node = pickCandidate.getNode();
final BranchGroup root = (node == null) ? null : node.getRoot();
final SceneGraph sceneGraph = (root == null) ? null : root.getSceneGraph();
final View view = (sceneGraph == null) ? null : sceneGraph.getView();
final Point3f viewPosition = (view == null) ? null : view.getPosition();
final Canvas3D canvas = (view == null) ? null : view.getCanvas3D(0);
final ProjectionPolicy projectionPolicy = (view == null) ? ProjectionPolicy.PERSPECTIVE_PROJECTION : view.getProjectionPolicy();
float fovH = size * 0.001167f; // this is estimated for a standard 1024*768 window
float h = fovH;
// if we can get everything we need, calculate the correction factor
if ( ( viewPosition != null ) && ( canvas != null ) )
{
switch ( projectionPolicy )
{
case PERSPECTIVE_PROJECTION:
fovH = ( size * FastMath.tan( view.getFieldOfView() ) / canvas.getHeight() );
break;
case PARALLEL_PROJECTION:
h = ( size / 2f ) * ( 2f / canvas.getWidth() );
break;
}
}
Point3f point = Point3f.fromPool();
Vector3f tmp = Vector3f.fromPool();
final int n = points.getVertexCount();
for ( int i = 0; i < n; i++ )
{
points.getVertex( i, point );
if ( projectionPolicy == ProjectionPolicy.PERSPECTIVE_PROJECTION )
{
tmp.sub( point, viewPosition );
h = fovH * tmp.length();
}
final float px = point.getX();
final float py = point.getY();
final float pz = point.getZ();
// three crossing quads, each aligned to an axis, with 2 triangles = 6 triangles to test
for ( int j = 0; j < 6; j++ )
{
float ax = 0f, ay = 0f, az = 0f, bx = 0f, by = 0f, bz = 0f, cx = 0f, cy = 0f, cz = 0f;
switch ( j )
{
// the intersection when looking along the z axis
case 0:
ax = -h; ay = -h; az = 0f;
bx = +h; by = +h; bz = 0f;
cx = -h; cy = +h; cz = 0f;
break;
case 1:
ax = -h; ay = -h; az = 0f;
bx = +h; by = -h; bz = 0f;
cx = +h; cy = +h; cz = 0f;
break;
// we also need to check the intersection when looking along the y axis
case 2:
ax = -h; ay = 0f; az = -h;
bx = +h; by = 0f; bz = +h;
cx = -h; cy = 0f; cz = +h;
break;
case 3:
ax = -h; ay = 0f; az = -h;
bx = +h; by = 0f; bz = -h;
cx = +h; cy = 0f; cz = +h;
break;
// we also need to check the intersection when looking along the x axis
case 4:
ax = 0f; ay = -h; az = -h;
bx = 0f; by = +h; bz = +h;
cx = 0f; cy = -h; cz = +h;
break;
case 5:
ax = 0f; ay = -h; az = -h;
bx = 0f; by = +h; bz = -h;
cx = 0f; cy = +h; cz = +h;
break;
}
bufferTriangle.setVertexCoordA( tmp.set( px + ax, py + ay, pz + az ) );
bufferTriangle.setVertexCoordB( tmp.set( px + bx, py + by, pz + bz ) );
bufferTriangle.setVertexCoordC( tmp.set( px + cx, py + cy, pz + cz ) );
float f = testTriangle( pickCandidate, i, bufferTriangle, pickRay, closestIntersection );
if ( f >= 0.0f )
{
if ( f < closestIntersection )
closestIntersection = f;
result = f;
}
}
}
Vector3f.toPool( tmp );
Point3f.toPool( point );
}
else if ( geom instanceof LineContainer )
{
final LineContainer lines = (LineContainer)geom;
if ( !( pickCandidate.getNode() instanceof Shape3D ) )
return ( result );
final Shape3D shape = (Shape3D)pickCandidate.getNode();
if ( shape.getAppearance() == null )
return ( result );
final LineAttributes lineAttribs = shape.getAppearance().getLineAttributes();
if ( lineAttribs == null )
return ( result );
final float lwidth = lineAttribs.getLineWidth();
// get the view, canvas and projection policy
final Leaf node = pickCandidate.getNode();
final BranchGroup root = (node == null) ? null : node.getRoot();
final SceneGraph sceneGraph = (root == null) ? null : root.getSceneGraph();
final View view = (sceneGraph == null) ? null : sceneGraph.getView();
final Point3f viewPosition = (view == null) ? null : view.getPosition();
final Canvas3D canvas = (view == null) ? null : view.getCanvas3D(0);
final ProjectionPolicy projectionPolicy = (view == null) ? ProjectionPolicy.PERSPECTIVE_PROJECTION : view.getProjectionPolicy();
float fovH = lwidth * 0.001167f; // this is estimated for a standard 1024*768 window
float hn = fovH;
float hn1 = fovH;
// if we can get everything we need, calculate the correction factor
if ( ( viewPosition != null ) && ( canvas != null ) )
{
switch ( projectionPolicy )
{
case PERSPECTIVE_PROJECTION:
fovH = ( lwidth * FastMath.tan( view.getFieldOfView() ) / canvas.getHeight() );
break;
case PARALLEL_PROJECTION:
hn = ( lwidth / 2f ) * ( 2f / canvas.getWidth() );
hn1 = hn;
break;
}
}
final int n = lines.getLinesCount();
Point3f pn = Point3f.fromPool();
Point3f pn1 = Point3f.fromPool();
Point3f pfll = Point3f.fromPool();
Point3f pful = Point3f.fromPool();
Point3f pflr = Point3f.fromPool();
Point3f pfur = Point3f.fromPool();
Point3f pbll = Point3f.fromPool();
Point3f pbul = Point3f.fromPool();
Point3f pblr = Point3f.fromPool();
Point3f pbur = Point3f.fromPool();
Vector3f lineVector = Vector3f.fromPool();
Vector3f right = Vector3f.fromPool();
Vector3f up = Vector3f.fromPool();
Vector3f tmp = Vector3f.fromPool();
Matrix4f transform = Matrix4f.fromPool();
Matrix3f rot = Matrix3f.fromPool();
// create a bounding box around the line segment and test intersection with the triangles forming the sides
for ( int i = 0; i < n; i++ )
{
lines.getLineCoordinates( i, pn, pn1 );
if ( projectionPolicy == ProjectionPolicy.PERSPECTIVE_PROJECTION )
{
tmp.sub( pn, viewPosition );
hn = fovH * tmp.length();
tmp.sub( pn1, viewPosition );
hn1 = fovH * tmp.length();
}
lineVector.set( pn1 ).sub( pn );
float l = lineVector.length();
lineVector.normalize();
// "front" face
pfll.set( -hn, 0f - hn, +hn );
pflr.set( +hn, 0f - hn, +hn );
pful.set( -hn1, l + hn1, +hn1 );
pfur.set( +hn1, l + hn1, +hn1 );
// "back" face
pbll.set( -hn, 0f - hn, -hn );
pblr.set( +hn, 0f - hn, -hn );
pbul.set( -hn1, l + hn1, -hn1 );
pbur.set( +hn1, l + hn1, -hn1 );
// create a new coordinate system aligned at the line direction
float dz = pn1.getZ() - pn.getZ();
tmp.set( ( (-0.0010f < dz) && (dz < 0.0010f) ) ? Vector3f.NEGATIVE_Z_AXIS : Vector3f.NEGATIVE_Y_AXIS );
right.cross( lineVector, tmp );
right.normalize();
up.cross( lineVector, right );
up.normalize();
// set the new system to the rotation matrix and shift the box to the line origin
rot.setColumn( 0, right );
rot.setColumn( 1, lineVector );
rot.setColumn( 2, up );
transform.setIdentity();
transform.setRotation( rot );
transform.setTranslation( pn );
transform.transform( pfll );
transform.transform( pful );
transform.transform( pflr );
transform.transform( pfur );
transform.transform( pbll );
transform.transform( pbul );
transform.transform( pblr );
transform.transform( pbur );
// two crossing rectangles and two quads for top and bottom = 8 triangles to test
for ( int j = 0; j < 8; j++ )
{
switch( j )
{
// crossing rectangle 1
case 0:
bufferTriangle.setVertexCoordA( pfll );
bufferTriangle.setVertexCoordB( pbur );
bufferTriangle.setVertexCoordC( pful );
break;
case 1:
bufferTriangle.setVertexCoordA( pfll );
bufferTriangle.setVertexCoordB( pbur );
bufferTriangle.setVertexCoordC( pblr );
break;
// crossing rectangle 2
case 2:
bufferTriangle.setVertexCoordA( pflr );
bufferTriangle.setVertexCoordB( pbul );
bufferTriangle.setVertexCoordC( pfur );
break;
case 3:
bufferTriangle.setVertexCoordA( pflr );
bufferTriangle.setVertexCoordB( pbul );
bufferTriangle.setVertexCoordC( pbll );
break;
// bottom quad
case 4:
bufferTriangle.setVertexCoordA( pbll );
bufferTriangle.setVertexCoordB( pflr );
bufferTriangle.setVertexCoordC( pfll );
break;
case 5:
bufferTriangle.setVertexCoordA( pbll );
bufferTriangle.setVertexCoordB( pflr );
bufferTriangle.setVertexCoordC( pblr );
break;
// top quad
case 6:
bufferTriangle.setVertexCoordA( pbul );
bufferTriangle.setVertexCoordB( pfur );
bufferTriangle.setVertexCoordC( pful );
break;
case 7:
bufferTriangle.setVertexCoordA( pbul );
bufferTriangle.setVertexCoordB( pfur );
bufferTriangle.setVertexCoordC( pbur );
break;
}
float f = testTriangle( pickCandidate, i, bufferTriangle, pickRay, closestIntersection );
if ( f >= 0.0f )
{
if ( f < closestIntersection )
{
closestIntersection = f;
}
result = f;
}
}
}
Point3f.toPool( pn );
Point3f.toPool( pn1 );
Point3f.toPool( pfll );
Point3f.toPool( pful );
Point3f.toPool( pflr );
Point3f.toPool( pfur );
Point3f.toPool( pbll );
Point3f.toPool( pbul );
Point3f.toPool( pblr );
Point3f.toPool( pbur );
Vector3f.toPool( lineVector );
Vector3f.toPool( tmp );
Vector3f.toPool( right );
Vector3f.toPool( up );
Matrix4f.toPool( transform );
Matrix3f.toPool( rot );
}
return ( result );
}
}