/* * Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.javafx.geom; import com.sun.javafx.geom.transform.Affine3D; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.geom.transform.NoninvertibleTransformException; /** * A ray used for picking. */ public class PickRay { private Vec3d origin = new Vec3d(); private Vec3d direction = new Vec3d(); private double nearClip = 0.0; private double farClip = Double.POSITIVE_INFINITY; // static final double EPS = 1.0e-13; static final double EPS = 1.0e-5f; public PickRay() { } public PickRay(Vec3d origin, Vec3d direction, double nearClip, double farClip) { set(origin, direction, nearClip, farClip); } public PickRay(double x, double y, double z, double nearClip, double farClip) { set(x, y, z, nearClip, farClip); } public static PickRay computePerspectivePickRay( double x, double y, boolean fixedEye, double viewWidth, double viewHeight, double fieldOfViewRadians, boolean verticalFieldOfView, Affine3D cameraTransform, double nearClip, double farClip, PickRay pickRay) { if (pickRay == null) { pickRay = new PickRay(); } Vec3d direction = pickRay.getDirectionNoClone(); double halfViewWidth = viewWidth / 2.0; double halfViewHeight = viewHeight / 2.0; double halfViewDim = verticalFieldOfView? halfViewHeight: halfViewWidth; // Distance to projection plane from eye double distanceZ = halfViewDim / Math.tan(fieldOfViewRadians / 2.0); direction.x = x - halfViewWidth; direction.y = y - halfViewHeight; direction.z = distanceZ; Vec3d eye = pickRay.getOriginNoClone(); if (fixedEye) { eye.set(0.0, 0.0, 0.0); } else { // set eye at center of viewport and move back so that projection plane // is at Z = 0 eye.set(halfViewWidth, halfViewHeight, -distanceZ); } pickRay.nearClip = nearClip * (direction.length() / (fixedEye ? distanceZ : 1.0)); pickRay.farClip = farClip * (direction.length() / (fixedEye ? distanceZ : 1.0)); pickRay.transform(cameraTransform); return pickRay; } public static PickRay computeParallelPickRay( double x, double y, double viewHeight, Affine3D cameraTransform, double nearClip, double farClip, PickRay pickRay) { if (pickRay == null) { pickRay = new PickRay(); } // This is the same math as in the perspective case, fixed // for the default 30 degrees vertical field of view. final double distanceZ = (viewHeight / 2.0) / Math.tan(Math.toRadians(15.0)); pickRay.set(x, y, distanceZ, nearClip * distanceZ, farClip * distanceZ); if (cameraTransform != null) { pickRay.transform(cameraTransform); } return pickRay; } public final void set(Vec3d origin, Vec3d direction, double nearClip, double farClip) { setOrigin(origin); setDirection(direction); this.nearClip = nearClip; this.farClip = farClip; } public final void set(double x, double y, double z, double nearClip, double farClip) { setOrigin(x, y, -z); setDirection(0, 0, z); this.nearClip = nearClip; this.farClip = farClip; } public void setPickRay(PickRay other) { setOrigin(other.origin); setDirection(other.direction); nearClip = other.nearClip; farClip = other.farClip; } public PickRay copy() { return new PickRay(origin, direction, nearClip, farClip); } /** * Sets the origin of the pick ray in world coordinates. * * @param origin the origin (in world coordinates). */ public void setOrigin(Vec3d origin) { this.origin.set(origin); } /** * Sets the origin of the pick ray in world coordinates. * * @param x the origin X coordinate * @param y the origin Y coordinate * @param z the origin Z coordinate */ public void setOrigin(double x, double y, double z) { this.origin.set(x, y, z); } public Vec3d getOrigin(Vec3d rv) { if (rv == null) { rv = new Vec3d(); } rv.set(origin); return rv; } public Vec3d getOriginNoClone() { return origin; } /** * Sets the direction vector of the pick ray. This vector need not * be normalized. * * @param direction the direction vector */ public void setDirection(Vec3d direction) { this.direction.set(direction); } /** * Sets the direction of the pick ray. The vector need not be normalized. * * @param x the direction X magnitude * @param y the direction Y magnitude * @param z the direction Z magnitude */ public void setDirection(double x, double y, double z) { this.direction.set(x, y, z); } public Vec3d getDirection(Vec3d rv) { if (rv == null) { rv = new Vec3d(); } rv.set(direction); return rv; } public Vec3d getDirectionNoClone() { return direction; } public double getNearClip() { return nearClip; } public double getFarClip() { return farClip; } public double distance(Vec3d iPnt) { double x = iPnt.x - origin.x; double y = iPnt.y - origin.y; double z = iPnt.z - origin.z; return Math.sqrt(x*x + y*y + z*z); } /** * Project the ray through the specified (inverted) transform and * onto the Z=0 plane of the resulting coordinate system. * If a perspective projection is being used then only a point * that projects forward from the eye to the plane will be returned, * otherwise a null will be returned to indicate that the projection * is behind the eye. * * @param inversetx the inverse of the model transform into which the * ray is to be projected * @param perspective true if the projection is happening in perspective * @param tmpvec a temporary {@code Vec3d} object for internal use * (may be null) * @param ret a {@code Point2D} object for storing the return value, * or null if a new object should be returned. * @return */ public Point2D projectToZeroPlane(BaseTransform inversetx, boolean perspective, Vec3d tmpvec, Point2D ret) { if (tmpvec == null) { tmpvec = new Vec3d(); } inversetx.transform(origin, tmpvec); double origX = tmpvec.x; double origY = tmpvec.y; double origZ = tmpvec.z; tmpvec.add(origin, direction); inversetx.transform(tmpvec, tmpvec); double dirX = tmpvec.x - origX; double dirY = tmpvec.y - origY; double dirZ = tmpvec.z - origZ; // Handle the case where pickRay is almost parallel to the Z-plane if (almostZero(dirZ)) { return null; } double t = -origZ / dirZ; if (perspective && t < 0) { // TODO: Or should we use Infinity? (RT-26888) return null; } if (ret == null) { ret = new Point2D(); } ret.setLocation((float) (origX + (dirX * t)), (float) (origY + (dirY * t))); return ret; } // Good to find a home for commonly use util. code such as EPS. // and almostZero. This code currently defined in multiple places, // such as Affine3D and GeneralTransform3D. private static final double EPSILON_ABSOLUTE = 1.0e-5; static boolean almostZero(double a) { return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE)); } private static boolean isNonZero(double v) { return ((v > EPS) || (v < -EPS)); } public void transform(BaseTransform t) { t.transform(origin, origin); t.deltaTransform(direction, direction); } public void inverseTransform(BaseTransform t) throws NoninvertibleTransformException { t.inverseTransform(origin, origin); t.inverseDeltaTransform(direction, direction); } public PickRay project(BaseTransform inversetx, boolean perspective, Vec3d tmpvec, Point2D ret) { if (tmpvec == null) { tmpvec = new Vec3d(); } inversetx.transform(origin, tmpvec); double origX = tmpvec.x; double origY = tmpvec.y; double origZ = tmpvec.z; tmpvec.add(origin, direction); inversetx.transform(tmpvec, tmpvec); double dirX = tmpvec.x - origX; double dirY = tmpvec.y - origY; double dirZ = tmpvec.z - origZ; PickRay pr = new PickRay(); pr.origin.x = origX; pr.origin.y = origY; pr.origin.z = origZ; pr.direction.x = dirX; pr.direction.y = dirY; pr.direction.z = dirZ; return pr; } @Override public String toString() { return "origin: " + origin + " direction: " + direction; } }