/** * 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.types.twodee.Rect2i; import org.openmali.vecmath2.Matrix4f; import org.openmali.vecmath2.Ray3f; import org.openmali.vecmath2.Tuple2f; import org.xith3d.render.Canvas3D; import org.xith3d.render.RenderPassConfig; import org.xith3d.scenegraph.View; import org.xith3d.scenegraph._SG_PrivilegedAccess; import org.xith3d.scenegraph.View.CameraMode; import org.xith3d.scenegraph.View.ProjectionPolicy; /** * Implements a pick-ray. * * @author Marvin Froehlich (aka Qudus) */ public class PickRay extends Ray3f { private static final long serialVersionUID = 6030131242963942660L; // store these values for later use private ProjectionPolicy projectionPolicy; private float fieldOfView, screenScale, centerViewX, centerViewY; private Matrix4f viewMatrix; private float canvasWidth, canvasHeight, canvasAspect; private float canvasX, canvasY; private View lastView = null; private RenderPassConfig lastRPC = null; /** * Recalculates the PickRay. * * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public void recalculate( float canvasX, float canvasY ) { this.canvasX = canvasX; this.canvasY = canvasY; float x = this.canvasX; float y = this.canvasY; if ( ( lastRPC != null ) && ( lastRPC.getViewport() != null ) ) { Rect2i viewport = lastRPC.getViewport(); x -= viewport.getLeft(); y -= viewport.getTop(); this.canvasWidth = viewport.getWidth(); this.canvasHeight = viewport.getHeight(); this.canvasAspect = this.canvasWidth / this.canvasHeight; } if ( projectionPolicy == ProjectionPolicy.PERSPECTIVE_PROJECTION ) { /* * Normalize the pixel location to the range [-1.0, 1.0] * and modify the x coordinate to take aspect ratio into account. */ float rx = ( 2.0f * x / canvasWidth - 1.0f ) * canvasAspect; float ry = 2.0f - 2.0f * y / canvasHeight - 1.0f; // Calculate the distance between viewer and view plane. float vpd = 1.0f / FastMath.tan( fieldOfView ); /* * Originate the ray at the local origin of the viewer and direct * it toward the local position of the click in the view plane. */ setOrigin( 0.0f, 0.0f, 0.0f ); setDirection( rx, ry, -vpd ); getDirection().normalize(); } else { /* * Normalize the pixel location to the range [-1.0, 1.0] * and modify the y coordinate to take aspect ratio into account. */ float rx = 2.0f * x / canvasWidth - 1.0f; float ry = ( 2.0f - 2.0f * y / canvasHeight - 1.0f ) / canvasAspect; /* * Originate the ray at local position of the click * in the view plane and direct it along the -Z axis. */ final float s = screenScale; final float cx = centerViewX; final float cy = centerViewY; setOrigin( -cx + s * rx, -cy + s * ry, 9999f ); setDirection( 0f, 0f, -1f ); } // Transform the ray into world space. viewMatrix.transform( getOrigin() ); viewMatrix.transform( getDirection() ); } /** * Recalculates the PickRay. * * @param viewMatrix teh model-view-matrix */ public void recalculate( Matrix4f viewMatrix ) { this.viewMatrix = viewMatrix; recalculate( canvasX, canvasY ); } /** * Creates a pick-ray * * @param projectionPolicy the View's projection policy * @param fieldOfView the View's field of view * @param screenScale the View's screen scale * @param centerViewX the x-coordinate of center-of-view * @param centerViewY the x-coordinate of center-of-view * @param viewMatrix the View's transform matrix * @param canvasWidth the Canvas3D's width * @param canvasHeight the Canvas3D's height * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public void recalculate( ProjectionPolicy projectionPolicy, float fieldOfView, float screenScale, float centerViewX, float centerViewY, Matrix4f viewMatrix, float canvasWidth, float canvasHeight, float canvasX, float canvasY ) { this.projectionPolicy = projectionPolicy; this.fieldOfView = fieldOfView; this.screenScale = screenScale; this.centerViewX = centerViewX; this.centerViewY = centerViewY; this.viewMatrix = viewMatrix; this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; this.canvasAspect = canvasWidth / canvasHeight; recalculate( canvasX, canvasY ); } public void recalculate( PickRay template, CameraMode cameraMode ) { this.lastView = template.lastView; this.lastRPC = template.lastRPC; this.projectionPolicy = template.projectionPolicy; this.fieldOfView = template.fieldOfView; this.screenScale = template.screenScale; this.centerViewX = template.centerViewX; this.centerViewY = template.centerViewY; this.canvasWidth = template.canvasWidth; this.canvasHeight = template.canvasHeight; this.canvasAspect = template.canvasAspect; if ( lastRPC != null ) _SG_PrivilegedAccess.set( lastView, true, lastRPC ); if ( cameraMode == null ) this.viewMatrix = lastView.getTransform().getMatrix4f(); else this.viewMatrix = lastView.getModelViewTransform( cameraMode, true ).getMatrix4f(); if ( lastRPC != null ) _SG_PrivilegedAccess.set( lastView, false, null ); recalculate( template.canvasX, template.canvasY ); } private static final float getCenterViewX( Tuple2f centerOfView ) { return ( ( centerOfView != null ) ? centerOfView.getX() : 0f ); } private static final float getCenterViewY( Tuple2f centerOfView ) { return ( ( centerOfView != null ) ? centerOfView.getY() : 0f ); } /** * Creates a pick-ray * * @param canvas the Canvas3D to take for calculation * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public void recalculate( Canvas3D canvas, int canvasX, int canvasY ) { final View view = canvas.getView(); this.lastView = view; final CameraMode cameraMode = ( lastRPC == null ) ? null : lastRPC.getCameraMode(); _SG_PrivilegedAccess.set( view, true, lastRPC ); this.projectionPolicy = view.getProjectionPolicy(); this.fieldOfView = view.getFieldOfView(); this.screenScale = view.getScreenScale(); this.centerViewX = getCenterViewX( view.getCenterOfView() ); this.centerViewY = getCenterViewY( view.getCenterOfView() ); /* * TODO: WORKAROUND: this enables picking to work with multiple viewports, * but we should check why the inverse matrix (returned by getModelViewTransform(...)) does not work here */ //if ( cameraMode == null ) // <- original code if ( ( cameraMode == null ) || ( cameraMode == View.CameraMode.VIEW_NORMAL ) ) this.viewMatrix = view.getTransform().getMatrix4f(); else this.viewMatrix = view.getModelViewTransform( cameraMode, true ).getMatrix4f(); this.canvasWidth = canvas.getWidth(); this.canvasHeight = canvas.getHeight(); this.canvasAspect = canvasWidth / canvasHeight; _SG_PrivilegedAccess.set( view, false, null ); recalculate( (float)canvasX, (float)canvasY ); } /** * Creates a pick-ray * * @param canvas the Canvas3D to take for calculation * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public void recalculate( int canvasX, int canvasY ) { Canvas3D canvas = lastView.getCanvas3D( 0 ); recalculate( canvas, canvasX, canvasY ); } /** * Creates a pick-ray * * @param rpc the RenderPassConfig to take calculation values from * @param canvas the Canvas3D to take for calculation * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public void recalculate( RenderPassConfig rpc, Canvas3D canvas, int canvasX, int canvasY ) { this.lastRPC = rpc; recalculate( canvas, canvasX, canvasY ); } /** * Creates a pick-ray * * @param canvas the Canvas3D to take for calculation (and the Canvas3D's * View) * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public void recalculate( CameraMode cameraMode, Canvas3D canvas, int canvasX, int canvasY ) { final float centerViewX = getCenterViewX( canvas.getView().getCenterOfView() ); final float centerViewY = getCenterViewY( canvas.getView().getCenterOfView() ); recalculate( canvas.getView().getProjectionPolicy(), canvas.getView().getFieldOfView(), centerViewX, centerViewY, canvas.getView().getScreenScale(), ( cameraMode == null ) ? canvas.getView().getTransform().getMatrix4f() : canvas.getView().getModelViewTransform( cameraMode, true ).getMatrix4f(), (float)canvas.getWidth(), (float)canvas.getHeight(), (float)canvasX, (float)canvasY ); this.lastView = canvas.getView(); this.lastRPC = null; } /** * Creates a pick-ray */ public PickRay() { super(); } /** * Creates a pick-ray * * @param projectionPolicy the View's projection policy * @param fieldOfView the View's field of view * @param screenScale the View's screen scale * @param centerViewX the x-coordinate of center-of-view * @param centerViewY the x-coordinate of center-of-view * @param viewMatrix the View's transform matrix * @param canvasWidth the Canvas3D's width * @param canvasHeight the Canvas3D's height * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public PickRay( ProjectionPolicy projectionPolicy, float fieldOfView, float screenScale, float centerViewX, float centerViewY, Matrix4f viewMatrix, float canvasWidth, float canvasHeight, float canvasX, float canvasY ) { super(); this.projectionPolicy = projectionPolicy; this.fieldOfView = fieldOfView; this.screenScale = screenScale; this.centerViewX = centerViewX; this.centerViewY = centerViewY; this.viewMatrix = viewMatrix; this.canvasWidth = canvasWidth; this.canvasHeight = canvasHeight; this.canvasAspect = canvasWidth / canvasHeight; recalculate( canvasX, canvasY ); } /** * Creates a pick-ray * * @param view the View to take for calculation * @param cameraMode * @param canvas the Canvas3D to take for calculation * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public PickRay( View view, CameraMode cameraMode, Canvas3D canvas, int canvasX, int canvasY ) { this( view.getProjectionPolicy(), view.getFieldOfView(), view.getScreenScale(), getCenterViewX( view.getCenterOfView() ), getCenterViewY( view.getCenterOfView() ), view.getModelViewTransform( cameraMode, true ).getMatrix4f(), (float)canvas.getWidth(), (float)canvas.getHeight(), (float)canvasX, (float)canvasY ); this.lastView = view; } /** * Creates a pick-ray * * @param cameraMode * @param canvas the Canvas3D to take for calculation (and the Canvas3D's View) * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public PickRay( CameraMode cameraMode, Canvas3D canvas, int canvasX, int canvasY ) { this( canvas.getView().getProjectionPolicy(), canvas.getView().getFieldOfView(), canvas.getView().getScreenScale(), getCenterViewX( canvas.getView().getCenterOfView() ), getCenterViewY( canvas.getView().getCenterOfView() ), canvas.getView().getModelViewTransform( cameraMode, true ).getMatrix4f(), (float)canvas.getWidth(), (float)canvas.getHeight(), (float)canvasX, (float)canvasY ); this.lastView = canvas.getView(); } /** * Creates a pick-ray * * @param rpc the RenderPassConfig to take calculation values from * @param canvas the Canvas3D to take for calculation (and the Canvas3D's View) * @param canvasX the x position on the Canvas3D * @param canvasY the y position on the Canvas3D */ public PickRay( RenderPassConfig rpc, Canvas3D canvas, int canvasX, int canvasY ) { this(); recalculate( rpc, canvas, canvasX, canvasY ); } }