All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scijava.java3d.utils.universe.ViewInfo Maven / Gradle / Ivy

The newest version!
/*
 * 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.universe ;

import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.Rectangle;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.scijava.java3d.Canvas3D;
import org.scijava.java3d.Node;
import org.scijava.java3d.PhysicalBody;
import org.scijava.java3d.PhysicalEnvironment;
import org.scijava.java3d.Screen3D;
import org.scijava.java3d.Sensor;
import org.scijava.java3d.Transform3D;
import org.scijava.java3d.View;
import org.scijava.java3d.ViewPlatform;
import org.scijava.vecmath.Point3d;
import org.scijava.vecmath.Vector3d;

/**
 * Provides methods to extract synchronized transform information from a View.
 * These transforms are derived from application scene graph information, as
 * opposed to similar core Java 3D methods that derive transforms from
 * internally maintained data.	This allows updates to the scene graph to be
 * synchronized with the current view platform position.

* * The architecture of the Java 3D 1.3 sample implementation introduces a * frame latency between updates to the application scene graph structure and * their effects on internal Java 3D state. getImagePlateToVworld * and other methods in the core Java 3D classes use a transform from view * platform coordinates to virtual world coordinates that can be out of date * with respect to the state of the view platform as set by the application. * When an application uses the transforms returned by those methods to update * view dependent parts of the scene graph, those updates might not be * synchronized with what the viewer actually sees.

* * The methods in this class work around this problem at the expense of * querying the application state of the scene graph to get the current * transform from view platform to virtual world coordinates. This can * involve a potential performance degradation, however, since the application * scene graph state is not designed for high performance queries. The view * platform must also have ALLOW_LOCAL_TO_VWORLD_READ capability * set, which potentially inhibits internal scene graph optimization.

* * On the other hand, application behaviors that create the view platform * transformation directly will have access to it without the need to query it * from the scene graph; in that case, the transforms from physical * coordinates to view platform coordinates provided by this class are all * that are needed. The ALLOW_LOCAL_TO_VWORLD_READ view platform * capability doesn't need to be set for these applications.

* * Other Synchronization Issues

* * Scene graph updates are guaranteed to take effect in the same frame only * if run from the processStimulus() method of a Behavior. Updates from * multiple behaviors are only guaranteed to take effect in the same frame if * they're responding to a WakeupOnElapsedFrames(0) condition. Use a single * behavior to perform view dependent updates if possible; otherwise, use * WakeupOnElapsedFrames(0) and set behavior scheduling intervals to ensure * that behaviors that need the current view platform transform are run after * it's set. Updating scene graph elements from anything other than the * Behavior thread, such as an external input thread or a renderer callback * in Canvas3D, will not necessarily be synchronized with rendering.

* * Direct updates to geometry data have a different frame latency than * updates to scene graph transforms and structure. In the Java 3D 1.3 * architecture, updates to by-reference geometry arrays and texture data have * a 1-frame latency, while updates to transforms and scene graph structure * have a 2-frame latency. Because of bug 4799494, which is outstanding * in Java 3D 1.3.1, updates to by-copy geometry arrays also have a 1-frame * latency. It is therefore recommended that view dependent scene graph * updates be limited to transforms and scene graph structure only.

* * If it is not possible to avoid updating geometry directly, then these * updates must be delayed by one frame in order to remain synchronized with * the view platform. This can be accomplished by creating an additional * behavior to actually update the geometry, separate from the behavior that * computes the changes that need to be made based on current view state. If * the update behavior is awakened by a behavior post from the computing * behavior then the update will be delayed by a single frame.

* * Implementation Notes

* * This utility is essentially a rewrite of a few private Java 3D core * classes, but designed for public use and source code availability. The * source code may be helpful in understanding some of the more complex * aspects of the view model, especially with regards to various interactions * between attributes which are not adequately documented. None of the actual * core Java 3D source code is used, but the code is designed to comply with * the view model as defined by the Java 3D Specification, so it can be * considered an alternative implementation. This class will produce the * same results as the Java 3D core implementation except for:

    * *
  • The frame latency issue for virtual world transforms.
  • * *

  • Active clip node status. If a clip node is active in the scene graph, * it should override the view's back clip plane. This class has no such * information, so this can't be implemented.
  • * *

  • "Infinite" view transforms for background geometry. These are simply * the rotation components of the normal view transforms with adjusted * clip planes. Again, this function depends upon scene graph content * inaccessible to this class.
  • * *

  • Small floating point precision differences resulting from the * alternative computations.
  • * *

  • Bugs in this class and the Java 3D core.
  • * *

  • Tracked head position.

* * The last item deserves some mention. Java 3D provides no way to directly * query the tracked head position being used by the renderer. The View's * getUserHeadToVworld method always incorporates a virtual world * transform that is out of date with respect to the application scene graph * state. ViewInfo reads data from the head tracking sensor directly, but * since head trackers are continuous input devices, getting the same data * that the renderer is using is unlikely. See the source code for the * private method getHeadInfo in this class for more information * and possible workarounds.

* * Thread Safety

* * All transforms are lazily evaluated. The updateScreen, * updateCanvas, updateViewPlatform, * updateView, and updateHead methods just set flags * indicating that derived transforms need to be recomputed; they are safe to * call from any thread. updateCanvas, for example, can safely * be called from an AWT event listener.

* * Screens and view platforms can be shared between separate views in the Java * 3D view model. To remain accurate, ViewInfo also allows this sharing. * Since it is likely that a multi-view application has separate threads * managing each view, potential concurrent modification of data associated * with a screen or a view platform is internally synchronized in this class. * It is safe for each thread to use its own instance of a ViewInfo * corresponding to the view it is managing.

* * Otherwise, none of the other methods in this class are internally * synchronized. Except for the update methods mentioned above, a single * instance of ViewInfo should not be used by more than one concurrent thread * without external synchronization.

* * @since Java 3D 1.3.1 */ public class ViewInfo { private final static boolean verbose = false ; /** * Indicates that updates to a Screen3D associated with the View should * be automatically checked with each call to a public method in this * class. */ public final static int SCREEN_AUTO_UPDATE = 1 ; /** * Indicates that updates to a Canvas3D associated with the View should * be automatically checked with each call to a public method in this * class. */ public final static int CANVAS_AUTO_UPDATE = 2 ; /** * Indicates that updates to the View should be automatically checked * with each call to a public method in this class. */ public final static int VIEW_AUTO_UPDATE = 4 ; /** * Indicates that updates to the tracked head position should be * automatically checked with each call to a public method in this class. */ public final static int HEAD_AUTO_UPDATE = 8 ; /** * Indicates that updates to the ViewPlatform localToVworld * transform should be automatically checked with each call to a public * method in this class. The View must be attached to a ViewPlatform * which is part of a live scene graph, and the ViewPlatform node must * have its ALLOW_LOCAL_TO_VWORLD_READ capability set. */ public final static int PLATFORM_AUTO_UPDATE = 16 ; // // Screen3D and ViewPlatform instances are shared across multiple Views in // the Java 3D view model. Since ViewInfo is per-View and we want to // cache screen and platform derived data, we maintain static references // to the screens and platforms here. // // From a design standpoint our ViewInfo objects should probably be in the // scope of an object that encloses these maps so they can be gc'ed // properly. This is cumbersome with the current design constraints, so // for now we provide an alternative constructor to override these static // maps and a method to explicitly clear them. The alternative // constructor can be used to wrap this class into a multi-view context // that provides the maps. // private static Map staticVpMap = new HashMap() ; private static Map staticSiMap = new HashMap() ; private Map screenMap = null ; private Map viewPlatformMap = null ; // The target View and some derived data. private View view = null ; private Sensor headTracker = null ; private boolean useTracking = false ; private boolean clipVirtual = false ; // The current ViewPlatform and Canvas3D information used by this object. private ViewPlatformInfo vpi = null ; private int canvasCount = 0 ; private Map canvasMap = new HashMap() ; private CanvasInfo[] canvasInfo = new CanvasInfo[1] ; // This View's update flags. The other update flags are maintained by // ScreenInfo, CanvasInfo, and ViewPlatformInfo. private boolean updateView = true ; private boolean updateHead = true ; private boolean autoUpdate = false ; private int autoUpdateFlags = 0 ; // Cached View policies. private int viewPolicy = View.SCREEN_VIEW ; private int resizePolicy = View.PHYSICAL_WORLD ; private int movementPolicy = View.PHYSICAL_WORLD ; private int eyePolicy = View.RELATIVE_TO_FIELD_OF_VIEW ; private int projectionPolicy = View.PERSPECTIVE_PROJECTION ; private int frontClipPolicy = View.PHYSICAL_EYE ; private int backClipPolicy = View.PHYSICAL_EYE ; private int scalePolicy = View.SCALE_SCREEN_SIZE ; private boolean coeCentering = true ; // This View's cached transforms. See ScreenInfo, CanvasInfo, and // ViewPlatformInfo for the rest of the cached transforms. private Transform3D coeToTrackerBase = null ; private Transform3D headToHeadTracker = null ; // These are from the head tracker read. private Transform3D headTrackerToTrackerBase = null ; private Transform3D trackerBaseToHeadTracker = null ; // These are derived from the head tracker read. private Transform3D headToTrackerBase = null ; private Transform3D coeToHeadTracker = null ; // Cached physical body and environment. private PhysicalEnvironment env = null ; private PhysicalBody body = null ; private Point3d leftEyeInHead = new Point3d() ; private Point3d rightEyeInHead = new Point3d() ; // Temporary variables. These could just be new'ed as needed, but we'll // assume that ViewInfo instances are used much more than they're created. private Vector3d v3d = new Vector3d() ; private double[] m16d = new double[16] ; private Point3d leftEye = new Point3d() ; private Point3d rightEye = new Point3d() ; private Map newMap = new HashMap() ; private Set newSet = new HashSet() ; /** * Creates a new ViewInfo for the specified View.

* * Applications are responsible for informing this class of changes to the * View, its Canvas3D and Screen3D components, the tracked head position, * and the ViewPlatform's localToVworld transform. These * notifications are performed with the updateView, * updateCanvas, updateScreen, * updateHead, and updateViewPlatform * methods.

* * The View must be attached to a ViewPlatform. If the ViewPlatform is * attached to a live scene graph, then ALLOW_POLICY_READ * capability must be set on the ViewPlatform node. * * @param view the View to use * @see #updateView * @see #updateCanvas updateCanvas(Canvas3D) * @see #updateScreen updateScreen(Screen3D) * @see #updateHead * @see #updateViewPlatform */ public ViewInfo(View view) { this(view, 0) ; } /** * Creates a new ViewInfo for the specified View. The View must be * attached to a ViewPlatform. If the ViewPlatform is attached to a live * scene graph, then ALLOW_POLICY_READ capability must be set * on the ViewPlatform node. * * @param view the View to use

* @param autoUpdateFlags a logical OR of any of the * VIEW_AUTO_UPDATE, CANVAS_AUTO_UPDATE, * SCREEN_AUTO_UPDATE, HEAD_AUTO_UPDATE, or * PLATFORM_AUTO_UPDATE flags to control whether changes to * the View, its Canvas3D or Screen3D components, the tracked head * position, or the ViewPlatform's localToVworld transform * are checked automatically with each call to a public method of this * class; if a flag is not set, then the application must inform this * class of updates to the corresponding data */ public ViewInfo(View view, int autoUpdateFlags) { this(view, autoUpdateFlags, staticSiMap, staticVpMap) ; } /** * Creates a new ViewInfo for the specified View. The View must be * attached to a ViewPlatform. If the ViewPlatform is attached to a live * scene graph, then ALLOW_POLICY_READ capability must be set * on the ViewPlatform node.

* * ViewInfo caches Screen3D and ViewPlatform data, but Screen3D and * ViewPlatform instances are shared across multiple Views in the Java 3D * view model. Since ViewInfo is per-View, all ViewInfo constructors * except for this one use static references to manage the shared Screen3D * and ViewPlatform objects. In this constructor, however, the caller * supplies two Map instances to hold these references for all ViewInfo * instances, so static references can be avoided; it can be used to wrap * this class into a multi-view context that provides the required * maps.

* * Alternatively, the other constructors can be used by calling * ViewInfo.clear when done with ViewInfo, or by simply * retaining the static references until the JVM exits.

* * @param view the View to use

* @param autoUpdateFlags a logical OR of any of the * VIEW_AUTO_UPDATE, CANVAS_AUTO_UPDATE, * SCREEN_AUTO_UPDATE, HEAD_AUTO_UPDATE, or * PLATFORM_AUTO_UPDATE flags to control whether changes to * the View, its Canvas3D or Screen3D components, the tracked head * position, or the ViewPlatform's localToVworld transform * are checked automatically with each call to a public method of this * class; if a flag is not set, then the application must inform this * class of updates to the corresponding data

* @param screenMap a writeable Map to hold Screen3D information * @param viewPlatformMap a writeable Map to hold ViewPlatform information */ public ViewInfo(View view, int autoUpdateFlags, Map screenMap, Map viewPlatformMap) { if (verbose) System.err.println("ViewInfo: init " + hashCode()) ; if (view == null) throw new IllegalArgumentException("View is null") ; if (screenMap == null) throw new IllegalArgumentException("screenMap is null") ; if (viewPlatformMap == null) throw new IllegalArgumentException("viewPlatformMap is null") ; this.view = view ; this.screenMap = screenMap ; this.viewPlatformMap = viewPlatformMap ; if (autoUpdateFlags == 0) { this.autoUpdate = false ; } else { this.autoUpdate = true ; this.autoUpdateFlags = autoUpdateFlags ; } getViewInfo() ; } /** * Gets the current transforms from image plate coordinates to view * platform coordinates and copies them into the given Transform3Ds.

* * With a monoscopic canvas the image plate transform is copied to the * first argument and the second argument is not used. For a stereo * canvas the first argument receives the left image plate transform, and * if the second argument is non-null it receives the right image plate * transform. These transforms are always the same unless a head mounted * display driven by a single stereo canvas is in use. * * @param c3d the Canvas3D associated with the image plate * @param ip2vpl the Transform3D to receive the left transform * @param ip2vpr the Transform3D to receive the right transform, or null */ public void getImagePlateToViewPlatform(Canvas3D c3d, Transform3D ip2vpl, Transform3D ip2vpr) { CanvasInfo ci = updateCache (c3d, "getImagePlateToViewPlatform", false) ; getImagePlateToViewPlatform(ci) ; ip2vpl.set(ci.plateToViewPlatform) ; if (ci.useStereo && ip2vpr != null) ip2vpr.set(ci.rightPlateToViewPlatform) ; } private void getImagePlateToViewPlatform(CanvasInfo ci) { if (ci.updatePlateToViewPlatform) { if (verbose) System.err.println("updating PlateToViewPlatform") ; if (ci.plateToViewPlatform == null) ci.plateToViewPlatform = new Transform3D() ; getCoexistenceToImagePlate(ci) ; getViewPlatformToCoexistence(ci) ; ci.plateToViewPlatform.mul(ci.coeToPlate, ci.viewPlatformToCoe) ; ci.plateToViewPlatform.invert() ; if (ci.useStereo) { if (ci.rightPlateToViewPlatform == null) ci.rightPlateToViewPlatform = new Transform3D() ; ci.rightPlateToViewPlatform.mul(ci.coeToRightPlate, ci.viewPlatformToCoe) ; ci.rightPlateToViewPlatform.invert() ; } ci.updatePlateToViewPlatform = false ; if (verbose) t3dPrint(ci.plateToViewPlatform, "plateToVp") ; } } /** * Gets the current transforms from image plate coordinates to virtual * world coordinates and copies them into the given Transform3Ds.

* * With a monoscopic canvas the image plate transform is copied to the * first argument and the second argument is not used. For a stereo * canvas the first argument receives the left image plate transform, and * if the second argument is non-null it receives the right image plate * transform. These transforms are always the same unless a head mounted * display driven by a single stereo canvas is in use.

* * The View must be attached to a ViewPlatform which is part of a live * scene graph, and the ViewPlatform node must have its * ALLOW_LOCAL_TO_VWORLD_READ capability set. * * @param c3d the Canvas3D associated with the image plate * @param ip2vwl the Transform3D to receive the left transform * @param ip2vwr the Transform3D to receive the right transform, or null */ public void getImagePlateToVworld(Canvas3D c3d, Transform3D ip2vwl, Transform3D ip2vwr) { CanvasInfo ci = updateCache(c3d, "getImagePlateToVworld", true) ; getImagePlateToVworld(ci) ; ip2vwl.set(ci.plateToVworld) ; if (ci.useStereo && ip2vwr != null) ip2vwr.set(ci.rightPlateToVworld) ; } private void getImagePlateToVworld(CanvasInfo ci) { if (ci.updatePlateToVworld) { if (verbose) System.err.println("updating PlateToVworld") ; if (ci.plateToVworld == null) ci.plateToVworld = new Transform3D() ; getImagePlateToViewPlatform(ci) ; ci.plateToVworld.mul (vpi.viewPlatformToVworld, ci.plateToViewPlatform) ; if (ci.useStereo) { if (ci.rightPlateToVworld == null) ci.rightPlateToVworld = new Transform3D() ; ci.rightPlateToVworld.mul (vpi.viewPlatformToVworld, ci.rightPlateToViewPlatform) ; } ci.updatePlateToVworld = false ; } } /** * Gets the current transforms from coexistence coordinates to image plate * coordinates and copies them into the given Transform3Ds. The default * coexistence centering enable and window movement policies are * true and PHYSICAL_WORLD respectively, which * will center coexistence coordinates to the middle of the canvas, * aligned with the screen (image plate). A movement policy of * VIRTUAL_WORLD centers coexistence coordinates to the * middle of the screen.

* * If coexistence centering is turned off, then canvases and screens can * have arbitrary positions with respect to coexistence, set through the * the Screen3D trackerBaseToImagePlate transform and the * PhysicalEnvironment coexistenceToTrackerBase transform. * These are calibration constants used for multiple fixed screen displays. * For head mounted displays the transform is determined by the user head * position along with calibration parameters found in Screen3D and * PhysicalBody. (See the source code for the private method * getEyesHMD for more information).

* * With a monoscopic canvas the image plate transform is copied to the * first argument and the second argument is not used. For a stereo * canvas the first argument receives the left image plate transform, and * if the second argument is non-null it receives the right image plate * transform. These transforms are always the same unless a head mounted * display driven by a single stereo canvas is in use.

* * @param c3d the Canvas3D associated with the image plate * @param coe2ipl the Transform3D to receive the left transform * @param coe2ipr the Transform3D to receive the right transform, or null */ public void getCoexistenceToImagePlate(Canvas3D c3d, Transform3D coe2ipl, Transform3D coe2ipr) { CanvasInfo ci = updateCache(c3d, "getCoexistenceToImagePlate", false) ; getCoexistenceToImagePlate(ci) ; coe2ipl.set(ci.coeToPlate) ; if (ci.useStereo && coe2ipr != null) coe2ipr.set(ci.coeToRightPlate) ; } private void getCoexistenceToImagePlate(CanvasInfo ci) { // // This method will always set coeToRightPlate even if stereo is not // in use. This is necessary so that getEyeToImagePlate() can handle // a monoscopic view policy of CYCLOPEAN_EYE_VIEW (which averages the // left and right eye positions) when the eyepoints are expressed in // coexistence coordinates or are derived from the tracked head. // if (ci.updateCoeToPlate) { if (verbose) System.err.println("updating CoeToPlate") ; if (ci.coeToPlate == null) { ci.coeToPlate = new Transform3D() ; ci.coeToRightPlate = new Transform3D() ; } if (viewPolicy == View.HMD_VIEW) { // Head mounted displays have their image plates fixed with // respect to the head, so get the head position in // coexistence. ci.coeToPlate.mul(ci.si.headTrackerToLeftPlate, coeToHeadTracker) ; if (ci.useStereo) // This is the only case in the view model in which the // right plate transform could be different from the left. ci.coeToRightPlate.mul(ci.si.headTrackerToRightPlate, coeToHeadTracker) ; else ci.coeToRightPlate.set(ci.coeToPlate) ; } else if (coeCentering) { // The default, for fixed single screen displays with no // motion tracking. The transform is just a translation. if (movementPolicy == View.PHYSICAL_WORLD) // The default. Coexistence is centered in the window. v3d.set(ci.canvasX + (ci.canvasWidth / 2.0), ci.canvasY + (ci.canvasHeight / 2.0), 0.0) ; else // Coexistence is centered in the screen. v3d.set(ci.si.screenWidth / 2.0, ci.si.screenHeight / 2.0, 0.0) ; ci.coeToPlate.set(v3d) ; ci.coeToRightPlate.set(v3d) ; } else { // Coexistence centering should be false for multiple fixed // screens and/or motion tracking. trackerBaseToImagePlate // and coexistenceToTrackerBase are used explicitly. ci.coeToPlate.mul(ci.si.trackerBaseToPlate, coeToTrackerBase) ; ci.coeToRightPlate.set(ci.coeToPlate) ; } ci.updateCoeToPlate = false ; if (verbose) t3dPrint(ci.coeToPlate, "coeToPlate") ; } } /** * Gets the current transform from view platform coordinates to * coexistence coordinates and copies it into the given transform. View * platform coordinates are always aligned with coexistence coordinates * but may differ in scale and in Y and Z offset. The scale is derived * from the window resize and screen scale policies, while the offset is * derived from the view attach policy.

* * Java 3D constructs a view from the physical position of the eyes * relative to the physical positions of the image plates; it then uses a * view platform to position that physical configuration into the virtual * world and from there computes the correct projections of the virtual * world onto the physical image plates. Coexistence coordinates are used * to place the physical positions of the view platform, eyes, head, image * plate, sensors, and tracker base in relation to each other. The view * platform is positioned with respect to the virtual world through the * scene graph, so the view platform to coexistence transform defines the * space in which the virtual world and physical world coexist.

* * This method requires a Canvas3D. A different transform may be returned * for each canvas in the view if any of the following apply:

    * *
  • The window resize policy is PHYSICAL_WORLD, which * alters the scale depending upon the width of the canvas.
  • * *

  • The screen scale policy is SCALE_SCREEN_SIZE, * which alters the scale depending upon the width of the screen * associated with the canvas.
  • * *

  • A window eyepoint policy of RELATIVE_TO_FIELD_OF_VIEW * with a view attach policy of NOMINAL_HEAD in effect, * which sets the view platform Z offset in coexistence coordinates * based on the width of the canvas. These are the default policies. * The offset also follows the width of the canvas when the * NOMINAL_FEET view attach policy is used.
* * @param c3d the Canvas3D to use * @param vp2coe the Transform3D to receive the transform */ public void getViewPlatformToCoexistence(Canvas3D c3d, Transform3D vp2coe) { CanvasInfo ci = updateCache (c3d, "getViewPlatformToCoexistence", false) ; getViewPlatformToCoexistence(ci) ; vp2coe.set(ci.viewPlatformToCoe) ; } private void getViewPlatformToCoexistence(CanvasInfo ci) { if (!ci.updateViewPlatformToCoe) return ; if (verbose) System.err.println("updating ViewPlatformToCoe") ; if (ci.viewPlatformToCoe == null) ci.viewPlatformToCoe = new Transform3D() ; // // The scale from view platform coordinates to coexistence coordinates // has two components -- the screen scale and the window scale. The // window scale only applies if the resize policy is PHYSICAL_WORLD. // // This scale is not the same as the vworld to view platform scale. // The latter is contained in the view platform's localToVworld // transform as defined by the scene graph. The complete scale factor // from virtual units to physical units is the product of the vworld // to view platform scale and the view platform to coexistence scale. // getScreenScale(ci) ; if (resizePolicy == View.PHYSICAL_WORLD) ci.viewPlatformToCoe.setScale(ci.screenScale * ci.windowScale) ; else ci.viewPlatformToCoe.setScale(ci.screenScale) ; if (viewPolicy == View.HMD_VIEW) { // In HMD mode view platform coordinates are the same as // coexistence coordinates, except for scale. ci.updateViewPlatformToCoe = false ; return ; } // // Otherwise, get the offset of the origin of view platform // coordinates relative to the origin of coexistence. This is is // specified by two policies: the view platform's view attach policy // and the physical environment's coexistence center in pworld policy. // double eyeOffset ; double eyeHeight = body.getNominalEyeHeightFromGround() ; int viewAttachPolicy = view.getViewPlatform().getViewAttachPolicy() ; int pworldAttachPolicy = env.getCoexistenceCenterInPworldPolicy() ; if (eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) // The view platform origin is the same as the eye position. eyeOffset = ci.getFieldOfViewOffset() ; else // The view platform origin is independent of the eye position. eyeOffset = body.getNominalEyeOffsetFromNominalScreen() ; if (pworldAttachPolicy == View.NOMINAL_SCREEN) { // The default. The physical coexistence origin locates the // nominal screen. This is rarely, if ever, set to anything // else, and the intended effects of the other settings are // not well documented. if (viewAttachPolicy == View.NOMINAL_HEAD) { // The default. The view platform origin is at the origin // of the nominal head in coexistence coordinates, offset // from the screen along +Z. If the window eyepoint // policy is RELATIVE_TO_FIELD_OF_VIEW, then the eyepoint // is the same as the view platform origin. v3d.set(0.0, 0.0, eyeOffset) ; } else if (viewAttachPolicy == View.NOMINAL_SCREEN) { // View platform and coexistence are the same except for // scale. v3d.set(0.0, 0.0, 0.0) ; } else { // The view platform origin is at the ground beneath the // head. v3d.set(0.0, -eyeHeight, eyeOffset) ; } } else if (pworldAttachPolicy == View.NOMINAL_HEAD) { // The physical coexistence origin locates the nominal head. if (viewAttachPolicy == View.NOMINAL_HEAD) { // The view platform origin is set to the head; // coexistence and view platform coordinates differ only // in scale. v3d.set(0.0, 0.0, 0.0) ; } else if (viewAttachPolicy == View.NOMINAL_SCREEN) { // The view platform is set in front of the head, at the // nominal screen location. v3d.set(0.0, 0.0, -eyeOffset) ; } else { // The view platform origin is at the ground beneath the // head. v3d.set(0.0, -eyeHeight, 0.0) ; } } else { // The physical coexistence origin locates the nominal feet. if (viewAttachPolicy == View.NOMINAL_HEAD) { v3d.set(0.0, eyeHeight, 0.0) ; } else if (viewAttachPolicy == View.NOMINAL_SCREEN) { v3d.set(0.0, eyeHeight, -eyeOffset) ; } else { v3d.set(0.0, 0.0, 0.0) ; } } ci.viewPlatformToCoe.setTranslation(v3d) ; ci.updateViewPlatformToCoe = false ; if (verbose) t3dPrint(ci.viewPlatformToCoe, "vpToCoe") ; } /** * Gets the current transform from coexistence coordinates to * view platform coordinates and copies it into the given transform.

* * This method requires a Canvas3D. The returned transform may differ * across canvases for the same reasons as discussed in the description of * getViewPlatformToCoexistence.

* * @param c3d the Canvas3D to use * @param coe2vp the Transform3D to receive the transform * @see #getViewPlatformToCoexistence * getViewPlatformToCoexistence(Canvas3D, Transform3D) */ public void getCoexistenceToViewPlatform(Canvas3D c3d, Transform3D coe2vp) { CanvasInfo ci = updateCache (c3d, "getCoexistenceToViewPlatform", false) ; getCoexistenceToViewPlatform(ci) ; coe2vp.set(ci.coeToViewPlatform) ; } private void getCoexistenceToViewPlatform(CanvasInfo ci) { if (ci.updateCoeToViewPlatform) { if (verbose) System.err.println("updating CoeToViewPlatform") ; if (ci.coeToViewPlatform == null) ci.coeToViewPlatform = new Transform3D() ; getViewPlatformToCoexistence(ci) ; ci.coeToViewPlatform.invert(ci.viewPlatformToCoe) ; ci.updateCoeToViewPlatform = false ; if (verbose) t3dPrint(ci.coeToViewPlatform, "coeToVp") ; } } /** * Gets the current transform from coexistence coordinates to virtual * world coordinates and copies it into the given transform.

* * The View must be attached to a ViewPlatform which is part of a live * scene graph, and the ViewPlatform node must have its * ALLOW_LOCAL_TO_VWORLD_READ capability set.

* * This method requires a Canvas3D. The returned transform may differ * across canvases for the same reasons as discussed in the description of * getViewPlatformToCoexistence.

* * @param c3d the Canvas3D to use * @param coe2vw the Transform3D to receive the transform * @see #getViewPlatformToCoexistence * getViewPlatformToCoexistence(Canvas3D, Transform3D) */ public void getCoexistenceToVworld(Canvas3D c3d, Transform3D coe2vw) { CanvasInfo ci = updateCache(c3d, "getCoexistenceToVworld", true) ; getCoexistenceToVworld(ci) ; coe2vw.set(ci.coeToVworld) ; } private void getCoexistenceToVworld(CanvasInfo ci) { if (ci.updateCoeToVworld) { if (verbose) System.err.println("updating CoexistenceToVworld") ; if (ci.coeToVworld == null) ci.coeToVworld = new Transform3D() ; getCoexistenceToViewPlatform(ci) ; ci.coeToVworld.mul(vpi.viewPlatformToVworld, ci.coeToViewPlatform) ; ci.updateCoeToVworld = false ; } } /** * Gets the transforms from eye coordinates to image plate coordinates and * copies them into the Transform3Ds specified.

* * When head tracking is used the eye positions are taken from the head * position and set in relation to the image plates with each Screen3D's * trackerBaseToImagePlate transform. Otherwise the window * eyepoint policy is used to derive the eyepoint relative to the image * plate. When using a head mounted display the eye position is * determined solely by calibration constants in Screen3D and * PhysicalBody; see the source code for the private method * getEyesHMD for more information.

* * Eye coordinates are always aligned with image plate coordinates, so * these transforms are always just translations. With a monoscopic * canvas the eye transform is copied to the first argument and the second * argument is not used. For a stereo canvas the first argument receives * the left eye transform, and if the second argument is non-null it * receives the right eye transform. * * @param c3d the Canvas3D associated with the image plate * @param e2ipl the Transform3D to receive left transform * @param e2ipr the Transform3D to receive right transform, or null */ public void getEyeToImagePlate(Canvas3D c3d, Transform3D e2ipl, Transform3D e2ipr) { CanvasInfo ci = updateCache(c3d, "getEyeToImagePlate", false) ; getEyeToImagePlate(ci) ; e2ipl.set(ci.eyeToPlate) ; if (ci.useStereo && e2ipr != null) e2ipr.set(ci.rightEyeToPlate) ; } private void getEyeToImagePlate(CanvasInfo ci) { if (ci.updateEyeInPlate) { if (verbose) System.err.println("updating EyeInPlate") ; if (ci.eyeToPlate == null) ci.eyeToPlate = new Transform3D() ; if (viewPolicy == View.HMD_VIEW) { getEyesHMD(ci) ; } else if (useTracking) { getEyesTracked(ci) ; } else { getEyesFixedScreen(ci) ; } ci.updateEyeInPlate = false ; if (verbose) System.err.println("eyeInPlate: " + ci.eyeInPlate) ; } } // // Get physical eye positions for head mounted displays. These are // determined solely by the headTrackerToImagePlate and headToHeadTracker // calibration constants defined by Screen3D and the PhysicalBody. // // Note that headTrackerLeftToImagePlate and headTrackerToRightImagePlate // should be set according to the *apparent* position and orientation of // the image plates, relative to the head and head tracker, as viewed // through the HMD optics. This is also true of the "physical" screen // width and height specified by the Screen3D -- they should be the // *apparent* width and height as viewed through the HMD optics. They // must be set directly through the Screen3D methods; the default pixel // metrics of 90 pixels/inch used by Java 3D aren't appropriate for HMD // optics. // // Most HMDs have 100% overlap between the left and right displays; in // that case, headTrackerToLeftImagePlate and headTrackerToRightImagePlate // should be identical. The HMD manufacturer's specifications of the // optics in terms of field of view, image overlap, and distance to the // focal plane should be used to derive these parameters. // private void getEyesHMD(CanvasInfo ci) { if (ci.useStereo) { // This case is for head mounted displays driven by a single // stereo canvas on a single screen. These use a field sequential // stereo signal to split the left and right images. leftEye.set(leftEyeInHead) ; headToHeadTracker.transform(leftEye) ; ci.si.headTrackerToLeftPlate.transform(leftEye, ci.eyeInPlate) ; rightEye.set(rightEyeInHead) ; headToHeadTracker.transform(rightEye) ; ci.si.headTrackerToRightPlate.transform(rightEye, ci.rightEyeInPlate) ; if (ci.rightEyeToPlate == null) ci.rightEyeToPlate = new Transform3D() ; v3d.set(ci.rightEyeInPlate) ; ci.rightEyeToPlate.set(v3d) ; } else { // This case is for 2-channel head mounted displays driven by two // monoscopic screens, one for each eye. switch (ci.monoscopicPolicy) { case View.LEFT_EYE_VIEW: leftEye.set(leftEyeInHead) ; headToHeadTracker.transform(leftEye) ; ci.si.headTrackerToLeftPlate.transform(leftEye, ci.eyeInPlate) ; break ; case View.RIGHT_EYE_VIEW: rightEye.set(rightEyeInHead) ; headToHeadTracker.transform(rightEye) ; ci.si.headTrackerToRightPlate.transform(rightEye, ci.eyeInPlate) ; break ; case View.CYCLOPEAN_EYE_VIEW: default: throw new IllegalStateException ("Illegal monoscopic view policy for 2-channel HMD") ; } } v3d.set(ci.eyeInPlate) ; ci.eyeToPlate.set(v3d) ; } private void getEyesTracked(CanvasInfo ci) { leftEye.set(leftEyeInHead) ; rightEye.set(rightEyeInHead) ; headToTrackerBase.transform(leftEye) ; headToTrackerBase.transform(rightEye) ; if (coeCentering) { // Coexistence and tracker base coordinates are the same. // Centering is normally turned off for tracking. getCoexistenceToImagePlate(ci) ; ci.coeToPlate.transform(leftEye) ; ci.coeToRightPlate.transform(rightEye) ; } else { // The normal policy for head tracking. ci.si.trackerBaseToPlate.transform(leftEye) ; ci.si.trackerBaseToPlate.transform(rightEye) ; } setEyeScreenRelative(ci, leftEye, rightEye) ; } private void getEyesFixedScreen(CanvasInfo ci) { switch (eyePolicy) { case View.RELATIVE_TO_FIELD_OF_VIEW: double z = ci.getFieldOfViewOffset() ; setEyeWindowRelative(ci, z, z) ; break ; case View.RELATIVE_TO_WINDOW: setEyeWindowRelative(ci, ci.leftManualEyeInPlate.z, ci.rightManualEyeInPlate.z) ; break ; case View.RELATIVE_TO_SCREEN: setEyeScreenRelative(ci, ci.leftManualEyeInPlate, ci.rightManualEyeInPlate) ; break ; case View.RELATIVE_TO_COEXISTENCE: view.getLeftManualEyeInCoexistence(leftEye) ; view.getRightManualEyeInCoexistence(rightEye) ; getCoexistenceToImagePlate(ci) ; ci.coeToPlate.transform(leftEye) ; ci.coeToRightPlate.transform(rightEye) ; setEyeScreenRelative(ci, leftEye, rightEye) ; break ; } } private void setEyeWindowRelative(CanvasInfo ci, double leftZ, double rightZ) { // Eye position X is offset from the window center. double centerX = (ci.canvasX + (ci.canvasWidth / 2.0)) ; leftEye.x = centerX + leftEyeInHead.x ; rightEye.x = centerX + rightEyeInHead.x ; // Eye position Y is always the canvas center. leftEye.y = rightEye.y = ci.canvasY + (ci.canvasHeight / 2.0) ; // Eye positions Z are as given. leftEye.z = leftZ ; rightEye.z = rightZ ; setEyeScreenRelative(ci, leftEye, rightEye) ; } private void setEyeScreenRelative(CanvasInfo ci, Point3d leftEye, Point3d rightEye) { if (ci.useStereo) { ci.eyeInPlate.set(leftEye) ; ci.rightEyeInPlate.set(rightEye) ; if (ci.rightEyeToPlate == null) ci.rightEyeToPlate = new Transform3D() ; v3d.set(ci.rightEyeInPlate) ; ci.rightEyeToPlate.set(v3d) ; } else { switch (ci.monoscopicPolicy) { case View.CYCLOPEAN_EYE_VIEW: ci.eyeInPlate.set((leftEye.x + rightEye.x) / 2.0, (leftEye.y + rightEye.y) / 2.0, (leftEye.z + rightEye.z) / 2.0) ; break ; case View.LEFT_EYE_VIEW: ci.eyeInPlate.set(leftEye) ; break ; case View.RIGHT_EYE_VIEW: ci.eyeInPlate.set(rightEye) ; break ; } } v3d.set(ci.eyeInPlate) ; ci.eyeToPlate.set(v3d) ; } /** * Gets the current transforms from eye coordinates to view platform * coordinates and copies them into the given Transform3Ds.

* * With a monoscopic canvas the eye transform is copied to the first * argument and the second argument is not used. For a stereo canvas the * first argument receives the left eye transform, and if the second * argument is non-null it receives the right eye transform.

* * This method requires a Canvas3D. When using a head mounted display, * head tracking with fixed screens, or a window eyepoint policy of * RELATIVE_TO_COEXISTENCE, then the transforms returned may * be different for each canvas if stereo is not in use and they have * different monoscopic view policies. They may additionally differ in * scale across canvases with the PHYSICAL_WORLD window * resize policy or the SCALE_SCREEN_SIZE screen scale * policy, which alter the scale depending upon the width of the canvas or * the width of the screen respectively.

* * With window eyepoint policies of RELATIVE_TO_FIELD_OF_VIEW, * RELATIVE_TO_SCREEN, or RELATIVE_TO_WINDOW, * then the transforms returned may differ across canvases due to * the following additional conditions:

    * *
  • The window eyepoint policy is RELATIVE_TO_WINDOW or * RELATIVE_TO_SCREEN, in which case the manual eye * position in image plate can be set differently for each * canvas.
  • * *

  • The window eyepoint policy is RELATIVE_TO_FIELD_OF_VIEW * and the view attach policy is NOMINAL_SCREEN, which * decouples the view platform's canvas Z offset from the eyepoint's * canvas Z offset.
  • * *

  • The eyepoint X and Y coordinates are centered in the canvas with a * window eyepoint policy of RELATIVE_TO_FIELD_OF_VIEW * or RELATIVE_TO_WINDOW, and a window movement policy * of VIRTUAL_WORLD centers the view platform's X and Y * coordinates to the middle of the screen.
  • * *

  • Coexistence centering is set false, which allows each canvas and * screen to have a different position with respect to coexistence * coordinates.
* * @param c3d the Canvas3D to use * @param e2vpl the Transform3D to receive the left transform * @param e2vpr the Transform3D to receive the right transform, or null */ public void getEyeToViewPlatform(Canvas3D c3d, Transform3D e2vpl, Transform3D e2vpr) { CanvasInfo ci = updateCache(c3d, "getEyeToViewPlatform", false) ; getEyeToViewPlatform(ci) ; e2vpl.set(ci.eyeToViewPlatform) ; if (ci.useStereo && e2vpr != null) e2vpr.set(ci.rightEyeToViewPlatform) ; } private void getEyeToViewPlatform(CanvasInfo ci) { if (ci.updateEyeToViewPlatform) { if (verbose) System.err.println("updating EyeToViewPlatform") ; if (ci.eyeToViewPlatform == null) ci.eyeToViewPlatform = new Transform3D() ; getEyeToImagePlate(ci) ; getImagePlateToViewPlatform(ci) ; ci.eyeToViewPlatform.mul(ci.plateToViewPlatform, ci.eyeToPlate) ; if (ci.useStereo) { if (ci.rightEyeToViewPlatform == null) ci.rightEyeToViewPlatform = new Transform3D() ; ci.rightEyeToViewPlatform.mul (ci.rightPlateToViewPlatform, ci.rightEyeToPlate) ; } ci.updateEyeToViewPlatform = false ; if (verbose) t3dPrint(ci.eyeToViewPlatform, "eyeToVp") ; } } /** * Gets the current transforms from view platform coordinates to eye * coordinates and copies them into the given Transform3Ds.

* * With a monoscopic canvas the eye transform is copied to the first * argument and the second argument is not used. For a stereo canvas the * first argument receives the left eye transform, and if the second * argument is non-null it receives the right eye transform.

* * This method requires a Canvas3D. The transforms returned may differ * across canvases for all the same reasons discussed in the description * of getEyeToViewPlatform. * * @param c3d the Canvas3D to use * @param vp2el the Transform3D to receive the left transform * @param vp2er the Transform3D to receive the right transform, or null * @see #getEyeToViewPlatform * getEyeToViewPlatform(Canvas3D, Transform3D, Transform3D) */ public void getViewPlatformToEye(Canvas3D c3d, Transform3D vp2el, Transform3D vp2er) { CanvasInfo ci = updateCache(c3d, "getViewPlatformToEye", false) ; getViewPlatformToEye(ci) ; vp2el.set(ci.viewPlatformToEye) ; if (ci.useStereo && vp2er != null) vp2er.set(ci.viewPlatformToRightEye) ; } private void getViewPlatformToEye(CanvasInfo ci) { if (ci.updateViewPlatformToEye) { if (verbose) System.err.println("updating ViewPlatformToEye") ; if (ci.viewPlatformToEye == null) ci.viewPlatformToEye = new Transform3D() ; getEyeToViewPlatform(ci) ; ci.viewPlatformToEye.invert(ci.eyeToViewPlatform) ; if (ci.useStereo) { if (ci.viewPlatformToRightEye == null) ci.viewPlatformToRightEye = new Transform3D() ; ci.viewPlatformToRightEye.invert(ci.rightEyeToViewPlatform) ; } ci.updateViewPlatformToEye = false ; } } /** * Gets the current transforms from eye coordinates to virtual world * coordinates and copies them into the given Transform3Ds.

* * With a monoscopic canvas the eye transform is copied to the first * argument and the second argument is not used. For a stereo canvas the * first argument receives the left eye transform, and if the second * argument is non-null it receives the right eye transform.

* * The View must be attached to a ViewPlatform which is part of a live * scene graph, and the ViewPlatform node must have its * ALLOW_LOCAL_TO_VWORLD_READ capability set.

* * This method requires a Canvas3D. The transforms returned may differ * across canvases for all the same reasons discussed in the description * of getEyeToViewPlatform. * * @param c3d the Canvas3D to use * @param e2vwl the Transform3D to receive the left transform * @param e2vwr the Transform3D to receive the right transform, or null * @see #getEyeToViewPlatform * getEyeToViewPlatform(Canvas3D, Transform3D, Transform3D) */ public void getEyeToVworld(Canvas3D c3d, Transform3D e2vwl, Transform3D e2vwr) { CanvasInfo ci = updateCache(c3d, "getEyeToVworld", true) ; getEyeToVworld(ci) ; e2vwl.set(ci.eyeToVworld) ; if (ci.useStereo && e2vwr != null) e2vwr.set(ci.rightEyeToVworld) ; } private void getEyeToVworld(CanvasInfo ci) { if (ci.updateEyeToVworld) { if (verbose) System.err.println("updating EyeToVworld") ; if (ci.eyeToVworld == null) ci.eyeToVworld = new Transform3D() ; getEyeToViewPlatform(ci) ; ci.eyeToVworld.mul (vpi.viewPlatformToVworld, ci.eyeToViewPlatform) ; if (ci.useStereo) { if (ci.rightEyeToVworld == null) ci.rightEyeToVworld = new Transform3D() ; ci.rightEyeToVworld.mul (vpi.viewPlatformToVworld, ci.rightEyeToViewPlatform) ; } ci.updateEyeToVworld = false ; } } /** * Gets the transforms from eye coordinates to clipping coordinates * and copies them into the given Transform3Ds. These transforms take * a viewing volume bounded by the physical canvas edges and the * physical front and back clip planes and project it into a range * bound to [-1.0 .. +1.0] on each of the X, Y, and Z axes. If a * perspective projection has been specified then the physical image * plate eye location defines the apex of a viewing frustum; * otherwise, the orientation of the image plate determines the * direction of a parallel projection.

* * With a monoscopic canvas the projection transform is copied to the * first argument and the second argument is not used. For a stereo * canvas the first argument receives the left projection transform, * and if the second argument is non-null it receives the right * projection transform.

* * If either of the clip policies VIRTUAL_EYE or * VIRTUAL_SCREEN are used, then the View should be attached * to a ViewPlatform that is part of a live scene graph and that has its * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a * scale factor of 1.0 will be used for the scale factor from virtual * world units to view platform units. * * @param c3d the Canvas3D to use * @param e2ccl the Transform3D to receive left transform * @param e2ccr the Transform3D to receive right transform, or null */ public void getProjection(Canvas3D c3d, Transform3D e2ccl, Transform3D e2ccr) { CanvasInfo ci = updateCache(c3d, "getProjection", true) ; getProjection(ci) ; e2ccl.set(ci.projection) ; if (ci.useStereo && e2ccr != null) e2ccr.set(ci.rightProjection) ; } private void getProjection(CanvasInfo ci) { if (ci.updateProjection) { if (verbose) System.err.println("updating Projection") ; if (ci.projection == null) ci.projection = new Transform3D() ; getEyeToImagePlate(ci) ; getClipDistances(ci) ; // Note: core Java 3D code insists that the back clip plane // relative to the image plate must be the same left back clip // distance for both the left and right eye. Not sure why this // should be, but the same is done here for compatibility. double backClip = getBackClip(ci, ci.eyeInPlate) ; computeProjection(ci, ci.eyeInPlate, getFrontClip(ci, ci.eyeInPlate), backClip, ci.projection) ; if (ci.useStereo) { if (ci.rightProjection == null) ci.rightProjection = new Transform3D() ; computeProjection(ci, ci.rightEyeInPlate, getFrontClip(ci, ci.rightEyeInPlate), backClip, ci.rightProjection) ; } ci.updateProjection = false ; if (verbose) t3dPrint(ci.projection, "projection") ; } } /** * Gets the transforms from clipping coordinates to eye coordinates * and copies them into the given Transform3Ds. These transforms take * the clip space volume bounded by the range [-1.0 .. + 1.0] on each * of the X, Y, and Z and project it into eye coordinates.

* * With a monoscopic canvas the projection transform is copied to the * first argument and the second argument is not used. For a stereo * canvas the first argument receives the left projection transform, and * if the second argument is non-null it receives the right projection * transform.

* * If either of the clip policies VIRTUAL_EYE or * VIRTUAL_SCREEN are used, then the View should be attached * to a ViewPlatform that is part of a live scene graph and that has its * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a * scale factor of 1.0 will be used for the scale factor from virtual * world units to view platform units. * * @param c3d the Canvas3D to use * @param cc2el the Transform3D to receive left transform * @param cc2er the Transform3D to receive right transform, or null */ public void getInverseProjection(Canvas3D c3d, Transform3D cc2el, Transform3D cc2er) { CanvasInfo ci = updateCache(c3d, "getInverseProjection", true) ; getInverseProjection(ci) ; cc2el.set(ci.inverseProjection) ; if (ci.useStereo && cc2er != null) cc2er.set(ci.inverseRightProjection) ; } private void getInverseProjection(CanvasInfo ci) { if (ci.updateInverseProjection) { if (verbose) System.err.println("updating InverseProjection") ; if (ci.inverseProjection == null) ci.inverseProjection = new Transform3D() ; getProjection(ci) ; ci.inverseProjection.invert(ci.projection) ; if (ci.useStereo) { if (ci.inverseRightProjection == null) ci.inverseRightProjection = new Transform3D() ; ci.inverseRightProjection.invert(ci.rightProjection) ; } ci.updateInverseProjection = false ; } } /** * Gets the transforms from clipping coordinates to view platform * coordinates and copies them into the given Transform3Ds. These * transforms take the clip space volume bounded by the range * [-1.0 .. +1.0] on each of the X, Y, and Z axes and project into * the view platform coordinate system.

* * With a monoscopic canvas the projection transform is copied to the * first argument and the second argument is not used. For a stereo * canvas the first argument receives the left projection transform, and * if the second argument is non-null it receives the right projection * transform.

* * If either of the clip policies VIRTUAL_EYE or * VIRTUAL_SCREEN are used, then the View should be attached * to a ViewPlatform that is part of a live scene graph and that has its * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a * scale factor of 1.0 will be used for the scale factor from virtual * world units to view platform units. * * @param c3d the Canvas3D to use * @param cc2vpl the Transform3D to receive left transform * @param cc2vpr the Transform3D to receive right transform, or null */ public void getInverseViewPlatformProjection(Canvas3D c3d, Transform3D cc2vpl, Transform3D cc2vpr) { CanvasInfo ci = updateCache (c3d, "getInverseViewPlatformProjection", true) ; getInverseViewPlatformProjection(ci) ; cc2vpl.set(ci.inverseViewPlatformProjection) ; if (ci.useStereo & cc2vpr != null) cc2vpr.set(ci.inverseViewPlatformRightProjection) ; } private void getInverseViewPlatformProjection(CanvasInfo ci) { if (ci.updateInverseViewPlatformProjection) { if (verbose) System.err.println("updating InverseVpProjection") ; if (ci.inverseViewPlatformProjection == null) ci.inverseViewPlatformProjection = new Transform3D() ; getInverseProjection(ci) ; getEyeToViewPlatform(ci) ; ci.inverseViewPlatformProjection.mul (ci.eyeToViewPlatform, ci.inverseProjection) ; if (ci.useStereo) { if (ci.inverseViewPlatformRightProjection == null) ci.inverseViewPlatformRightProjection = new Transform3D() ; ci.inverseViewPlatformRightProjection.mul (ci.rightEyeToViewPlatform, ci.inverseRightProjection) ; } ci.updateInverseVworldProjection = false ; } } /** * Gets the transforms from clipping coordinates to virtual world * coordinates and copies them into the given Transform3Ds. These * transforms take the clip space volume bounded by the range * [-1.0 .. +1.0] on each of the X, Y, and Z axes and project into * the virtual world.

* * With a monoscopic canvas the projection transform is copied to the * first argument and the second argument is not used. For a stereo * canvas the first argument receives the left projection transform, and * if the second argument is non-null it receives the right projection * transform.

* * The View must be attached to a ViewPlatform which is part of a live * scene graph, and the ViewPlatform node must have its * ALLOW_LOCAL_TO_VWORLD_READ capability set. * * @param c3d the Canvas3D to use * @param cc2vwl the Transform3D to receive left transform * @param cc2vwr the Transform3D to receive right transform, or null */ public void getInverseVworldProjection(Canvas3D c3d, Transform3D cc2vwl, Transform3D cc2vwr) { CanvasInfo ci = updateCache(c3d, "getInverseVworldProjection", true) ; getInverseVworldProjection(ci) ; cc2vwl.set(ci.inverseVworldProjection) ; if (ci.useStereo & cc2vwr != null) cc2vwr.set(ci.inverseVworldRightProjection) ; } private void getInverseVworldProjection(CanvasInfo ci) { if (ci.updateInverseVworldProjection) { if (verbose) System.err.println("updating InverseVwProjection") ; if (ci.inverseVworldProjection == null) ci.inverseVworldProjection = new Transform3D() ; getInverseViewPlatformProjection(ci) ; ci.inverseVworldProjection.mul (vpi.viewPlatformToVworld, ci.inverseViewPlatformProjection) ; if (ci.useStereo) { if (ci.inverseVworldRightProjection == null) ci.inverseVworldRightProjection = new Transform3D() ; ci.inverseVworldRightProjection.mul (vpi.viewPlatformToVworld, ci.inverseViewPlatformRightProjection) ; } ci.updateInverseVworldProjection = false ; } } // // Compute a projection matrix from the given eye position in image plate, // the front and back clip Z positions in image plate, and the current // canvas position in image plate. // private void computeProjection(CanvasInfo ci, Point3d eye, double front, double back, Transform3D p) { // Convert everything to eye coordinates. double lx = ci.canvasX - eye.x ; // left (low x) double ly = ci.canvasY - eye.y ; // bottom (low y) double hx = (ci.canvasX+ci.canvasWidth) - eye.x ; // right (high x) double hy = (ci.canvasY+ci.canvasHeight) - eye.y ; // top (high y) double nz = front - eye.z ; // front (near z) double fz = back - eye.z ; // back (far z) double iz = -eye.z ; // plate (image z) if (projectionPolicy == View.PERSPECTIVE_PROJECTION) computePerspectiveProjection(lx, ly, hx, hy, iz, nz, fz, m16d) ; else computeParallelProjection(lx, ly, hx, hy, nz, fz, m16d) ; p.set(m16d) ; } // // Compute a perspective projection from the given eye-space bounds. // private void computePerspectiveProjection(double lx, double ly, double hx, double hy, double iz, double nz, double fz, double[] m) { // // We first derive the X and Y projection components without regard // for Z scaling. The Z scaling or perspective depth is handled by // matrix elements expressed solely in terms of the near and far clip // planes. // // Since the eye is at the origin, the projector for any point V in // eye space is just V. Any point along this ray can be expressed in // parametric form as P = tV. To find the projection onto the plane // containing the canvas, find t such that P.z = iz; ie, t = iz/V.z. // The projection P is thus [V.x*iz/V.z, V.y*iz/V.z, iz]. // // This projection can expressed as the following matrix equation: // // -iz 0 0 0 V.x // 0 -iz 0 0 X V.y // 0 0 -iz 0 V.z // 0 0 -1 0 1 {matrix 1} // // where the matrix elements have been negated so that w is positive. // This is mostly by convention, although some hardware won't handle // clipping in the -w half-space. // // After the point has been projected to the image plate, the // canvas bounds need to be mapped to the [-1..1] of Java 3D's // clipping space. The scale factor for X is thus 2/(hx - lx); adding // the translation results in (V.x - lx)(2/(hx - lx)) - 1, which after // some algebra can be confirmed to the same as the following // canonical scale/offset form: // // V.x*2/(hx - lx) - (hx + lx)/(hx - lx) // // Similarly for Y: // // V.y*2/(hy - ly) - (hy + ly)/(hy - ly) // // If we set idx = 1/(hx - lx) and idy = 1/(hy - ly), then we get: // // 2*V.x*idx - (hx + lx)idx // 2*V.y*idy - (hy + ly)idy // // These scales and offsets are represented by the following matrix: // // 2*idx 0 0 -(hx + lx)*idx // 0 2*idy 0 -(hy + ly)*idy // 0 0 1 0 // 0 0 0 1 {matrix 2} // // The result after concatenating the projection transform // ({matrix 2} X {matrix 1}): // // -2*iz*idx 0 (hx + lx)*idx 0 // 0 -2*iz*idy (hy + ly)*idy 0 // 0 0 -iz {a} 0 {b} // 0 0 -1 0 {matrix 3} // // The Z scaling is handled by m[10] ("a") and m[11] ("b"), which must // map the range [front..back] to [1..-1] in clipping space. If ze is // the Z coordinate in eye space, and zc is the Z coordinate in // clipping space after division by w, then from {matrix 3}: // // zc = (a*ze + b)/-ze = -(a + b/ze) // // We want this to map to +1 when ze is at the near clip plane, and // to -1 when ze is at the far clip plane: // // -(a + b/nz) = +1 // -(a + b/fz) = -1 // // Solving results in: // // a = -(nz + fz)/(nz - fz) // b = (2*nz*fz)/(nz - fz). // // NOTE: this produces a perspective transform that has matrix // components with a different scale than the matrix computed by the // Java 3D core. They do in fact effect the equivalent clipping in 4D // homogeneous coordinates and project to the same 3D Euclidean // coordinates. m[14] is always -1 in our derivation above. If the // matrix components produced by Java 3D core are divided by its value // of -m[14], then both matrices are the same. // double idx = 1.0 / (hx - lx) ; double idy = 1.0 / (hy - ly) ; double idz = 1.0 / (nz - fz) ; m[0] = -2.0 * iz * idx ; m[5] = -2.0 * iz * idy ; m[2] = (hx + lx) * idx ; m[6] = (hy + ly) * idy ; m[10] = -(nz + fz) * idz ; m[11] = 2.0 * fz * nz * idz ; m[14] = -1.0 ; m[1] = m[3] = m[4] = m[7] = m[8] = m[9] = m[12] = m[13] = m[15] = 0.0 ; } // // Compute a parallel projection from the given eye-space bounds. // private void computeParallelProjection(double lx, double ly, double hx, double hy, double nz, double fz, double[] m) { // // A parallel projection in eye space just involves scales and offsets // with no w division. We can use {matrix 2} for the X and Y scales // and offsets and then use a linear mapping of the front and back // clip distances to the [1..-1] Z clip range. // double idx = 1.0 / (hx - lx) ; double idy = 1.0 / (hy - ly) ; double idz = 1.0 / (nz - fz) ; m[0] = 2.0 * idx ; m[5] = 2.0 * idy ; m[10] = 2.0 * idz ; m[3] = -(hx + lx) * idx ; m[7] = -(hy + ly) * idy ; m[11] = -(nz + fz) * idz ; m[15] = 1.0 ; m[1] = m[2] = m[4] = m[6] = m[8] = m[9] = m[12] = m[13] = m[14] = 0.0 ; } // // Get front clip plane Z coordinate in image plate space. // private double getFrontClip(CanvasInfo ci, Point3d eye) { if (frontClipPolicy == View.PHYSICAL_EYE || frontClipPolicy == View.VIRTUAL_EYE) { return eye.z - ci.frontClipDistance ; } else { return - ci.frontClipDistance ; } } // // Get back clip plane Z coordinate in image plate space. // private double getBackClip(CanvasInfo ci, Point3d eye) { // // Note: Clip node status is unavailable here. If a clip node is // active in the scene graph, it should override the view's back // clip plane. // if (backClipPolicy == View.PHYSICAL_EYE || backClipPolicy == View.VIRTUAL_EYE) { return eye.z - ci.backClipDistance ; } else { return -ci.backClipDistance ; } } // // Compute clip distance scale. // private double getClipScale(CanvasInfo ci, int clipPolicy) { if (clipPolicy == View.VIRTUAL_EYE || clipPolicy == View.VIRTUAL_SCREEN) { getScreenScale(ci) ; if (resizePolicy == View.PHYSICAL_WORLD) return vpi.vworldToViewPlatformScale * ci.screenScale * ci.windowScale ; else return vpi.vworldToViewPlatformScale * ci.screenScale ; } else { if (resizePolicy == View.PHYSICAL_WORLD) return ci.windowScale ; // see below else return 1.0 ; } } /** * Gets the front clip distance scaled to physical meters. This is useful * for ensuring that objects positioned relative to a physical coordinate * system (such as eye, image plate, or coexistence) will be within the * viewable Z depth. This distance will be relative to either the eye or * the image plate depending upon the front clip policy.

* * Note that this is not necessarily the clip distance as set by * setFrontClipDistance, even when the front clip policy * is PHYSICAL_SCREEN or PHYSICAL_EYE. If * the window resize policy is PHYSICAL_WORLD, then physical * clip distances as specified are in fact scaled by the ratio of the * window width to the screen width. The Java 3D view model does this * to prevent the physical clip planes from moving with respect to the * virtual world when the window is resized.

* * If either of the clip policies VIRTUAL_EYE or * VIRTUAL_SCREEN are used, then the View should be attached * to a ViewPlatform that is part of a live scene graph and that has its * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a * scale factor of 1.0 will be used for the scale factor from virtual * world units to view platform units. * * @param c3d the Canvas3D to use * @return the physical front clip distance */ public double getPhysicalFrontClipDistance(Canvas3D c3d) { CanvasInfo ci = updateCache (c3d, "getPhysicalFrontClipDistance", true) ; getClipDistances(ci) ; return ci.frontClipDistance ; } /** * Gets the back clip distance scaled to physical meters. This is useful * for ensuring that objects positioned relative to a physical coordinate * system (such as eye, image plate, or coexistence) will be within the * viewable Z depth. This distance will be relative to either the eye or * the image plate depending upon the back clip policy.

* * Note that this is not necessarily the clip distance as set by * setBackClipDistance, even when the back clip policy * is PHYSICAL_SCREEN or PHYSICAL_EYE. If * the window resize policy is PHYSICAL_WORLD, then physical * clip distances as specified are in fact scaled by the ratio of the * window width to the screen width. The Java 3D view model does this * to prevent the physical clip planes from moving with respect to the * virtual world when the window is resized.

* * If either of the clip policies VIRTUAL_EYE or * VIRTUAL_SCREEN are used, then the View should be attached * to a ViewPlatform that is part of a live scene graph and that has its * ALLOW_LOCAL_TO_VWORLD_READ capability set; otherwise, a * scale factor of 1.0 will be used for the scale factor from virtual * world units to view platform units. * * @param c3d the Canvas3D to use * @return the physical back clip distance */ public double getPhysicalBackClipDistance(Canvas3D c3d) { CanvasInfo ci = updateCache(c3d, "getPhysicalBackClipDistance", true) ; getClipDistances(ci) ; return ci.backClipDistance ; } private void getClipDistances(CanvasInfo ci) { if (ci.updateClipDistances) { if (verbose) System.err.println("updating clip distances") ; ci.frontClipDistance = view.getFrontClipDistance() * getClipScale(ci, frontClipPolicy) ; ci.backClipDistance = view.getBackClipDistance() * getClipScale(ci, backClipPolicy) ; ci.updateClipDistances = false ; if (verbose) { System.err.println (" front clip distance " + ci.frontClipDistance) ; System.err.println (" back clip distance " + ci.backClipDistance) ; } } } private void getScreenScale(CanvasInfo ci) { if (ci.updateScreenScale) { if (verbose) System.err.println("updating screen scale") ; if (scalePolicy == View.SCALE_SCREEN_SIZE) ci.screenScale = ci.si.screenWidth / 2.0 ; else ci.screenScale = view.getScreenScale() ; ci.updateScreenScale = false ; if (verbose) System.err.println("screen scale " + ci.screenScale) ; } } /** * Gets the scale factor from physical meters to view platform units.

* * This method requires a Canvas3D. A different scale may be returned * for each canvas in the view if any of the following apply:

    * *
  • The window resize policy is PHYSICAL_WORLD, which * alters the scale depending upon the width of the canvas.
  • * *

  • The screen scale policy is SCALE_SCREEN_SIZE, * which alters the scale depending upon the width of the screen * associated with the canvas.
* * @param c3d the Canvas3D to use * @return the physical to view platform scale */ public double getPhysicalToViewPlatformScale(Canvas3D c3d) { CanvasInfo ci = updateCache (c3d, "getPhysicalToViewPlatformScale", false) ; getPhysicalToViewPlatformScale(ci) ; return ci.physicalToVpScale ; } private void getPhysicalToViewPlatformScale(CanvasInfo ci) { if (ci.updatePhysicalToVpScale) { if (verbose) System.err.println("updating PhysicalToVp scale") ; getScreenScale(ci) ; if (resizePolicy == View.PHYSICAL_WORLD) ci.physicalToVpScale = 1.0/(ci.screenScale * ci.windowScale) ; else ci.physicalToVpScale = 1.0/ci.screenScale ; ci.updatePhysicalToVpScale = false ; if (verbose) System.err.println("PhysicalToVp scale " + ci.physicalToVpScale) ; } } /** * Gets the scale factor from physical meters to virtual units.

* * This method requires a Canvas3D. A different scale may be returned * across canvases for the same reasons as discussed in the description of * getPhysicalToViewPlatformScale.

* * The View must be attached to a ViewPlatform which is part of a live * scene graph, and the ViewPlatform node must have its * ALLOW_LOCAL_TO_VWORLD_READ capability set. * * @param c3d the Canvas3D to use * @return the physical to virtual scale * @see #getPhysicalToViewPlatformScale * getPhysicalToViewPlatformScale(Canvas3D) */ public double getPhysicalToVirtualScale(Canvas3D c3d) { CanvasInfo ci = updateCache(c3d, "getPhysicalToVirtualScale", true) ; getPhysicalToVirtualScale(ci) ; return ci.physicalToVirtualScale ; } private void getPhysicalToVirtualScale(CanvasInfo ci) { if (ci.updatePhysicalToVirtualScale) { if (verbose) System.err.println("updating PhysicalToVirtual scale") ; getPhysicalToViewPlatformScale(ci) ; ci.physicalToVirtualScale = ci.physicalToVpScale / vpi.vworldToViewPlatformScale ; ci.updatePhysicalToVirtualScale = false ; if (verbose) System.err.println("PhysicalToVirtual scale " + ci.physicalToVirtualScale) ; } } /** * Gets the width of the specified canvas scaled to physical meters. This * is derived from the physical screen width as reported by the Screen3D * associated with the canvas. If the screen width is not explicitly set * using the setPhysicalScreenWidth method of Screen3D, then * Java 3D will derive the screen width based on a screen resolution of 90 * pixels/inch. * * @param c3d the Canvas3D to use * @return the width of the canvas scaled to physical meters */ public double getPhysicalWidth(Canvas3D c3d) { CanvasInfo ci = updateCache(c3d, "getPhysicalWidth", false) ; return ci.canvasWidth ; } /** * Gets the height of the specified canvas scaled to physical meters. This * is derived from the physical screen height as reported by the Screen3D * associated with the canvas. If the screen height is not explicitly set * using the setPhysicalScreenHeight method of Screen3D, then * Java 3D will derive the screen height based on a screen resolution of 90 * pixels/inch. * * @param c3d the Canvas3D to use * @return the height of the canvas scaled to physical meters */ public double getPhysicalHeight(Canvas3D c3d) { CanvasInfo ci = updateCache(c3d, "getPhysicalHeight", false) ; return ci.canvasHeight ; } /** * Gets the location of the specified canvas relative to the image plate * origin. This is derived from the physical screen parameters as * reported by the Screen3D associated with the canvas. If the screen * width and height are not explicitly set in Screen3D, then Java 3D will * derive those screen parameters based on a screen resolution of 90 * pixels/inch. * * @param c3d the Canvas3D to use * @param location the output position, in meters, of the lower-left * corner of the canvas relative to the image plate lower-left corner; Z * is always 0.0 */ public void getPhysicalLocation(Canvas3D c3d, Point3d location) { CanvasInfo ci = updateCache(c3d, "getPhysicalLocation", false) ; location.set(ci.canvasX, ci.canvasY, 0.0) ; } /** * Gets the location of the AWT pixel value and copies it into the * specified Point3d. * * @param c3d the Canvas3D to use * @param x the X coordinate of the pixel relative to the upper-left * corner of the canvas * @param y the Y coordinate of the pixel relative to the upper-left * corner of the canvas * @param location the output position, in meters, relative to the * lower-left corner of the image plate; Z is always 0.0 */ public void getPixelLocationInImagePlate(Canvas3D c3d, int x, int y, Point3d location) { CanvasInfo ci = updateCache (c3d, "getPixelLocationInImagePlate", false) ; location.set(ci.canvasX + ((double)x * ci.si.metersPerPixelX), ci.canvasY - ((double)y * ci.si.metersPerPixelY) + ci.canvasHeight, 0.0) ; } /** * Gets a read from the specified sensor and transforms it to virtual * world coordinates. The View must be attached to a ViewPlatform which * is part of a live scene graph, and the ViewPlatform node must have its * ALLOW_LOCAL_TO_VWORLD_READ capability set.

* * This method requires a Canvas3D. The returned transform may differ * across canvases for the same reasons as discussed in the description of * getViewPlatformToCoexistence. * * @param sensor the Sensor instance to read * @param s2vw the output transform * @see #getViewPlatformToCoexistence * getViewPlatformToCoexistence(Canvas3D, Transform3D) */ public void getSensorToVworld(Canvas3D c3d, Sensor sensor, Transform3D s2vw) { CanvasInfo ci = updateCache(c3d, "getSensorToVworld", true) ; getTrackerBaseToVworld(ci) ; sensor.getRead(s2vw) ; s2vw.mul(ci.trackerBaseToVworld, s2vw) ; } /** * Gets the transform from tracker base coordinates to view platform * coordinates and copies it into the specified Transform3D.

* * This method requires a Canvas3D. The returned transform may differ * across canvases for the same reasons as discussed in the description of * getViewPlatformToCoexistence. * * @param c3d the Canvas3D to use * @param tb2vp the output transform * @see #getViewPlatformToCoexistence * getViewPlatformToCoexistence(Canvas3D, Transform3D) */ public void getTrackerBaseToViewPlatform(Canvas3D c3d, Transform3D tb2vp) { CanvasInfo ci = updateCache (c3d, "getTrackerBaseToViewPlatform", false) ; getTrackerBaseToViewPlatform(ci) ; tb2vp.set(ci.trackerBaseToViewPlatform) ; } private void getTrackerBaseToViewPlatform(CanvasInfo ci) { if (ci.updateTrackerBaseToViewPlatform) { if (verbose) System.err.println("updating TrackerBaseToVp") ; if (ci.trackerBaseToViewPlatform == null) ci.trackerBaseToViewPlatform = new Transform3D() ; getViewPlatformToCoexistence(ci) ; ci.trackerBaseToViewPlatform.mul(coeToTrackerBase, ci.viewPlatformToCoe) ; ci.trackerBaseToViewPlatform.invert() ; ci.updateTrackerBaseToViewPlatform = false ; if (verbose) t3dPrint(ci.trackerBaseToViewPlatform, "TrackerBaseToViewPlatform") ; } } /** * Gets the transform from tracker base coordinates to virtual world * coordinates and copies it into the specified Transform3D. The View * must be attached to a ViewPlatform which is part of a live scene graph, * and the ViewPlatform node must have its * ALLOW_LOCAL_TO_VWORLD_READ capability set.

* * This method requires a Canvas3D. The returned transform may differ * across canvases for the same reasons as discussed in the description of * getViewPlatformToCoexistence. * * @param c3d the Canvas3D to use * @param tb2vw the output transform * @see #getViewPlatformToCoexistence * getViewPlatformToCoexistence(Canvas3D, Transform3D) */ public void getTrackerBaseToVworld(Canvas3D c3d, Transform3D tb2vw) { CanvasInfo ci = updateCache(c3d, "getTrackerBaseToVworld", true) ; getTrackerBaseToVworld(ci) ; tb2vw.set(ci.trackerBaseToVworld) ; } private void getTrackerBaseToVworld(CanvasInfo ci) { if (ci.updateTrackerBaseToVworld) { if (verbose) System.err.println("updating TrackerBaseToVworld") ; if (ci.trackerBaseToVworld == null) ci.trackerBaseToVworld = new Transform3D() ; // // We compute trackerBaseToViewPlatform and compose with // viewPlatformToVworld instead of computing imagePlateToVworld // and composing with trackerBaseToImagePlate. That way it works // with HMD and avoids the issue of choosing the left image plate // or right image plate transform. // getTrackerBaseToViewPlatform(ci) ; ci.trackerBaseToVworld.mul(vpi.viewPlatformToVworld, ci.trackerBaseToViewPlatform) ; ci.updateTrackerBaseToVworld = false ; } } /** * Release all static memory references held by ViewInfo, if any. These * are the Screen3D and ViewPlatform maps shared by all existing ViewInfo * instances if they're not provided by a constructor. Releasing the * screen references effectively releases all canvas references in all * ViewInfo instances as well.

* * It is safe to continue using existing ViewInfo instances after calling * this method; the data in the released maps will be re-derived as * needed. */ public static synchronized void clear() { Iterator i = staticVpMap.values().iterator() ; while (i.hasNext()) ((ViewPlatformInfo)i.next()).clear() ; staticVpMap.clear() ; i = staticSiMap.values().iterator() ; while (i.hasNext()) ((ScreenInfo)i.next()).clear() ; staticSiMap.clear() ; } /** * Arrange for an update of cached screen parameters. If automatic update * has not been enabled, then this method should be called if any of the * attributes of the Screen3D have changed. This method should also be * called if the screen changes pixel resolution. * * @param s3d the Screen3D to update */ public void updateScreen(Screen3D s3d) { if (verbose) System.err.println("updateScreen") ; ScreenInfo si = (ScreenInfo)screenMap.get(s3d) ; if (si != null) si.updateScreen = true ; } /** * Arrange for an update of cached canvas parameters. If automatic update * has not been enabled, then this method should be called if any of the * attributes of the Canvas3D have changed. These attributes include the * canvas position and size, but do not include the attributes of * the associated Screen3D, which are cached separately. * * @param c3d the Canvas3D to update */ public void updateCanvas(Canvas3D c3d) { if (verbose) System.err.println("updateCanvas") ; CanvasInfo ci = (CanvasInfo)canvasMap.get(c3d) ; if (ci != null) ci.updateCanvas = true ; } /** * Arrange for an update of cached view parameters. If automatic update * has not been enabled for the View, then this method should be called if * any of the attributes of the View associated with this object have * changed.

* * These do not include the attributes of the existing Canvas3D or * Screen3D components of the View, but do include the attributes of all * other components such as the PhysicalEnvironment and PhysicalBody, and * all attributes of the attached ViewPlatform except for its * localToVworld transform. The screen and canvas components * as well as the ViewPlatform's localToVworld are cached * separately.

* * This method should also be called if the ViewPlatform is replaced with * another using the View's attachViewPlatform method, or if * any of the setCanvas3D, addCanvas3D, * insertCanvas3D, removeCanvas3D, or * removeAllCanvas3Ds methods of View are called to change * the View's canvas list.

* * Calling this method causes most transforms to be re-derived. It should * be used only when necessary. */ public void updateView() { if (verbose) System.err.println("updateView") ; this.updateView = true ; } /** * Arrange for an update of the cached head position if head tracking is * enabled. If automatic update has not enabled for the head position, * then this method should be called anytime a new head position is to be * read. */ public void updateHead() { if (verbose) System.err.println("updateHead") ; this.updateHead = true ; } /** * Arrange for an update of the cached localToVworld * transform of the view platform. If automatic update has not been * enabled for this transform, then this method should be called anytime * the view platform has been repositioned in the virtual world and a * transform involving virtual world coordinates is desired.

* * The View must be attached to a ViewPlatform which is part of a live * scene graph, and the ViewPlatform node must have its * ALLOW_LOCAL_TO_VWORLD_READ capability set. */ public void updateViewPlatform() { if (verbose) System.err.println("updateViewPlatform") ; vpi.updateViewPlatformToVworld = true ; } // // Set cache update bits based on auto update flags. // VIEW_AUTO_UPDATE is handled in updateCache(). // private void getAutoUpdate(CanvasInfo ci) { if ((autoUpdateFlags & SCREEN_AUTO_UPDATE) != 0) ci.si.updateScreen = true ; if ((autoUpdateFlags & CANVAS_AUTO_UPDATE) != 0) ci.updateCanvas = true ; if ((autoUpdateFlags & PLATFORM_AUTO_UPDATE) != 0) vpi.updateViewPlatformToVworld = true ; if ((autoUpdateFlags & HEAD_AUTO_UPDATE) != 0) this.updateHead = true ; } // // Update any changed cached data. This takes a Canvas3D instance. The // cache mechanism could have used a Canvas3D index into the View instead, // but the direct reference is probably more convenient for applications. // private CanvasInfo updateCache(Canvas3D c3d, String name, boolean vworld) { if (verbose) { System.err.println("updateCache: " + name + " in " + hashCode()) ; System.err.println(" canvas " + c3d.hashCode()) ; } // The View may have had Canvas3D instances added or removed, or may // have been attached to a different ViewPlatform, so update the view // before anything else. if (updateView || (autoUpdateFlags & VIEW_AUTO_UPDATE) != 0) getViewInfo() ; // Now get the CanvasInfo to update. CanvasInfo ci = (CanvasInfo)canvasMap.get(c3d) ; if (ci == null) throw new IllegalArgumentException( "Specified Canvas3D is not a component of the View") ; // Check rest of autoUpdateFlags. if (autoUpdate) getAutoUpdate(ci) ; // Update the screen, canvas, view platform, and head caches. if (ci.si.updateScreen) ci.si.getScreenInfo() ; if (ci.updateCanvas) ci.getCanvasInfo() ; if (vworld && vpi.updateViewPlatformToVworld) vpi.getViewPlatformToVworld() ; if (useTracking && updateHead) getHeadInfo() ; // Return the CanvasInfo instance. return ci ; } // // Get physical view parameters and derived data. This is a fairly // heavyweight method -- everything gets marked for update since we don't // currently track changes in individual view attributes. Fortunately // there shouldn't be a need to call it very often. // private void getViewInfo() { if (verbose) System.err.println(" getViewInfo") ; // Check if an update of the Canvas3D collection is needed. if (this.canvasCount != view.numCanvas3Ds()) { this.canvasCount = view.numCanvas3Ds() ; getCanvases() ; } else { for (int i = 0 ; i < canvasCount ; i++) { if (canvasMap.get(view.getCanvas3D(i)) != canvasInfo[i]) { getCanvases() ; break ; } } } // Update the ViewPlatform. getViewPlatform() ; // Update the PhysicalBody and PhysicalEnvironment. this.body = view.getPhysicalBody() ; this.env = view.getPhysicalEnvironment() ; // Use the result of the possibly overridden method useHeadTracking() // to determine if head tracking is to be used within ViewInfo. this.useTracking = useHeadTracking() ; // Get the head tracker only if really available. if (view.getTrackingEnable() && env.getTrackingAvailable()) { int headIndex = env.getHeadIndex() ; this.headTracker = env.getSensor(headIndex) ; } // Get the new policies and update data derived from them. this.viewPolicy = view.getViewPolicy() ; this.projectionPolicy = view.getProjectionPolicy() ; this.resizePolicy = view.getWindowResizePolicy() ; this.movementPolicy = view.getWindowMovementPolicy() ; this.eyePolicy = view.getWindowEyepointPolicy() ; this.scalePolicy = view.getScreenScalePolicy() ; this.backClipPolicy = view.getBackClipPolicy() ; this.frontClipPolicy = view.getFrontClipPolicy() ; if (useTracking || viewPolicy == View.HMD_VIEW) { if (this.headToHeadTracker == null) this.headToHeadTracker = new Transform3D() ; if (this.headTrackerToTrackerBase == null) this.headTrackerToTrackerBase = new Transform3D() ; if (viewPolicy == View.HMD_VIEW) { if (this.trackerBaseToHeadTracker == null) this.trackerBaseToHeadTracker = new Transform3D() ; if (this.coeToHeadTracker == null) this.coeToHeadTracker = new Transform3D() ; } else { if (this.headToTrackerBase == null) this.headToTrackerBase = new Transform3D() ; } body.getLeftEyePosition(this.leftEyeInHead) ; body.getRightEyePosition(this.rightEyeInHead) ; body.getHeadToHeadTracker(this.headToHeadTracker) ; if (verbose) { System.err.println(" leftEyeInHead " + leftEyeInHead) ; System.err.println(" rightEyeInHead " + rightEyeInHead) ; t3dPrint(headToHeadTracker, " headToHeadTracker") ; } } if (eyePolicy == View.RELATIVE_TO_WINDOW || eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) { body.getLeftEyePosition(this.leftEyeInHead) ; body.getRightEyePosition(this.rightEyeInHead) ; if (verbose) { System.err.println(" leftEyeInHead " + leftEyeInHead) ; System.err.println(" rightEyeInHead " + rightEyeInHead) ; } } if ((env.getCoexistenceCenterInPworldPolicy() != View.NOMINAL_SCREEN) || (viewPolicy == View.HMD_VIEW)) this.coeCentering = false ; else this.coeCentering = view.getCoexistenceCenteringEnable() ; if (!coeCentering || useTracking) { if (this.coeToTrackerBase == null) this.coeToTrackerBase = new Transform3D() ; env.getCoexistenceToTrackerBase(this.coeToTrackerBase) ; if (verbose) t3dPrint(coeToTrackerBase, " coeToTrackerBase") ; } if (backClipPolicy == View.VIRTUAL_EYE || backClipPolicy == View.VIRTUAL_SCREEN || frontClipPolicy == View.VIRTUAL_EYE || frontClipPolicy == View.VIRTUAL_SCREEN) { this.clipVirtual = true ; } else { this.clipVirtual = false ; } // Propagate view updates to each canvas. for (int i = 0 ; i < canvasCount ; i++) this.canvasInfo[i].updateViewDependencies() ; this.updateView = false ; if (verbose) { System.err.println(" tracking " + useTracking) ; System.err.println(" coeCentering " + coeCentering) ; System.err.println(" clipVirtual " + clipVirtual) ; } } // // Each view can have multiple canvases, each with an associated screen. // Each canvas is associated with only one view. Each screen can have // multiple canvases that are used across multiple views. We rebuild the // canvas info instead of trying to figure out what canvases have been // added or removed from the view. // private void getCanvases() { if (this.canvasInfo.length < canvasCount) { this.canvasInfo = new CanvasInfo[canvasCount] ; } for (int i = 0 ; i < canvasCount ; i++) { Canvas3D c3d = view.getCanvas3D(i) ; Screen3D s3d = c3d.getScreen3D() ; // Check if we have a new screen. ScreenInfo si = (ScreenInfo)screenMap.get(s3d) ; if (si == null) { si = new ScreenInfo(s3d, c3d.getGraphicsConfiguration()) ; screenMap.put(s3d, si) ; } // Check to see if we've encountered the screen so far in this // loop over the view's canvases. If not, clear the screen's list // of canvases for this ViewInfo. if (newSet.add(si)) si.clear(this) ; // Check if this is a new canvas. CanvasInfo ci = (CanvasInfo)canvasMap.get(c3d) ; if (ci == null) ci = new CanvasInfo(c3d, si) ; // Add this canvas to the screen's list for this ViewInfo. si.addCanvasInfo(this, ci) ; // Add this canvas to the new canvas map and canvas array. this.newMap.put(c3d, ci) ; this.canvasInfo[i] = ci ; } // Null out old references if canvas count shrinks. for (int i = canvasCount ; i < canvasInfo.length ; i++) this.canvasInfo[i] = null ; // Update the CanvasInfo map. Map tmp = canvasMap ; this.canvasMap = newMap ; this.newMap = tmp ; // Clear the temporary collections. this.newMap.clear() ; this.newSet.clear() ; } // // Force the creation of new CanvasInfo instances. This is called when a // screen is removed from the screen map. // private void clearCanvases() { this.canvasCount = 0 ; this.canvasMap.clear() ; this.updateView = true ; } // // Update the view platform. Each view can be attached to only one, but // each view platform can have many views attached. // private void getViewPlatform() { ViewPlatform vp = view.getViewPlatform() ; if (vp == null) throw new IllegalStateException ("The View must be attached to a ViewPlatform") ; ViewPlatformInfo tmpVpi = (ViewPlatformInfo)viewPlatformMap.get(vp) ; if (tmpVpi == null) { // We haven't encountered this ViewPlatform before. tmpVpi = new ViewPlatformInfo(vp) ; viewPlatformMap.put(vp, tmpVpi) ; } if (this.vpi != tmpVpi) { // ViewPlatform has changed. Could set an update flag here if it // would be used, but updating the view updates everything anyway. if (this.vpi != null) { // Remove this ViewInfo from the list of Views attached to the // old ViewPlatform. this.vpi.removeViewInfo(this) ; } this.vpi = tmpVpi ; this.vpi.addViewInfo(this) ; // updateViewPlatformToVworld is initially set false since the // capability to read the vworld transform may not be // available. If it is, set it here. if (vp.getCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ)) { this.vpi.updateViewPlatformToVworld = true ; if (verbose) System.err.println(" vworld read allowed") ; } else if (verbose) System.err.println(" vworld read disallowed") ; } } // // Force the creation of a new ViewPlatformInfo when a view platform is // removed from the view platform map. // private void clearViewPlatform() { this.updateView = true ; } // // Update vworld dependencies for this ViewInfo -- called by // ViewPlatformInfo.getViewPlatformToVworld(). // private void updateVworldDependencies() { for (int i = 0 ; i < canvasCount ; i++) this.canvasInfo[i].updateVworldDependencies() ; } /** * Returns a reference to a Transform3D containing the current transform * from head tracker coordinates to tracker base coordinates. It is only * called if useHeadTracking returns true and a head position * update is specified with updateHead or the * HEAD_AUTO_UPDATE constructor flag.

* * The default implementation uses the head tracking sensor specified by * the View's PhysicalEnvironment, and reads it by calling the sensor's * getRead method directly. The result is a sensor reading * that may have been taken at a slightly different time from the one used * by the renderer. This method can be overridden to synchronize the two * readings through an external mechanism. * * @return current head tracker to tracker base transform * @see #useHeadTracking * @see #updateHead * @see #HEAD_AUTO_UPDATE */ protected Transform3D getHeadTrackerToTrackerBase() { headTracker.getRead(this.headTrackerToTrackerBase) ; return this.headTrackerToTrackerBase ; } /** * Returns true if head tracking should be used.

* * The default implementation returns true if the View's * getTrackingEnable method and the PhysicalEnvironment's * getTrackingAvailable method both return true. * These are the same conditions under which the Java 3D renderer uses * head tracking. This method can be overridden if there is any need to * decouple the head tracking status of ViewInfo from the renderer. * * @return true if ViewInfo should use head tracking */ protected boolean useHeadTracking() { return view.getTrackingEnable() && env.getTrackingAvailable() ; } // // Cache the current tracked head position and derived data. // private void getHeadInfo() { if (verbose) System.err.println(" getHeadInfo") ; this.headTrackerToTrackerBase = getHeadTrackerToTrackerBase() ; if (viewPolicy == View.HMD_VIEW) { this.trackerBaseToHeadTracker.invert(headTrackerToTrackerBase) ; this.coeToHeadTracker.mul(trackerBaseToHeadTracker, coeToTrackerBase) ; } else { this.headToTrackerBase.mul(headTrackerToTrackerBase, headToHeadTracker) ; } for (int i = 0 ; i < canvasCount ; i++) this.canvasInfo[i].updateHeadDependencies() ; this.updateHead = false ; // // The head position used by the Java 3D renderer isn't accessible // in the public API. A head tracker generates continuous data, so // getting the same sensor read as the renderer is unlikely. // // Possible workaround: for fixed screens, get the Java 3D // renderer's version of plateToVworld and headToVworld by calling // Canvas3D.getImagePlateToVworld() and View.getUserHeadToVworld(). // Although the vworld components will have frame latency, they can // be cancelled out by inverting the former transform and // multiplying by the latter, resulting in userHeadToImagePlate, // which can then be transformed to tracker base coordinates. // // For head mounted displays, the head to image plate transforms are // just calibration constants, so they're of no use. There are more // involved workarounds possible, but one that may work for both fixed // screens and HMD is to define a SensorInterposer class that extends // Sensor. Take the View's head tracking sensor, use it to construct // a SensorInterposer, and then replace the head tracking sensor with // the SensorInterposer. SensorInterposer can then override the // getRead() methods and thus control what the Java 3D renderer gets. // getHeadTrackerToTrackerBase() is a protected method in ViewInfo // which can be overridden to call a variant of getRead() so that // calls from ViewInfo and from the renderer can be distinguished. // // Even if getting the same head position as used by the renderer is // achieved, tracked eye space interactions with objects in the // virtual world still can't be synchronized with rendering. This // means that objects in the virtual world cannot be made to appear in // a fixed position relative to the tracked head position without a // frame lag between them. // // The reason for this is that the tracked head position used by the // Java 3D renderer is updated asynchronously from scene graph // updates. This is done to reduce latency between the user's // position and the rendered image, which is directly related to the // quality of the immersive virtual reality experience. So while an // update to the scene graph may have a frame latency before it gets // rendered, a change to the user's tracked position is always // reflected in the current frame. // // This problem can't be fixed without eliminating the frame latency // in the Java 3D internal state, although there are possible // workarounds at the expense of increased user position latency. // These involve disabling tracking, reading the head sensor directly, // performing whatever eye space interactions are necessary with the // virtual world (using the view platform's current localToVworld), // and then propagating the head position change to the renderer // manually through a behavior post mechanism that delays it by a // frame. // // For example, with head tracking in a fixed screen environment (such // as a CAVE), disable Java 3D head tracking and set the View's window // eyepoint policy to RELATIVE_TO_COEXISTENCE. Read the sensor to get // the head position relative to the tracker base, transform it to // coexistence coordinates using the inverse of the value of the // coexistenceToTrackerBase transform, and then set the eye positions // manually with the View's set{Left,Right}ManualEyeInCoexistence // methods. If these method calls are delayed through a behavior post // mechanism, then they will be synchronized with the rendering of the // scene graph updates. // // With a head mounted display the sensor can be read directly to get // the head position relative to the tracker base. If Java 3D's head // tracking is disabled, it uses identity for the current // headTrackerToTrackerBase transform. It concatenates its inverse, // trackerBaseToHeadTracker, with coexistenceToTrackerBase to get the // image plate positions in coexistence; the former transform is // inaccessible, but the latter can be set through the // PhysicalEnvironment. So the workaround is to maintain a local copy // with the real value of coexistenceToTrackerBase, but set the // PhysicalEnvironment copy to the product of the real value and the // trackerBaseToHeadTracker inverted from the sensor read. Like the // CAVE example, this update to the View would have to be delayed in // order to synchronize with scene graph updates. // // Another possibility is to put the Java 3D view model in // compatibility mode, where it accepts vpcToEye and eyeToCc // (projection) directly. The various view attributes can still be // set and accessed, but will be ignored by the Java 3D view model. // The ViewInfo methods can be used to compute the view and projection // matrices, which can then be delayed to synchronize with the scene // graph. // // Note that these workarounds could be used to make view-dependent // scene graph updates consistent, but they still can't do anything // about synchronizing the actual physical position of the user with // the rendered images. That requires zero latency between position // update and scene graph state. // // Still another possibility: extrapolate the position of the user // into the next few frames from a sample of recently recorded // positions. Unfortunately, that is also a very hard problem. The // Java 3D Sensor API is designed to support prediction but it was // never realized successfully in the sample implementation. } // // A per-screen cache, shared between ViewInfo instances. In the Java 3D // view model a single screen can be associated with multiple canvas // and view instances. // private static class ScreenInfo { private Screen3D s3d = null ; private GraphicsConfiguration graphicsConfiguration = null ; private boolean updateScreen = true ; private Map viewInfoMap = new HashMap() ; private List viewInfoList = new LinkedList() ; private Transform3D t3d = new Transform3D() ; private double screenWidth = 0.0 ; private double screenHeight = 0.0 ; private boolean updateScreenSize = true ; private Rectangle screenBounds = null ; private double metersPerPixelX = 0.0 ; private double metersPerPixelY = 0.0 ; private boolean updatePixelSize = true ; // These transforms are pre-allocated here since they are required by // some view policies and we don't know what views this screen will be // attached to. Their default identity values are used if not // explicitly set. TODO: allocate if needed in getCanvasInfo(), where // view information will be available. private Transform3D trackerBaseToPlate = new Transform3D() ; private Transform3D headTrackerToLeftPlate = new Transform3D() ; private Transform3D headTrackerToRightPlate = new Transform3D() ; private boolean updateTrackerBaseToPlate = false ; private boolean updateHeadTrackerToPlate = false ; private ScreenInfo(Screen3D s3d, GraphicsConfiguration gc) { this.s3d = s3d ; this.graphicsConfiguration = gc ; if (verbose) System.err.println(" ScreenInfo: init " + s3d.hashCode()) ; } private List getCanvasList(ViewInfo vi) { List canvasList = (List)viewInfoMap.get(vi) ; if (canvasList == null) { canvasList = new LinkedList() ; viewInfoMap.put(vi, canvasList) ; viewInfoList.add(canvasList) ; } return canvasList ; } private synchronized void clear(ViewInfo vi) { getCanvasList(vi).clear() ; } private synchronized void clear() { Iterator i = viewInfoMap.keySet().iterator() ; while (i.hasNext()) ((ViewInfo)i.next()).clearCanvases() ; viewInfoMap.clear() ; i = viewInfoList.iterator() ; while (i.hasNext()) ((List)i.next()).clear() ; viewInfoList.clear() ; } private synchronized void addCanvasInfo(ViewInfo vi, CanvasInfo ci) { getCanvasList(vi).add(ci) ; } // // Get all relevant screen information, find out what changed, and // flag derived data. With normal use it's unlikely that any of the // Screen3D attributes will change after the first time this method is // called. It's possible that the screen resolution changed or some // sort of interactive screen calibration is in process. // private synchronized void getScreenInfo() { if (verbose) System.err.println(" getScreenInfo " + s3d.hashCode()); // This is used for positioning screens in relation to each other // and must be accurate for good results with multi-screen // displays. By default the coexistence to tracker base transform // is identity so in that case this transform will also set the // image plate in coexistence coordinates. s3d.getTrackerBaseToImagePlate(t3d) ; if (! t3d.equals(trackerBaseToPlate)) { this.trackerBaseToPlate.set(t3d) ; this.updateTrackerBaseToPlate = true ; if (verbose) t3dPrint(trackerBaseToPlate, " trackerBaseToPlate") ; } // This transform and the following are used for head mounted // displays. They should be based on the *apparent* position of // the screens as viewed through the HMD optics. s3d.getHeadTrackerToLeftImagePlate(t3d) ; if (! t3d.equals(headTrackerToLeftPlate)) { this.headTrackerToLeftPlate.set(t3d) ; this.updateHeadTrackerToPlate = true ; if (verbose) t3dPrint(headTrackerToLeftPlate, " headTrackerToLeftPlate") ; } s3d.getHeadTrackerToRightImagePlate(t3d) ; if (! t3d.equals(headTrackerToRightPlate)) { this.headTrackerToRightPlate.set(t3d) ; this.updateHeadTrackerToPlate = true ; if (verbose) t3dPrint(headTrackerToRightPlate, " headTrackerToRightPlate") ; } // If the screen width and height in meters are not explicitly set // through the Screen3D, then the Screen3D will assume a pixel // resolution of 90 pixels/inch and compute the dimensions from // the screen resolution. These dimensions should be measured // accurately for multi-screen displays. For HMD, these // dimensions should be the *apparent* width and height as viewed // through the HMD optics. double w = s3d.getPhysicalScreenWidth() ; double h = s3d.getPhysicalScreenHeight(); if (w != screenWidth || h != screenHeight) { this.screenWidth = w ; this.screenHeight = h ; this.updateScreenSize = true ; if (verbose) { System.err.println(" screen width " + screenWidth) ; System.err.println(" screen height " + screenHeight) ; } } GraphicsConfiguration gc1 = graphicsConfiguration; // Workaround for Issue 316 - use the default config for screen 0 // if the graphics config is null if (gc1 == null) { gc1 = GraphicsEnvironment.getLocalGraphicsEnvironment(). getDefaultScreenDevice().getDefaultConfiguration(); } this.screenBounds = gc1.getBounds() ; double mpx = screenWidth / (double)screenBounds.width ; double mpy = screenHeight / (double)screenBounds.height ; if ((mpx != metersPerPixelX) || (mpy != metersPerPixelY)) { this.metersPerPixelX = mpx ; this.metersPerPixelY = mpy ; this.updatePixelSize = true ; if (verbose) { System.err.println(" screen bounds " + screenBounds) ; System.err.println(" pixel size X " + metersPerPixelX) ; System.err.println(" pixel size Y " + metersPerPixelY) ; } } // Propagate screen updates to each canvas in each ViewInfo. Iterator vi = viewInfoList.iterator() ; while (vi.hasNext()) { Iterator ci = ((List)vi.next()).iterator() ; while (ci.hasNext()) ((CanvasInfo)ci.next()).updateScreenDependencies() ; } this.updateTrackerBaseToPlate = false ; this.updateHeadTrackerToPlate = false ; this.updateScreenSize = false ; this.updatePixelSize = false ; this.updateScreen = false ; } } // // A per-ViewPlatform cache, shared between ViewInfo instances. In the // Java 3D view model, a view platform may have several views attached to // it. The only view platform data cached here is its localToVworld, the // inverse of its localToVworld, and the scale from vworld to view // platform coordinates. The view platform to coexistence transform is // cached by the CanvasInfo instances associated with the ViewInfo. // private static class ViewPlatformInfo { private ViewPlatform vp = null ; private List viewInfo = new LinkedList() ; private double[] m = new double[16] ; // These transforms are pre-allocated since we don't know what views // will be attached. Their default identity values are used if a // vworld dependent computation is requested and no initial update of // the view platform was performed; this occurs if the local to vworld // read capability isn't set. TODO: rationalize this and allocate // only if necessary. private Transform3D viewPlatformToVworld = new Transform3D() ; private Transform3D vworldToViewPlatform = new Transform3D() ; private double vworldToViewPlatformScale = 1.0 ; // Set these update flags initially false since we might not have the // capability to read the vworld transform. private boolean updateViewPlatformToVworld = false ; private boolean updateVworldScale = false ; private ViewPlatformInfo(ViewPlatform vp) { this.vp = vp ; if (verbose) System.err.println (" ViewPlatformInfo: init " + vp.hashCode()) ; } private synchronized void addViewInfo(ViewInfo vi) { this.viewInfo.add(vi) ; } private synchronized void removeViewInfo(ViewInfo vi) { this.viewInfo.remove(vi) ; } private synchronized void clear() { Iterator i = viewInfo.iterator() ; while (i.hasNext()) ((ViewInfo)i.next()).clearViewPlatform() ; viewInfo.clear() ; } // // Get the view platform's current localToVworld and // force the update of derived data. // private synchronized void getViewPlatformToVworld() { if (verbose) System.err.println (" getViewPlatformToVworld " + vp.hashCode()) ; vp.getLocalToVworld(this.viewPlatformToVworld) ; this.vworldToViewPlatform.invert(viewPlatformToVworld) ; // Get the scale factor from the virtual world to view platform // transform. Note that this is always a congruent transform. vworldToViewPlatform.get(m) ; double newScale = Math.sqrt(m[0]*m[0] + m[1]*m[1] + m[2]*m[2]) ; // May need to update clip plane distances if scale changed. We'll // check with an epsilon commensurate with single precision float. // It would be more efficient to check the square of the distance // and then compute the square root only if different, but that // makes choosing an epsilon difficult. if ((newScale > vworldToViewPlatformScale + 0.0000001) || (newScale < vworldToViewPlatformScale - 0.0000001)) { this.vworldToViewPlatformScale = newScale ; this.updateVworldScale = true ; if (verbose) System.err.println(" vworld scale " + vworldToViewPlatformScale) ; } // All virtual world transforms must be updated. Iterator i = viewInfo.iterator() ; while (i.hasNext()) ((ViewInfo)i.next()).updateVworldDependencies() ; this.updateVworldScale = false ; this.updateViewPlatformToVworld = false ; } } // // A per-canvas cache. // private class CanvasInfo { private Canvas3D c3d = null ; private ScreenInfo si = null ; private boolean updateCanvas = true ; private double canvasX = 0.0 ; private double canvasY = 0.0 ; private boolean updatePosition = true ; private double canvasWidth = 0.0 ; private double canvasHeight = 0.0 ; private double windowScale = 0.0 ; private boolean updateWindowScale = true ; private double screenScale = 0.0 ; private boolean updateScreenScale = true ; private boolean useStereo = false ; private boolean updateStereo = true ; // // coeToPlate is the same for each Canvas3D in a Screen3D unless // coexistence centering is enabled and the window movement policy is // PHYSICAL_WORLD. // private Transform3D coeToPlate = null ; private Transform3D coeToRightPlate = null ; private boolean updateCoeToPlate = true ; // // viewPlatformToCoe is the same for each Canvas3D in a View unless // the window resize policy is PHYSICAL_WORLD, in which case the scale // factor includes the window scale; or if the screen scale policy is // SCALE_SCREEN_SIZE, in which case the scale factor depends upon the // width of the screen associated with the canvas; or if the window // eyepoint policy is RELATIVE_TO_FIELD_OF_VIEW and the view attach // policy is not NOMINAL_SCREEN, which will set the view platform // origin in coexistence based on the width of the canvas. // private Transform3D viewPlatformToCoe = null ; private Transform3D coeToViewPlatform = null ; private boolean updateViewPlatformToCoe = true ; private boolean updateCoeToViewPlatform = true ; // // plateToViewPlatform is composed from viewPlatformToCoe and // coeToPlate. // private Transform3D plateToViewPlatform = null ; private Transform3D rightPlateToViewPlatform = null ; private boolean updatePlateToViewPlatform = true ; // // trackerBaseToViewPlatform is computed from viewPlatformToCoe and // coeToTrackerBase. // private Transform3D trackerBaseToViewPlatform = null ; private boolean updateTrackerBaseToViewPlatform = true ; // // Eye position in image plate is always different for each Canvas3D // in a View, unless two or more Canvas3D instances are the same // position and size, or two or more Canvas3D instances are using a // window eyepoint policy of RELATIVE_TO_SCREEN and have the same // settings for the manual eye positions. // private Point3d eyeInPlate = new Point3d() ; private Point3d rightEyeInPlate = new Point3d() ; private Transform3D eyeToPlate = null ; private Transform3D rightEyeToPlate = null ; private boolean updateEyeInPlate = true ; private Point3d leftManualEyeInPlate = new Point3d() ; private Point3d rightManualEyeInPlate = new Point3d() ; private boolean updateManualEye = true ; private int monoscopicPolicy = -1 ; private boolean updateMonoPolicy = true ; // // eyeToViewPlatform is computed from eyeToPlate and // plateToViewPlatform. // private Transform3D eyeToViewPlatform = null ; private Transform3D rightEyeToViewPlatform = null ; private boolean updateEyeToViewPlatform = true ; private Transform3D viewPlatformToEye = null ; private Transform3D viewPlatformToRightEye = null ; private boolean updateViewPlatformToEye = true ; // // The projection transform depends upon eye position in image plate. // private Transform3D projection = null ; private Transform3D rightProjection = null ; private boolean updateProjection = true ; private Transform3D inverseProjection = null ; private Transform3D inverseRightProjection = null ; private boolean updateInverseProjection = true ; private Transform3D inverseViewPlatformProjection = null ; private Transform3D inverseViewPlatformRightProjection = null ; private boolean updateInverseViewPlatformProjection = true ; // // The physical clip distances can be affected by the canvas width // with the PHYSICAL_WORLD resize policy. // private double frontClipDistance = 0.0 ; private double backClipDistance = 0.0 ; private boolean updateClipDistances = true ; // // The physical to view platform scale can be affected by the canvas // width with the PHYSICAL_WORLD resize policy. // private double physicalToVpScale = 0.0 ; private double physicalToVirtualScale = 0.0 ; private boolean updatePhysicalToVpScale = true ; private boolean updatePhysicalToVirtualScale = true ; // // The vworld transforms require reading the ViewPlaform's // localToVworld tranform. // private Transform3D plateToVworld = null ; private Transform3D rightPlateToVworld = null ; private boolean updatePlateToVworld = true ; private Transform3D coeToVworld = null ; private boolean updateCoeToVworld = true ; private Transform3D eyeToVworld = null ; private Transform3D rightEyeToVworld = null ; private boolean updateEyeToVworld = true ; private Transform3D trackerBaseToVworld = null ; private boolean updateTrackerBaseToVworld = true ; private Transform3D inverseVworldProjection = null ; private Transform3D inverseVworldRightProjection = null ; private boolean updateInverseVworldProjection = true ; private CanvasInfo(Canvas3D c3d, ScreenInfo si) { this.si = si ; this.c3d = c3d ; if (verbose) System.err.println(" CanvasInfo: init " + c3d.hashCode()) ; } private void getCanvasInfo() { if (verbose) System.err.println(" getCanvasInfo " + c3d.hashCode()) ; boolean newStereo = c3d.getStereoEnable() && c3d.getStereoAvailable() ; if (useStereo != newStereo) { this.useStereo = newStereo ; this.updateStereo = true ; if (verbose) System.err.println(" stereo " + useStereo) ; } this.canvasWidth = c3d.getWidth() * si.metersPerPixelX ; this.canvasHeight = c3d.getHeight() * si.metersPerPixelY ; double newScale = canvasWidth / si.screenWidth ; if (windowScale != newScale) { this.windowScale = newScale ; this.updateWindowScale = true ; if (verbose) { System.err.println(" width " + canvasWidth) ; System.err.println(" height " + canvasHeight) ; System.err.println(" scale " + windowScale) ; } } // For multiple physical screens, AWT returns the canvas location // relative to the origin of the aggregated virtual screen. We // need the location relative to the physical screen origin. Point awtLocation = c3d.getLocationOnScreen() ; int x = awtLocation.x - si.screenBounds.x ; int y = awtLocation.y - si.screenBounds.y ; double newCanvasX = si.metersPerPixelX * x ; double newCanvasY = si.metersPerPixelY * (si.screenBounds.height - (y + c3d.getHeight())) ; if (canvasX != newCanvasX || canvasY != newCanvasY) { this.canvasX = newCanvasX ; this.canvasY = newCanvasY ; this.updatePosition = true ; if (verbose) { System.err.println(" lower left X " + canvasX) ; System.err.println(" lower left Y " + canvasY) ; } } int newMonoPolicy = c3d.getMonoscopicViewPolicy() ; if (monoscopicPolicy != newMonoPolicy) { this.monoscopicPolicy = newMonoPolicy ; this.updateMonoPolicy = true ; if (verbose && !useStereo) { if (monoscopicPolicy == View.LEFT_EYE_VIEW) System.err.println(" left eye view") ; else if (monoscopicPolicy == View.RIGHT_EYE_VIEW) System.err.println(" right eye view") ; else System.err.println(" cyclopean view") ; } } c3d.getLeftManualEyeInImagePlate(leftEye) ; c3d.getRightManualEyeInImagePlate(rightEye) ; if (!leftEye.equals(leftManualEyeInPlate) || !rightEye.equals(rightManualEyeInPlate)) { this.leftManualEyeInPlate.set(leftEye) ; this.rightManualEyeInPlate.set(rightEye) ; this.updateManualEye = true ; if (verbose && (eyePolicy == View.RELATIVE_TO_WINDOW || eyePolicy == View.RELATIVE_TO_SCREEN)) { System.err.println(" left manual eye in plate " + leftManualEyeInPlate) ; System.err.println(" right manual eye in plate " + rightManualEyeInPlate) ; } } updateCanvasDependencies() ; this.updateStereo = false ; this.updateWindowScale = false ; this.updatePosition = false ; this.updateMonoPolicy = false ; this.updateManualEye = false ; this.updateCanvas = false ; } private double getFieldOfViewOffset() { return 0.5 * canvasWidth / Math.tan(0.5 * view.getFieldOfView()) ; } private void updateScreenDependencies() { if (si.updatePixelSize || si.updateScreenSize) { if (eyePolicy == View.RELATIVE_TO_WINDOW || eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) { // Physical location of the canvas might change without // changing the pixel location. updateEyeInPlate = true ; } if (resizePolicy == View.PHYSICAL_WORLD || eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) { // Could change the window scale or view platform Z offset. updateViewPlatformToCoe = true ; } if (resizePolicy == View.PHYSICAL_WORLD) { // Window scale affects the clip distance and the physical // to viewplatform scale. updateClipDistances = true ; updatePhysicalToVpScale = true ; updatePhysicalToVirtualScale = true ; } // Upper right corner of canvas may have moved from eye. updateProjection = true ; } if (si.updateScreenSize && scalePolicy == View.SCALE_SCREEN_SIZE) { // Screen scale affects the clip distances and physical to // view platform scale. The screen scale is also a component // of viewPlatformToCoe. updateScreenScale = true ; updateClipDistances = true ; updatePhysicalToVpScale = true ; updatePhysicalToVirtualScale = true ; updateViewPlatformToCoe = true ; } if (viewPolicy == View.HMD_VIEW) { if (si.updateHeadTrackerToPlate) { // Plate moves with respect to the eye and coexistence. updateEyeInPlate = true ; updateCoeToPlate = true ; } } else if (coeCentering) { if (movementPolicy == View.PHYSICAL_WORLD) { // Coexistence is centered on the canvas. if (si.updatePixelSize || si.updateScreenSize) // Physical location of the canvas might change // without changing the pixel location. updateCoeToPlate = true ; } else if (si.updateScreenSize) // Coexistence is centered on the screen. updateCoeToPlate = true ; } else if (si.updateTrackerBaseToPlate) { // Image plate has possibly changed location. Could be // offset by an update to coeToTrackerBase in the // PhysicalEnvironment though. updateCoeToPlate = true ; } if (updateCoeToPlate && eyePolicy == View.RELATIVE_TO_COEXISTENCE) { // Coexistence has moved with respect to plate. updateEyeInPlate = true ; } if (updateViewPlatformToCoe) { // Derived transforms. trackerBaseToViewPlatform is composed // from viewPlatformToCoe and coexistenceToTrackerBase. updateCoeToViewPlatform = true ; updateCoeToVworld = true ; updateTrackerBaseToViewPlatform = true ; updateTrackerBaseToVworld = true ; } if (updateCoeToPlate || updateViewPlatformToCoe) { // The image plate to view platform transform is composed from // the coexistence to image plate and view platform to // coexistence transforms, so these need updates as well. updatePlateToViewPlatform = true ; updatePlateToVworld = true ; } updateEyeDependencies() ; } private void updateEyeDependencies() { if (updateEyeInPlate) { updateEyeToVworld = true ; updateProjection = true ; } if (updateProjection) { updateInverseProjection = true ; updateInverseViewPlatformProjection = true ; updateInverseVworldProjection = true ; } if (updateEyeInPlate || updatePlateToViewPlatform) { updateViewPlatformToEye = true ; updateEyeToViewPlatform = true ; } } private void updateCanvasDependencies() { if (updateStereo || updateMonoPolicy || (updateManualEye && (eyePolicy == View.RELATIVE_TO_WINDOW || eyePolicy == View.RELATIVE_TO_SCREEN))) { updateEyeInPlate = true ; } if (updateWindowScale || updatePosition) { if (coeCentering && movementPolicy == View.PHYSICAL_WORLD) { // Coexistence is centered on the canvas. updateCoeToPlate = true ; if (eyePolicy == View.RELATIVE_TO_COEXISTENCE) updateEyeInPlate = true ; } if (eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW || eyePolicy == View.RELATIVE_TO_WINDOW) // Eye depends on canvas position and size. updateEyeInPlate = true ; } if (updateWindowScale) { if (resizePolicy == View.PHYSICAL_WORLD || eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) { // View platform scale and its origin Z offset changed. // trackerBaseToViewPlatform needs viewPlatformToCoe. updateViewPlatformToCoe = true ; updateCoeToViewPlatform = true ; updateCoeToVworld = true ; updateTrackerBaseToViewPlatform = true ; updateTrackerBaseToVworld = true ; } if (resizePolicy == View.PHYSICAL_WORLD) { // Clip distance and physical to view platform scale are // affected by the window size. updateClipDistances = true ; updateProjection = true ; updatePhysicalToVpScale = true ; updatePhysicalToVirtualScale = true ; } } if (updateViewPlatformToCoe || updateCoeToPlate) { // The image plate to view platform transform is composed from // the coexistence to image plate and the view platform to // coexistence transforms, so these need updates. updatePlateToViewPlatform = true ; updatePlateToVworld = true ; } if (coeCentering && !updateManualEye && !updateWindowScale && (movementPolicy == View.PHYSICAL_WORLD) && (eyePolicy != View.RELATIVE_TO_SCREEN)) { // The canvas may have moved, but the eye, coexistence, and // view platform moved with it. updateEyeDependencies() // isn't called since it would unnecessarily update the // projection and eyeToViewPlatform transforms. The tested // policies are all true by default. return ; } updateEyeDependencies() ; } // // TODO: A brave soul could refine cache updates here. There are a // lot of attributes to monitor, so we just update everything for now. // private void updateViewDependencies() { // View policy, physical body eye positions, head to head // tracker, window eyepoint policy, field of view, coexistence // centering, or coexistence to image plate may have changed. updateEyeInPlate = true ; // If the eye position in image plate has changed, then the // projection transform may need to be updated. The projection // policy and clip plane distances and policies may have changed. // The window resize policy and screen scale may have changed, // which affects clip plane distance scaling. updateProjection = true ; updateClipDistances = true ; updatePhysicalToVpScale = true ; updatePhysicalToVirtualScale = true ; // View policy, coexistence to tracker base, coexistence centering // enable, or window movement policy may have changed. updateCoeToPlate = true ; // Screen scale, resize policy, view policy, view platform, // physical body, physical environment, eyepoint policy, or field // of view may have changed. updateViewPlatformToCoe = true ; updateCoeToViewPlatform = true ; updateCoeToVworld = true ; // The image plate to view platform transform is composed from the // coexistence to image plate and view platform to coexistence // transforms, so these need updates. updatePlateToViewPlatform = true ; updatePlateToVworld = true ; // View platform to coexistence or coexistence to tracker base may // have changed. updateTrackerBaseToViewPlatform = true ; updateTrackerBaseToVworld = true ; // Screen scale policy or explicit screen scale may have changed. updateScreenScale = true ; // Update transforms derived from eye info. updateEyeDependencies() ; } private void updateHeadDependencies() { if (viewPolicy == View.HMD_VIEW) { // Image plates are fixed relative to the head, so their // positions have changed with respect to coexistence, the // view platform, and the virtual world. The eyes are fixed // with respect to the image plates, so the projection doesn't // change with respect to them. updateCoeToPlate = true ; updatePlateToViewPlatform = true ; updatePlateToVworld = true ; updateViewPlatformToEye = true ; updateEyeToViewPlatform = true ; updateEyeToVworld = true ; updateInverseViewPlatformProjection = true ; updateInverseVworldProjection = true ; } else { // Eye positions have changed with respect to the fixed // screens, so the projections must be updated as well as the // positions. updateEyeInPlate = true ; updateEyeDependencies() ; } } private void updateVworldDependencies() { updatePlateToVworld = true ; updateCoeToVworld = true ; updateEyeToVworld = true ; updateTrackerBaseToVworld = true ; updateInverseVworldProjection = true ; if (vpi.updateVworldScale) updatePhysicalToVirtualScale = true ; if (vpi.updateVworldScale && clipVirtual) { // vworldToViewPlatformScale changed and clip plane distances // are in virtual units. updateProjection = true ; updateClipDistances = true ; updateInverseProjection = true ; updateInverseViewPlatformProjection = true ; } } } /** * Prints out the specified transform in a readable format. * * @param t3d transform to be printed * @param name the name of the transform */ private static void t3dPrint(Transform3D t3d, String name) { double[] m = new double[16] ; t3d.get(m) ; String[] sa = formatMatrixRows(4, 4, m) ; System.err.println(name) ; for (int i = 0 ; i < 4 ; i++) System.err.println(sa[i]) ; } /** * Formats a matrix with fixed fractional digits and integer padding to * align the decimal points in columns. Non-negative numbers print up to * 7 integer digits, while negative numbers print up to 6 integer digits * to account for the negative sign. 6 fractional digits are printed. * * @param rowCount number of rows in the matrix * @param colCount number of columns in the matrix * @param m matrix to be formatted * @return matrix rows formatted into strings */ private static String[] formatMatrixRows (int rowCount, int colCount, double[] m) { DecimalFormat df = new DecimalFormat("0.000000") ; FieldPosition fp = new FieldPosition(DecimalFormat.INTEGER_FIELD) ; StringBuffer sb0 = new StringBuffer() ; StringBuffer sb1 = new StringBuffer() ; String[] rows = new String[rowCount] ; for (int i = 0 ; i < rowCount ; i++) { sb0.setLength(0) ; for (int j = 0 ; j < colCount ; j++) { sb1.setLength(0) ; df.format(m[i*colCount+j], sb1, fp) ; int pad = 8 - fp.getEndIndex() ; for (int k = 0 ; k < pad ; k++) { sb1.insert(0, " ") ; } sb0.append(sb1) ; } rows[i] = sb0.toString() ; } return rows ; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy