0001: /*
0002: * $RCSfile: ViewInfo.java,v $
0003: *
0004: * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions
0008: * are met:
0009: *
0010: * - Redistribution of source code must retain the above copyright
0011: * notice, this list of conditions and the following disclaimer.
0012: *
0013: * - Redistribution in binary form must reproduce the above copyright
0014: * notice, this list of conditions and the following disclaimer in
0015: * the documentation and/or other materials provided with the
0016: * distribution.
0017: *
0018: * Neither the name of Sun Microsystems, Inc. or the names of
0019: * contributors may be used to endorse or promote products derived
0020: * from this software without specific prior written permission.
0021: *
0022: * This software is provided "AS IS," without a warranty of any
0023: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
0024: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
0025: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
0026: * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
0027: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
0028: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
0029: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
0030: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
0031: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
0032: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
0033: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
0034: * POSSIBILITY OF SUCH DAMAGES.
0035: *
0036: * You acknowledge that this software is not designed, licensed or
0037: * intended for use in the design, construction, operation or
0038: * maintenance of any nuclear facility.
0039: *
0040: * $Revision: 1.6 $
0041: * $Date: 2007/02/09 17:20:45 $
0042: * $State: Exp $
0043: */
0044:
0045: package com.sun.j3d.utils.universe;
0046:
0047: import java.awt.GraphicsConfiguration;
0048: import java.awt.GraphicsEnvironment;
0049: import java.awt.Point;
0050: import java.awt.Rectangle;
0051: import java.text.DecimalFormat;
0052: import java.text.FieldPosition;
0053: import java.util.*;
0054: import javax.media.j3d.*;
0055: import javax.vecmath.*;
0056:
0057: /**
0058: * Provides methods to extract synchronized transform information from a View.
0059: * These transforms are derived from application scene graph information, as
0060: * opposed to similar core Java 3D methods that derive transforms from
0061: * internally maintained data. This allows updates to the scene graph to be
0062: * synchronized with the current view platform position.<p>
0063: *
0064: * The architecture of the Java 3D 1.3 sample implementation introduces a
0065: * frame latency between updates to the application scene graph structure and
0066: * their effects on internal Java 3D state. <code>getImagePlateToVworld</code>
0067: * and other methods in the core Java 3D classes use a transform from view
0068: * platform coordinates to virtual world coordinates that can be out of date
0069: * with respect to the state of the view platform as set by the application.
0070: * When an application uses the transforms returned by those methods to update
0071: * view dependent parts of the scene graph, those updates might not be
0072: * synchronized with what the viewer actually sees.<p>
0073: *
0074: * The methods in this class work around this problem at the expense of
0075: * querying the application state of the scene graph to get the current
0076: * transform from view platform to virtual world coordinates. This can
0077: * involve a potential performance degradation, however, since the application
0078: * scene graph state is not designed for high performance queries. The view
0079: * platform must also have <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability
0080: * set, which potentially inhibits internal scene graph optimization.<p>
0081: *
0082: * On the other hand, application behaviors that create the view platform
0083: * transformation directly will have access to it without the need to query it
0084: * from the scene graph; in that case, the transforms from physical
0085: * coordinates to view platform coordinates provided by this class are all
0086: * that are needed. The <code>ALLOW_LOCAL_TO_VWORLD_READ</code> view platform
0087: * capability doesn't need to be set for these applications.<p>
0088: *
0089: * <b>Other Synchronization Issues</b><p>
0090: *
0091: * Scene graph updates are guaranteed to take effect in the same frame only
0092: * if run from the processStimulus() method of a Behavior. Updates from
0093: * multiple behaviors are only guaranteed to take effect in the same frame if
0094: * they're responding to a WakeupOnElapsedFrames(0) condition. Use a single
0095: * behavior to perform view dependent updates if possible; otherwise, use
0096: * WakeupOnElapsedFrames(0) and set behavior scheduling intervals to ensure
0097: * that behaviors that need the current view platform transform are run after
0098: * it's set. Updating scene graph elements from anything other than the
0099: * Behavior thread, such as an external input thread or a renderer callback
0100: * in Canvas3D, will not necessarily be synchronized with rendering.<p>
0101: *
0102: * Direct updates to geometry data have a different frame latency than
0103: * updates to scene graph transforms and structure. In the Java 3D 1.3
0104: * architecture, updates to by-reference geometry arrays and texture data have
0105: * a 1-frame latency, while updates to transforms and scene graph structure
0106: * have a 2-frame latency. Because of bug 4799494, which is outstanding
0107: * in Java 3D 1.3.1, updates to by-copy geometry arrays also have a 1-frame
0108: * latency. It is therefore recommended that view dependent scene graph
0109: * updates be limited to transforms and scene graph structure only.<p>
0110: *
0111: * If it is not possible to avoid updating geometry directly, then these
0112: * updates must be delayed by one frame in order to remain synchronized with
0113: * the view platform. This can be accomplished by creating an additional
0114: * behavior to actually update the geometry, separate from the behavior that
0115: * computes the changes that need to be made based on current view state. If
0116: * the update behavior is awakened by a behavior post from the computing
0117: * behavior then the update will be delayed by a single frame.<p>
0118: *
0119: * <b>Implementation Notes</b><p>
0120: *
0121: * This utility is essentially a rewrite of a few private Java 3D core
0122: * classes, but designed for public use and source code availability. The
0123: * source code may be helpful in understanding some of the more complex
0124: * aspects of the view model, especially with regards to various interactions
0125: * between attributes which are not adequately documented. None of the actual
0126: * core Java 3D source code is used, but the code is designed to comply with
0127: * the view model as defined by the Java 3D Specification, so it can be
0128: * considered an alternative implementation. This class will produce the
0129: * same results as the Java 3D core implementation except for:<p><ul>
0130: *
0131: * <li>The frame latency issue for virtual world transforms.</li><p>
0132: *
0133: * <li>Active clip node status. If a clip node is active in the scene graph,
0134: * it should override the view's back clip plane. This class has no such
0135: * information, so this can't be implemented.</li><p>
0136: *
0137: * <li>"Infinite" view transforms for background geometry. These are simply
0138: * the rotation components of the normal view transforms with adjusted
0139: * clip planes. Again, this function depends upon scene graph content
0140: * inaccessible to this class.</li><p>
0141: *
0142: * <li>Small floating point precision differences resulting from the
0143: * alternative computations.</li><p>
0144: *
0145: * <li>Bugs in this class and the Java 3D core.</li><p>
0146: *
0147: * <li>Tracked head position.</li></ul><p>
0148: *
0149: * The last item deserves some mention. Java 3D provides no way to directly
0150: * query the tracked head position being used by the renderer. The View's
0151: * <code>getUserHeadToVworld</code> method always incorporates a virtual world
0152: * transform that is out of date with respect to the application scene graph
0153: * state. ViewInfo reads data from the head tracking sensor directly, but
0154: * since head trackers are continuous input devices, getting the same data
0155: * that the renderer is using is unlikely. See the source code for the
0156: * private method <code>getHeadInfo</code> in this class for more information
0157: * and possible workarounds.<p>
0158: *
0159: * <b>Thread Safety</b><p>
0160: *
0161: * All transforms are lazily evaluated. The <code>updateScreen</code>,
0162: * <code>updateCanvas</code>, <code>updateViewPlatform</code>,
0163: * <code>updateView</code>, and <code>updateHead</code> methods just set flags
0164: * indicating that derived transforms need to be recomputed; they are safe to
0165: * call from any thread. <code>updateCanvas</code>, for example, can safely
0166: * be called from an AWT event listener.<p>
0167: *
0168: * Screens and view platforms can be shared between separate views in the Java
0169: * 3D view model. To remain accurate, ViewInfo also allows this sharing.
0170: * Since it is likely that a multi-view application has separate threads
0171: * managing each view, potential concurrent modification of data associated
0172: * with a screen or a view platform is internally synchronized in this class.
0173: * It is safe for each thread to use its own instance of a ViewInfo
0174: * corresponding to the view it is managing.<p>
0175: *
0176: * Otherwise, none of the other methods in this class are internally
0177: * synchronized. <i>Except for the update methods mentioned above, a single
0178: * instance of ViewInfo should not be used by more than one concurrent thread
0179: * without external synchronization.</i><p>
0180: *
0181: * @since Java 3D 1.3.1
0182: */
0183: public class ViewInfo {
0184: private final static boolean verbose = false;
0185:
0186: /**
0187: * Indicates that updates to a Screen3D associated with the View should
0188: * be automatically checked with each call to a public method in this
0189: * class.
0190: */
0191: public final static int SCREEN_AUTO_UPDATE = 1;
0192:
0193: /**
0194: * Indicates that updates to a Canvas3D associated with the View should
0195: * be automatically checked with each call to a public method in this
0196: * class.
0197: */
0198: public final static int CANVAS_AUTO_UPDATE = 2;
0199:
0200: /**
0201: * Indicates that updates to the View should be automatically checked
0202: * with each call to a public method in this class.
0203: */
0204: public final static int VIEW_AUTO_UPDATE = 4;
0205:
0206: /**
0207: * Indicates that updates to the tracked head position should be
0208: * automatically checked with each call to a public method in this class.
0209: */
0210: public final static int HEAD_AUTO_UPDATE = 8;
0211:
0212: /**
0213: * Indicates that updates to the ViewPlatform <code>localToVworld</code>
0214: * transform should be automatically checked with each call to a public
0215: * method in this class. The View must be attached to a ViewPlatform
0216: * which is part of a live scene graph, and the ViewPlatform node must
0217: * have its <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set.
0218: */
0219: public final static int PLATFORM_AUTO_UPDATE = 16;
0220:
0221: //
0222: // Screen3D and ViewPlatform instances are shared across multiple Views in
0223: // the Java 3D view model. Since ViewInfo is per-View and we want to
0224: // cache screen and platform derived data, we maintain static references
0225: // to the screens and platforms here.
0226: //
0227: // From a design standpoint our ViewInfo objects should probably be in the
0228: // scope of an object that encloses these maps so they can be gc'ed
0229: // properly. This is cumbersome with the current design constraints, so
0230: // for now we provide an alternative constructor to override these static
0231: // maps and a method to explicitly clear them. The alternative
0232: // constructor can be used to wrap this class into a multi-view context
0233: // that provides the maps.
0234: //
0235: private static Map staticVpMap = new HashMap();
0236: private static Map staticSiMap = new HashMap();
0237:
0238: private Map screenMap = null;
0239: private Map viewPlatformMap = null;
0240:
0241: // The target View and some derived data.
0242: private View view = null;
0243: private Sensor headTracker = null;
0244: private boolean useTracking = false;
0245: private boolean clipVirtual = false;
0246:
0247: // The current ViewPlatform and Canvas3D information used by this object.
0248: private ViewPlatformInfo vpi = null;
0249: private int canvasCount = 0;
0250: private Map canvasMap = new HashMap();
0251: private CanvasInfo[] canvasInfo = new CanvasInfo[1];
0252:
0253: // This View's update flags. The other update flags are maintained by
0254: // ScreenInfo, CanvasInfo, and ViewPlatformInfo.
0255: private boolean updateView = true;
0256: private boolean updateHead = true;
0257: private boolean autoUpdate = false;
0258: private int autoUpdateFlags = 0;
0259:
0260: // Cached View policies.
0261: private int viewPolicy = View.SCREEN_VIEW;
0262: private int resizePolicy = View.PHYSICAL_WORLD;
0263: private int movementPolicy = View.PHYSICAL_WORLD;
0264: private int eyePolicy = View.RELATIVE_TO_FIELD_OF_VIEW;
0265: private int projectionPolicy = View.PERSPECTIVE_PROJECTION;
0266: private int frontClipPolicy = View.PHYSICAL_EYE;
0267: private int backClipPolicy = View.PHYSICAL_EYE;
0268: private int scalePolicy = View.SCALE_SCREEN_SIZE;
0269: private boolean coeCentering = true;
0270:
0271: // This View's cached transforms. See ScreenInfo, CanvasInfo, and
0272: // ViewPlatformInfo for the rest of the cached transforms.
0273: private Transform3D coeToTrackerBase = null;
0274: private Transform3D headToHeadTracker = null;
0275:
0276: // These are from the head tracker read.
0277: private Transform3D headTrackerToTrackerBase = null;
0278: private Transform3D trackerBaseToHeadTracker = null;
0279:
0280: // These are derived from the head tracker read.
0281: private Transform3D headToTrackerBase = null;
0282: private Transform3D coeToHeadTracker = null;
0283:
0284: // Cached physical body and environment.
0285: private PhysicalEnvironment env = null;
0286: private PhysicalBody body = null;
0287: private Point3d leftEyeInHead = new Point3d();
0288: private Point3d rightEyeInHead = new Point3d();
0289:
0290: // Temporary variables. These could just be new'ed as needed, but we'll
0291: // assume that ViewInfo instances are used much more than they're created.
0292: private Vector3d v3d = new Vector3d();
0293: private double[] m16d = new double[16];
0294: private Point3d leftEye = new Point3d();
0295: private Point3d rightEye = new Point3d();
0296: private Map newMap = new HashMap();
0297: private Set newSet = new HashSet();
0298:
0299: /**
0300: * Creates a new ViewInfo for the specified View.<p>
0301: *
0302: * Applications are responsible for informing this class of changes to the
0303: * View, its Canvas3D and Screen3D components, the tracked head position,
0304: * and the ViewPlatform's <code>localToVworld</code> transform. These
0305: * notifications are performed with the <code>updateView</code>,
0306: * <code>updateCanvas</code>, <code>updateScreen</code>,
0307: * <code>updateHead</code>, and <code>updateViewPlatform</code>
0308: * methods.<p>
0309: *
0310: * The View must be attached to a ViewPlatform. If the ViewPlatform is
0311: * attached to a live scene graph, then <code>ALLOW_POLICY_READ</code>
0312: * capability must be set on the ViewPlatform node.
0313: *
0314: * @param view the View to use
0315: * @see #updateView
0316: * @see #updateCanvas updateCanvas(Canvas3D)
0317: * @see #updateScreen updateScreen(Screen3D)
0318: * @see #updateHead
0319: * @see #updateViewPlatform
0320: */
0321: public ViewInfo(View view) {
0322: this (view, 0);
0323: }
0324:
0325: /**
0326: * Creates a new ViewInfo for the specified View. The View must be
0327: * attached to a ViewPlatform. If the ViewPlatform is attached to a live
0328: * scene graph, then <code>ALLOW_POLICY_READ</code> capability must be set
0329: * on the ViewPlatform node.
0330: *
0331: * @param view the View to use<p>
0332: * @param autoUpdateFlags a logical <code>OR</code> of any of the
0333: * <code>VIEW_AUTO_UPDATE</code>, <code>CANVAS_AUTO_UPDATE</code>,
0334: * <code>SCREEN_AUTO_UPDATE</code>, <code>HEAD_AUTO_UPDATE</code>, or
0335: * <code>PLATFORM_AUTO_UPDATE</code> flags to control whether changes to
0336: * the View, its Canvas3D or Screen3D components, the tracked head
0337: * position, or the ViewPlatform's <code>localToVworld</code> transform
0338: * are checked automatically with each call to a public method of this
0339: * class; if a flag is not set, then the application must inform this
0340: * class of updates to the corresponding data
0341: */
0342: public ViewInfo(View view, int autoUpdateFlags) {
0343: this (view, autoUpdateFlags, staticSiMap, staticVpMap);
0344: }
0345:
0346: /**
0347: * Creates a new ViewInfo for the specified View. The View must be
0348: * attached to a ViewPlatform. If the ViewPlatform is attached to a live
0349: * scene graph, then <code>ALLOW_POLICY_READ</code> capability must be set
0350: * on the ViewPlatform node.<p>
0351: *
0352: * ViewInfo caches Screen3D and ViewPlatform data, but Screen3D and
0353: * ViewPlatform instances are shared across multiple Views in the Java 3D
0354: * view model. Since ViewInfo is per-View, all ViewInfo constructors
0355: * except for this one use static references to manage the shared Screen3D
0356: * and ViewPlatform objects. In this constructor, however, the caller
0357: * supplies two Map instances to hold these references for all ViewInfo
0358: * instances, so static references can be avoided; it can be used to wrap
0359: * this class into a multi-view context that provides the required
0360: * maps.<p>
0361: *
0362: * Alternatively, the other constructors can be used by calling
0363: * <code>ViewInfo.clear</code> when done with ViewInfo, or by simply
0364: * retaining the static references until the JVM exits.<p>
0365: *
0366: * @param view the View to use<p>
0367: * @param autoUpdateFlags a logical <code>OR</code> of any of the
0368: * <code>VIEW_AUTO_UPDATE</code>, <code>CANVAS_AUTO_UPDATE</code>,
0369: * <code>SCREEN_AUTO_UPDATE</code>, <code>HEAD_AUTO_UPDATE</code>, or
0370: * <code>PLATFORM_AUTO_UPDATE</code> flags to control whether changes to
0371: * the View, its Canvas3D or Screen3D components, the tracked head
0372: * position, or the ViewPlatform's <code>localToVworld</code> transform
0373: * are checked automatically with each call to a public method of this
0374: * class; if a flag is not set, then the application must inform this
0375: * class of updates to the corresponding data<p>
0376: * @param screenMap a writeable Map to hold Screen3D information
0377: * @param viewPlatformMap a writeable Map to hold ViewPlatform information
0378: */
0379: public ViewInfo(View view, int autoUpdateFlags, Map screenMap,
0380: Map viewPlatformMap) {
0381:
0382: if (verbose)
0383: System.err.println("ViewInfo: init " + hashCode());
0384: if (view == null)
0385: throw new IllegalArgumentException("View is null");
0386: if (screenMap == null)
0387: throw new IllegalArgumentException("screenMap is null");
0388: if (viewPlatformMap == null)
0389: throw new IllegalArgumentException(
0390: "viewPlatformMap is null");
0391:
0392: this .view = view;
0393: this .screenMap = screenMap;
0394: this .viewPlatformMap = viewPlatformMap;
0395:
0396: if (autoUpdateFlags == 0) {
0397: this .autoUpdate = false;
0398: } else {
0399: this .autoUpdate = true;
0400: this .autoUpdateFlags = autoUpdateFlags;
0401: }
0402:
0403: getViewInfo();
0404: }
0405:
0406: /**
0407: * Gets the current transforms from image plate coordinates to view
0408: * platform coordinates and copies them into the given Transform3Ds.<p>
0409: *
0410: * With a monoscopic canvas the image plate transform is copied to the
0411: * first argument and the second argument is not used. For a stereo
0412: * canvas the first argument receives the left image plate transform, and
0413: * if the second argument is non-null it receives the right image plate
0414: * transform. These transforms are always the same unless a head mounted
0415: * display driven by a single stereo canvas is in use.
0416: *
0417: * @param c3d the Canvas3D associated with the image plate
0418: * @param ip2vpl the Transform3D to receive the left transform
0419: * @param ip2vpr the Transform3D to receive the right transform, or null
0420: */
0421: public void getImagePlateToViewPlatform(Canvas3D c3d,
0422: Transform3D ip2vpl, Transform3D ip2vpr) {
0423:
0424: CanvasInfo ci = updateCache(c3d, "getImagePlateToViewPlatform",
0425: false);
0426:
0427: getImagePlateToViewPlatform(ci);
0428: ip2vpl.set(ci.plateToViewPlatform);
0429: if (ci.useStereo && ip2vpr != null)
0430: ip2vpr.set(ci.rightPlateToViewPlatform);
0431: }
0432:
0433: private void getImagePlateToViewPlatform(CanvasInfo ci) {
0434: if (ci.updatePlateToViewPlatform) {
0435: if (verbose)
0436: System.err.println("updating PlateToViewPlatform");
0437: if (ci.plateToViewPlatform == null)
0438: ci.plateToViewPlatform = new Transform3D();
0439:
0440: getCoexistenceToImagePlate(ci);
0441: getViewPlatformToCoexistence(ci);
0442:
0443: ci.plateToViewPlatform.mul(ci.coeToPlate,
0444: ci.viewPlatformToCoe);
0445: ci.plateToViewPlatform.invert();
0446:
0447: if (ci.useStereo) {
0448: if (ci.rightPlateToViewPlatform == null)
0449: ci.rightPlateToViewPlatform = new Transform3D();
0450:
0451: ci.rightPlateToViewPlatform.mul(ci.coeToRightPlate,
0452: ci.viewPlatformToCoe);
0453: ci.rightPlateToViewPlatform.invert();
0454: }
0455: ci.updatePlateToViewPlatform = false;
0456: if (verbose)
0457: t3dPrint(ci.plateToViewPlatform, "plateToVp");
0458: }
0459: }
0460:
0461: /**
0462: * Gets the current transforms from image plate coordinates to virtual
0463: * world coordinates and copies them into the given Transform3Ds.<p>
0464: *
0465: * With a monoscopic canvas the image plate transform is copied to the
0466: * first argument and the second argument is not used. For a stereo
0467: * canvas the first argument receives the left image plate transform, and
0468: * if the second argument is non-null it receives the right image plate
0469: * transform. These transforms are always the same unless a head mounted
0470: * display driven by a single stereo canvas is in use.<p>
0471: *
0472: * The View must be attached to a ViewPlatform which is part of a live
0473: * scene graph, and the ViewPlatform node must have its
0474: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set.
0475: *
0476: * @param c3d the Canvas3D associated with the image plate
0477: * @param ip2vwl the Transform3D to receive the left transform
0478: * @param ip2vwr the Transform3D to receive the right transform, or null
0479: */
0480: public void getImagePlateToVworld(Canvas3D c3d, Transform3D ip2vwl,
0481: Transform3D ip2vwr) {
0482:
0483: CanvasInfo ci = updateCache(c3d, "getImagePlateToVworld", true);
0484: getImagePlateToVworld(ci);
0485: ip2vwl.set(ci.plateToVworld);
0486: if (ci.useStereo && ip2vwr != null)
0487: ip2vwr.set(ci.rightPlateToVworld);
0488: }
0489:
0490: private void getImagePlateToVworld(CanvasInfo ci) {
0491: if (ci.updatePlateToVworld) {
0492: if (verbose)
0493: System.err.println("updating PlateToVworld");
0494: if (ci.plateToVworld == null)
0495: ci.plateToVworld = new Transform3D();
0496:
0497: getImagePlateToViewPlatform(ci);
0498: ci.plateToVworld.mul(vpi.viewPlatformToVworld,
0499: ci.plateToViewPlatform);
0500:
0501: if (ci.useStereo) {
0502: if (ci.rightPlateToVworld == null)
0503: ci.rightPlateToVworld = new Transform3D();
0504:
0505: ci.rightPlateToVworld.mul(vpi.viewPlatformToVworld,
0506: ci.rightPlateToViewPlatform);
0507: }
0508: ci.updatePlateToVworld = false;
0509: }
0510: }
0511:
0512: /**
0513: * Gets the current transforms from coexistence coordinates to image plate
0514: * coordinates and copies them into the given Transform3Ds. The default
0515: * coexistence centering enable and window movement policies are
0516: * <code>true</code> and <code>PHYSICAL_WORLD</code> respectively, which
0517: * will center coexistence coordinates to the middle of the canvas,
0518: * aligned with the screen (image plate). A movement policy of
0519: * <code>VIRTUAL_WORLD</code> centers coexistence coordinates to the
0520: * middle of the screen.<p>
0521: *
0522: * If coexistence centering is turned off, then canvases and screens can
0523: * have arbitrary positions with respect to coexistence, set through the
0524: * the Screen3D <code>trackerBaseToImagePlate</code> transform and the
0525: * PhysicalEnvironment <code>coexistenceToTrackerBase</code> transform.
0526: * These are calibration constants used for multiple fixed screen displays.
0527: * For head mounted displays the transform is determined by the user head
0528: * position along with calibration parameters found in Screen3D and
0529: * PhysicalBody. (See the source code for the private method
0530: * <code>getEyesHMD</code> for more information).<p>
0531: *
0532: * With a monoscopic canvas the image plate transform is copied to the
0533: * first argument and the second argument is not used. For a stereo
0534: * canvas the first argument receives the left image plate transform, and
0535: * if the second argument is non-null it receives the right image plate
0536: * transform. These transforms are always the same unless a head mounted
0537: * display driven by a single stereo canvas is in use.<p>
0538: *
0539: * @param c3d the Canvas3D associated with the image plate
0540: * @param coe2ipl the Transform3D to receive the left transform
0541: * @param coe2ipr the Transform3D to receive the right transform, or null
0542: */
0543: public void getCoexistenceToImagePlate(Canvas3D c3d,
0544: Transform3D coe2ipl, Transform3D coe2ipr) {
0545:
0546: CanvasInfo ci = updateCache(c3d, "getCoexistenceToImagePlate",
0547: false);
0548: getCoexistenceToImagePlate(ci);
0549: coe2ipl.set(ci.coeToPlate);
0550: if (ci.useStereo && coe2ipr != null)
0551: coe2ipr.set(ci.coeToRightPlate);
0552: }
0553:
0554: private void getCoexistenceToImagePlate(CanvasInfo ci) {
0555: //
0556: // This method will always set coeToRightPlate even if stereo is not
0557: // in use. This is necessary so that getEyeToImagePlate() can handle
0558: // a monoscopic view policy of CYCLOPEAN_EYE_VIEW (which averages the
0559: // left and right eye positions) when the eyepoints are expressed in
0560: // coexistence coordinates or are derived from the tracked head.
0561: //
0562: if (ci.updateCoeToPlate) {
0563: if (verbose)
0564: System.err.println("updating CoeToPlate");
0565: if (ci.coeToPlate == null) {
0566: ci.coeToPlate = new Transform3D();
0567: ci.coeToRightPlate = new Transform3D();
0568: }
0569: if (viewPolicy == View.HMD_VIEW) {
0570: // Head mounted displays have their image plates fixed with
0571: // respect to the head, so get the head position in
0572: // coexistence.
0573: ci.coeToPlate.mul(ci.si.headTrackerToLeftPlate,
0574: coeToHeadTracker);
0575: if (ci.useStereo)
0576: // This is the only case in the view model in which the
0577: // right plate transform could be different from the left.
0578: ci.coeToRightPlate.mul(
0579: ci.si.headTrackerToRightPlate,
0580: coeToHeadTracker);
0581: else
0582: ci.coeToRightPlate.set(ci.coeToPlate);
0583: } else if (coeCentering) {
0584: // The default, for fixed single screen displays with no
0585: // motion tracking. The transform is just a translation.
0586: if (movementPolicy == View.PHYSICAL_WORLD)
0587: // The default. Coexistence is centered in the window.
0588: v3d.set(ci.canvasX + (ci.canvasWidth / 2.0),
0589: ci.canvasY + (ci.canvasHeight / 2.0), 0.0);
0590: else
0591: // Coexistence is centered in the screen.
0592: v3d.set(ci.si.screenWidth / 2.0,
0593: ci.si.screenHeight / 2.0, 0.0);
0594:
0595: ci.coeToPlate.set(v3d);
0596: ci.coeToRightPlate.set(v3d);
0597: } else {
0598: // Coexistence centering should be false for multiple fixed
0599: // screens and/or motion tracking. trackerBaseToImagePlate
0600: // and coexistenceToTrackerBase are used explicitly.
0601: ci.coeToPlate.mul(ci.si.trackerBaseToPlate,
0602: coeToTrackerBase);
0603: ci.coeToRightPlate.set(ci.coeToPlate);
0604: }
0605: ci.updateCoeToPlate = false;
0606: if (verbose)
0607: t3dPrint(ci.coeToPlate, "coeToPlate");
0608: }
0609: }
0610:
0611: /**
0612: * Gets the current transform from view platform coordinates to
0613: * coexistence coordinates and copies it into the given transform. View
0614: * platform coordinates are always aligned with coexistence coordinates
0615: * but may differ in scale and in Y and Z offset. The scale is derived
0616: * from the window resize and screen scale policies, while the offset is
0617: * derived from the view attach policy.<p>
0618: *
0619: * Java 3D constructs a view from the physical position of the eyes
0620: * relative to the physical positions of the image plates; it then uses a
0621: * view platform to position that physical configuration into the virtual
0622: * world and from there computes the correct projections of the virtual
0623: * world onto the physical image plates. Coexistence coordinates are used
0624: * to place the physical positions of the view platform, eyes, head, image
0625: * plate, sensors, and tracker base in relation to each other. The view
0626: * platform is positioned with respect to the virtual world through the
0627: * scene graph, so the view platform to coexistence transform defines the
0628: * space in which the virtual world and physical world coexist.<p>
0629: *
0630: * This method requires a Canvas3D. A different transform may be returned
0631: * for each canvas in the view if any of the following apply:<p><ul>
0632: *
0633: * <li>The window resize policy is <code>PHYSICAL_WORLD</code>, which
0634: * alters the scale depending upon the width of the canvas.</li><p>
0635: *
0636: * <li>The screen scale policy is <code>SCALE_SCREEN_SIZE</code>,
0637: * which alters the scale depending upon the width of the screen
0638: * associated with the canvas.</li><p>
0639: *
0640: * <li>A window eyepoint policy of <code>RELATIVE_TO_FIELD_OF_VIEW</code>
0641: * with a view attach policy of <code>NOMINAL_HEAD</code> in effect,
0642: * which sets the view platform Z offset in coexistence coordinates
0643: * based on the width of the canvas. These are the default policies.
0644: * The offset also follows the width of the canvas when the
0645: * <code>NOMINAL_FEET</code> view attach policy is used.</li></ul>
0646: *
0647: * @param c3d the Canvas3D to use
0648: * @param vp2coe the Transform3D to receive the transform
0649: */
0650: public void getViewPlatformToCoexistence(Canvas3D c3d,
0651: Transform3D vp2coe) {
0652:
0653: CanvasInfo ci = updateCache(c3d,
0654: "getViewPlatformToCoexistence", false);
0655:
0656: getViewPlatformToCoexistence(ci);
0657: vp2coe.set(ci.viewPlatformToCoe);
0658: }
0659:
0660: private void getViewPlatformToCoexistence(CanvasInfo ci) {
0661: if (!ci.updateViewPlatformToCoe)
0662: return;
0663: if (verbose)
0664: System.err.println("updating ViewPlatformToCoe");
0665: if (ci.viewPlatformToCoe == null)
0666: ci.viewPlatformToCoe = new Transform3D();
0667: //
0668: // The scale from view platform coordinates to coexistence coordinates
0669: // has two components -- the screen scale and the window scale. The
0670: // window scale only applies if the resize policy is PHYSICAL_WORLD.
0671: //
0672: // This scale is not the same as the vworld to view platform scale.
0673: // The latter is contained in the view platform's localToVworld
0674: // transform as defined by the scene graph. The complete scale factor
0675: // from virtual units to physical units is the product of the vworld
0676: // to view platform scale and the view platform to coexistence scale.
0677: //
0678: getScreenScale(ci);
0679: if (resizePolicy == View.PHYSICAL_WORLD)
0680: ci.viewPlatformToCoe.setScale(ci.screenScale
0681: * ci.windowScale);
0682: else
0683: ci.viewPlatformToCoe.setScale(ci.screenScale);
0684:
0685: if (viewPolicy == View.HMD_VIEW) {
0686: // In HMD mode view platform coordinates are the same as
0687: // coexistence coordinates, except for scale.
0688: ci.updateViewPlatformToCoe = false;
0689: return;
0690: }
0691:
0692: //
0693: // Otherwise, get the offset of the origin of view platform
0694: // coordinates relative to the origin of coexistence. This is is
0695: // specified by two policies: the view platform's view attach policy
0696: // and the physical environment's coexistence center in pworld policy.
0697: //
0698: double eyeOffset;
0699: double eyeHeight = body.getNominalEyeHeightFromGround();
0700: int viewAttachPolicy = view.getViewPlatform()
0701: .getViewAttachPolicy();
0702: int pworldAttachPolicy = env
0703: .getCoexistenceCenterInPworldPolicy();
0704:
0705: if (eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW)
0706: // The view platform origin is the same as the eye position.
0707: eyeOffset = ci.getFieldOfViewOffset();
0708: else
0709: // The view platform origin is independent of the eye position.
0710: eyeOffset = body.getNominalEyeOffsetFromNominalScreen();
0711:
0712: if (pworldAttachPolicy == View.NOMINAL_SCREEN) {
0713: // The default. The physical coexistence origin locates the
0714: // nominal screen. This is rarely, if ever, set to anything
0715: // else, and the intended effects of the other settings are
0716: // not well documented.
0717: if (viewAttachPolicy == View.NOMINAL_HEAD) {
0718: // The default. The view platform origin is at the origin
0719: // of the nominal head in coexistence coordinates, offset
0720: // from the screen along +Z. If the window eyepoint
0721: // policy is RELATIVE_TO_FIELD_OF_VIEW, then the eyepoint
0722: // is the same as the view platform origin.
0723: v3d.set(0.0, 0.0, eyeOffset);
0724: } else if (viewAttachPolicy == View.NOMINAL_SCREEN) {
0725: // View platform and coexistence are the same except for
0726: // scale.
0727: v3d.set(0.0, 0.0, 0.0);
0728: } else {
0729: // The view platform origin is at the ground beneath the
0730: // head.
0731: v3d.set(0.0, -eyeHeight, eyeOffset);
0732: }
0733: } else if (pworldAttachPolicy == View.NOMINAL_HEAD) {
0734: // The physical coexistence origin locates the nominal head.
0735: if (viewAttachPolicy == View.NOMINAL_HEAD) {
0736: // The view platform origin is set to the head;
0737: // coexistence and view platform coordinates differ only
0738: // in scale.
0739: v3d.set(0.0, 0.0, 0.0);
0740: } else if (viewAttachPolicy == View.NOMINAL_SCREEN) {
0741: // The view platform is set in front of the head, at the
0742: // nominal screen location.
0743: v3d.set(0.0, 0.0, -eyeOffset);
0744: } else {
0745: // The view platform origin is at the ground beneath the
0746: // head.
0747: v3d.set(0.0, -eyeHeight, 0.0);
0748: }
0749: } else {
0750: // The physical coexistence origin locates the nominal feet.
0751: if (viewAttachPolicy == View.NOMINAL_HEAD) {
0752: v3d.set(0.0, eyeHeight, 0.0);
0753: } else if (viewAttachPolicy == View.NOMINAL_SCREEN) {
0754: v3d.set(0.0, eyeHeight, -eyeOffset);
0755: } else {
0756: v3d.set(0.0, 0.0, 0.0);
0757: }
0758: }
0759:
0760: ci.viewPlatformToCoe.setTranslation(v3d);
0761: ci.updateViewPlatformToCoe = false;
0762: if (verbose)
0763: t3dPrint(ci.viewPlatformToCoe, "vpToCoe");
0764: }
0765:
0766: /**
0767: * Gets the current transform from coexistence coordinates to
0768: * view platform coordinates and copies it into the given transform.<p>
0769: *
0770: * This method requires a Canvas3D. The returned transform may differ
0771: * across canvases for the same reasons as discussed in the description of
0772: * <code>getViewPlatformToCoexistence</code>.<p>
0773: *
0774: * @param c3d the Canvas3D to use
0775: * @param coe2vp the Transform3D to receive the transform
0776: * @see #getViewPlatformToCoexistence
0777: * getViewPlatformToCoexistence(Canvas3D, Transform3D)
0778: */
0779: public void getCoexistenceToViewPlatform(Canvas3D c3d,
0780: Transform3D coe2vp) {
0781:
0782: CanvasInfo ci = updateCache(c3d,
0783: "getCoexistenceToViewPlatform", false);
0784:
0785: getCoexistenceToViewPlatform(ci);
0786: coe2vp.set(ci.coeToViewPlatform);
0787: }
0788:
0789: private void getCoexistenceToViewPlatform(CanvasInfo ci) {
0790: if (ci.updateCoeToViewPlatform) {
0791: if (verbose)
0792: System.err.println("updating CoeToViewPlatform");
0793: if (ci.coeToViewPlatform == null)
0794: ci.coeToViewPlatform = new Transform3D();
0795:
0796: getViewPlatformToCoexistence(ci);
0797: ci.coeToViewPlatform.invert(ci.viewPlatformToCoe);
0798:
0799: ci.updateCoeToViewPlatform = false;
0800: if (verbose)
0801: t3dPrint(ci.coeToViewPlatform, "coeToVp");
0802: }
0803: }
0804:
0805: /**
0806: * Gets the current transform from coexistence coordinates to virtual
0807: * world coordinates and copies it into the given transform.<p>
0808: *
0809: * The View must be attached to a ViewPlatform which is part of a live
0810: * scene graph, and the ViewPlatform node must have its
0811: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set.<p>
0812: *
0813: * This method requires a Canvas3D. The returned transform may differ
0814: * across canvases for the same reasons as discussed in the description of
0815: * <code>getViewPlatformToCoexistence</code>.<p>
0816: *
0817: * @param c3d the Canvas3D to use
0818: * @param coe2vw the Transform3D to receive the transform
0819: * @see #getViewPlatformToCoexistence
0820: * getViewPlatformToCoexistence(Canvas3D, Transform3D)
0821: */
0822: public void getCoexistenceToVworld(Canvas3D c3d, Transform3D coe2vw) {
0823:
0824: CanvasInfo ci = updateCache(c3d, "getCoexistenceToVworld", true);
0825: getCoexistenceToVworld(ci);
0826: coe2vw.set(ci.coeToVworld);
0827: }
0828:
0829: private void getCoexistenceToVworld(CanvasInfo ci) {
0830: if (ci.updateCoeToVworld) {
0831: if (verbose)
0832: System.err.println("updating CoexistenceToVworld");
0833: if (ci.coeToVworld == null)
0834: ci.coeToVworld = new Transform3D();
0835:
0836: getCoexistenceToViewPlatform(ci);
0837: ci.coeToVworld.mul(vpi.viewPlatformToVworld,
0838: ci.coeToViewPlatform);
0839:
0840: ci.updateCoeToVworld = false;
0841: }
0842: }
0843:
0844: /**
0845: * Gets the transforms from eye coordinates to image plate coordinates and
0846: * copies them into the Transform3Ds specified.<p>
0847: *
0848: * When head tracking is used the eye positions are taken from the head
0849: * position and set in relation to the image plates with each Screen3D's
0850: * <code>trackerBaseToImagePlate</code> transform. Otherwise the window
0851: * eyepoint policy is used to derive the eyepoint relative to the image
0852: * plate. When using a head mounted display the eye position is
0853: * determined solely by calibration constants in Screen3D and
0854: * PhysicalBody; see the source code for the private method
0855: * <code>getEyesHMD</code> for more information.<p>
0856: *
0857: * Eye coordinates are always aligned with image plate coordinates, so
0858: * these transforms are always just translations. With a monoscopic
0859: * canvas the eye transform is copied to the first argument and the second
0860: * argument is not used. For a stereo canvas the first argument receives
0861: * the left eye transform, and if the second argument is non-null it
0862: * receives the right eye transform.
0863: *
0864: * @param c3d the Canvas3D associated with the image plate
0865: * @param e2ipl the Transform3D to receive left transform
0866: * @param e2ipr the Transform3D to receive right transform, or null
0867: */
0868: public void getEyeToImagePlate(Canvas3D c3d, Transform3D e2ipl,
0869: Transform3D e2ipr) {
0870:
0871: CanvasInfo ci = updateCache(c3d, "getEyeToImagePlate", false);
0872: getEyeToImagePlate(ci);
0873: e2ipl.set(ci.eyeToPlate);
0874: if (ci.useStereo && e2ipr != null)
0875: e2ipr.set(ci.rightEyeToPlate);
0876: }
0877:
0878: private void getEyeToImagePlate(CanvasInfo ci) {
0879: if (ci.updateEyeInPlate) {
0880: if (verbose)
0881: System.err.println("updating EyeInPlate");
0882: if (ci.eyeToPlate == null)
0883: ci.eyeToPlate = new Transform3D();
0884:
0885: if (viewPolicy == View.HMD_VIEW) {
0886: getEyesHMD(ci);
0887: } else if (useTracking) {
0888: getEyesTracked(ci);
0889: } else {
0890: getEyesFixedScreen(ci);
0891: }
0892: ci.updateEyeInPlate = false;
0893: if (verbose)
0894: System.err.println("eyeInPlate: " + ci.eyeInPlate);
0895: }
0896: }
0897:
0898: //
0899: // Get physical eye positions for head mounted displays. These are
0900: // determined solely by the headTrackerToImagePlate and headToHeadTracker
0901: // calibration constants defined by Screen3D and the PhysicalBody.
0902: //
0903: // Note that headTrackerLeftToImagePlate and headTrackerToRightImagePlate
0904: // should be set according to the *apparent* position and orientation of
0905: // the image plates, relative to the head and head tracker, as viewed
0906: // through the HMD optics. This is also true of the "physical" screen
0907: // width and height specified by the Screen3D -- they should be the
0908: // *apparent* width and height as viewed through the HMD optics. They
0909: // must be set directly through the Screen3D methods; the default pixel
0910: // metrics of 90 pixels/inch used by Java 3D aren't appropriate for HMD
0911: // optics.
0912: //
0913: // Most HMDs have 100% overlap between the left and right displays; in
0914: // that case, headTrackerToLeftImagePlate and headTrackerToRightImagePlate
0915: // should be identical. The HMD manufacturer's specifications of the
0916: // optics in terms of field of view, image overlap, and distance to the
0917: // focal plane should be used to derive these parameters.
0918: //
0919: private void getEyesHMD(CanvasInfo ci) {
0920: if (ci.useStereo) {
0921: // This case is for head mounted displays driven by a single
0922: // stereo canvas on a single screen. These use a field sequential
0923: // stereo signal to split the left and right images.
0924: leftEye.set(leftEyeInHead);
0925: headToHeadTracker.transform(leftEye);
0926: ci.si.headTrackerToLeftPlate.transform(leftEye,
0927: ci.eyeInPlate);
0928: rightEye.set(rightEyeInHead);
0929: headToHeadTracker.transform(rightEye);
0930: ci.si.headTrackerToRightPlate.transform(rightEye,
0931: ci.rightEyeInPlate);
0932: if (ci.rightEyeToPlate == null)
0933: ci.rightEyeToPlate = new Transform3D();
0934:
0935: v3d.set(ci.rightEyeInPlate);
0936: ci.rightEyeToPlate.set(v3d);
0937: } else {
0938: // This case is for 2-channel head mounted displays driven by two
0939: // monoscopic screens, one for each eye.
0940: switch (ci.monoscopicPolicy) {
0941: case View.LEFT_EYE_VIEW:
0942: leftEye.set(leftEyeInHead);
0943: headToHeadTracker.transform(leftEye);
0944: ci.si.headTrackerToLeftPlate.transform(leftEye,
0945: ci.eyeInPlate);
0946: break;
0947: case View.RIGHT_EYE_VIEW:
0948: rightEye.set(rightEyeInHead);
0949: headToHeadTracker.transform(rightEye);
0950: ci.si.headTrackerToRightPlate.transform(rightEye,
0951: ci.eyeInPlate);
0952: break;
0953: case View.CYCLOPEAN_EYE_VIEW:
0954: default:
0955: throw new IllegalStateException(
0956: "Illegal monoscopic view policy for 2-channel HMD");
0957: }
0958: }
0959: v3d.set(ci.eyeInPlate);
0960: ci.eyeToPlate.set(v3d);
0961: }
0962:
0963: private void getEyesTracked(CanvasInfo ci) {
0964: leftEye.set(leftEyeInHead);
0965: rightEye.set(rightEyeInHead);
0966: headToTrackerBase.transform(leftEye);
0967: headToTrackerBase.transform(rightEye);
0968: if (coeCentering) {
0969: // Coexistence and tracker base coordinates are the same.
0970: // Centering is normally turned off for tracking.
0971: getCoexistenceToImagePlate(ci);
0972: ci.coeToPlate.transform(leftEye);
0973: ci.coeToRightPlate.transform(rightEye);
0974: } else {
0975: // The normal policy for head tracking.
0976: ci.si.trackerBaseToPlate.transform(leftEye);
0977: ci.si.trackerBaseToPlate.transform(rightEye);
0978: }
0979: setEyeScreenRelative(ci, leftEye, rightEye);
0980: }
0981:
0982: private void getEyesFixedScreen(CanvasInfo ci) {
0983: switch (eyePolicy) {
0984: case View.RELATIVE_TO_FIELD_OF_VIEW:
0985: double z = ci.getFieldOfViewOffset();
0986: setEyeWindowRelative(ci, z, z);
0987: break;
0988: case View.RELATIVE_TO_WINDOW:
0989: setEyeWindowRelative(ci, ci.leftManualEyeInPlate.z,
0990: ci.rightManualEyeInPlate.z);
0991: break;
0992: case View.RELATIVE_TO_SCREEN:
0993: setEyeScreenRelative(ci, ci.leftManualEyeInPlate,
0994: ci.rightManualEyeInPlate);
0995: break;
0996: case View.RELATIVE_TO_COEXISTENCE:
0997: view.getLeftManualEyeInCoexistence(leftEye);
0998: view.getRightManualEyeInCoexistence(rightEye);
0999:
1000: getCoexistenceToImagePlate(ci);
1001: ci.coeToPlate.transform(leftEye);
1002: ci.coeToRightPlate.transform(rightEye);
1003: setEyeScreenRelative(ci, leftEye, rightEye);
1004: break;
1005: }
1006: }
1007:
1008: private void setEyeWindowRelative(CanvasInfo ci, double leftZ,
1009: double rightZ) {
1010:
1011: // Eye position X is offset from the window center.
1012: double centerX = (ci.canvasX + (ci.canvasWidth / 2.0));
1013: leftEye.x = centerX + leftEyeInHead.x;
1014: rightEye.x = centerX + rightEyeInHead.x;
1015:
1016: // Eye position Y is always the canvas center.
1017: leftEye.y = rightEye.y = ci.canvasY + (ci.canvasHeight / 2.0);
1018:
1019: // Eye positions Z are as given.
1020: leftEye.z = leftZ;
1021: rightEye.z = rightZ;
1022:
1023: setEyeScreenRelative(ci, leftEye, rightEye);
1024: }
1025:
1026: private void setEyeScreenRelative(CanvasInfo ci, Point3d leftEye,
1027: Point3d rightEye) {
1028: if (ci.useStereo) {
1029: ci.eyeInPlate.set(leftEye);
1030: ci.rightEyeInPlate.set(rightEye);
1031:
1032: if (ci.rightEyeToPlate == null)
1033: ci.rightEyeToPlate = new Transform3D();
1034:
1035: v3d.set(ci.rightEyeInPlate);
1036: ci.rightEyeToPlate.set(v3d);
1037: } else {
1038: switch (ci.monoscopicPolicy) {
1039: case View.CYCLOPEAN_EYE_VIEW:
1040: ci.eyeInPlate.set((leftEye.x + rightEye.x) / 2.0,
1041: (leftEye.y + rightEye.y) / 2.0,
1042: (leftEye.z + rightEye.z) / 2.0);
1043: break;
1044: case View.LEFT_EYE_VIEW:
1045: ci.eyeInPlate.set(leftEye);
1046: break;
1047: case View.RIGHT_EYE_VIEW:
1048: ci.eyeInPlate.set(rightEye);
1049: break;
1050: }
1051: }
1052: v3d.set(ci.eyeInPlate);
1053: ci.eyeToPlate.set(v3d);
1054: }
1055:
1056: /**
1057: * Gets the current transforms from eye coordinates to view platform
1058: * coordinates and copies them into the given Transform3Ds.<p>
1059: *
1060: * With a monoscopic canvas the eye transform is copied to the first
1061: * argument and the second argument is not used. For a stereo canvas the
1062: * first argument receives the left eye transform, and if the second
1063: * argument is non-null it receives the right eye transform.<p>
1064: *
1065: * This method requires a Canvas3D. When using a head mounted display,
1066: * head tracking with fixed screens, or a window eyepoint policy of
1067: * <code>RELATIVE_TO_COEXISTENCE</code>, then the transforms returned may
1068: * be different for each canvas if stereo is not in use and they have
1069: * different monoscopic view policies. They may additionally differ in
1070: * scale across canvases with the <code>PHYSICAL_WORLD</code> window
1071: * resize policy or the <code>SCALE_SCREEN_SIZE</code> screen scale
1072: * policy, which alter the scale depending upon the width of the canvas or
1073: * the width of the screen respectively.<p>
1074: *
1075: * With window eyepoint policies of <code>RELATIVE_TO_FIELD_OF_VIEW</code>,
1076: * <code>RELATIVE_TO_SCREEN</code>, or <code>RELATIVE_TO_WINDOW</code>,
1077: * then the transforms returned may differ across canvases due to
1078: * the following additional conditions:<p><ul>
1079: *
1080: * <li>The window eyepoint policy is <code>RELATIVE_TO_WINDOW</code> or
1081: * <code>RELATIVE_TO_SCREEN</code>, in which case the manual eye
1082: * position in image plate can be set differently for each
1083: * canvas.</li><p>
1084: *
1085: * <li>The window eyepoint policy is <code>RELATIVE_TO_FIELD_OF_VIEW</code>
1086: * and the view attach policy is <code>NOMINAL_SCREEN</code>, which
1087: * decouples the view platform's canvas Z offset from the eyepoint's
1088: * canvas Z offset.</li><p>
1089: *
1090: * <li>The eyepoint X and Y coordinates are centered in the canvas with a
1091: * window eyepoint policy of <code>RELATIVE_TO_FIELD_OF_VIEW</code>
1092: * or <code>RELATIVE_TO_WINDOW</code>, and a window movement policy
1093: * of <code>VIRTUAL_WORLD</code> centers the view platform's X and Y
1094: * coordinates to the middle of the screen.</li><p>
1095: *
1096: * <li>Coexistence centering is set false, which allows each canvas and
1097: * screen to have a different position with respect to coexistence
1098: * coordinates.</li></ul>
1099: *
1100: * @param c3d the Canvas3D to use
1101: * @param e2vpl the Transform3D to receive the left transform
1102: * @param e2vpr the Transform3D to receive the right transform, or null
1103: */
1104: public void getEyeToViewPlatform(Canvas3D c3d, Transform3D e2vpl,
1105: Transform3D e2vpr) {
1106:
1107: CanvasInfo ci = updateCache(c3d, "getEyeToViewPlatform", false);
1108: getEyeToViewPlatform(ci);
1109: e2vpl.set(ci.eyeToViewPlatform);
1110: if (ci.useStereo && e2vpr != null)
1111: e2vpr.set(ci.rightEyeToViewPlatform);
1112: }
1113:
1114: private void getEyeToViewPlatform(CanvasInfo ci) {
1115: if (ci.updateEyeToViewPlatform) {
1116: if (verbose)
1117: System.err.println("updating EyeToViewPlatform");
1118: if (ci.eyeToViewPlatform == null)
1119: ci.eyeToViewPlatform = new Transform3D();
1120:
1121: getEyeToImagePlate(ci);
1122: getImagePlateToViewPlatform(ci);
1123: ci.eyeToViewPlatform.mul(ci.plateToViewPlatform,
1124: ci.eyeToPlate);
1125:
1126: if (ci.useStereo) {
1127: if (ci.rightEyeToViewPlatform == null)
1128: ci.rightEyeToViewPlatform = new Transform3D();
1129:
1130: ci.rightEyeToViewPlatform
1131: .mul(ci.rightPlateToViewPlatform,
1132: ci.rightEyeToPlate);
1133: }
1134: ci.updateEyeToViewPlatform = false;
1135: if (verbose)
1136: t3dPrint(ci.eyeToViewPlatform, "eyeToVp");
1137: }
1138: }
1139:
1140: /**
1141: * Gets the current transforms from view platform coordinates to eye
1142: * coordinates and copies them into the given Transform3Ds.<p>
1143: *
1144: * With a monoscopic canvas the eye transform is copied to the first
1145: * argument and the second argument is not used. For a stereo canvas the
1146: * first argument receives the left eye transform, and if the second
1147: * argument is non-null it receives the right eye transform.<p>
1148: *
1149: * This method requires a Canvas3D. The transforms returned may differ
1150: * across canvases for all the same reasons discussed in the description
1151: * of <code>getEyeToViewPlatform</code>.
1152: *
1153: * @param c3d the Canvas3D to use
1154: * @param vp2el the Transform3D to receive the left transform
1155: * @param vp2er the Transform3D to receive the right transform, or null
1156: * @see #getEyeToViewPlatform
1157: * getEyeToViewPlatform(Canvas3D, Transform3D, Transform3D)
1158: */
1159: public void getViewPlatformToEye(Canvas3D c3d, Transform3D vp2el,
1160: Transform3D vp2er) {
1161:
1162: CanvasInfo ci = updateCache(c3d, "getViewPlatformToEye", false);
1163: getViewPlatformToEye(ci);
1164: vp2el.set(ci.viewPlatformToEye);
1165: if (ci.useStereo && vp2er != null)
1166: vp2er.set(ci.viewPlatformToRightEye);
1167: }
1168:
1169: private void getViewPlatformToEye(CanvasInfo ci) {
1170: if (ci.updateViewPlatformToEye) {
1171: if (verbose)
1172: System.err.println("updating ViewPlatformToEye");
1173: if (ci.viewPlatformToEye == null)
1174: ci.viewPlatformToEye = new Transform3D();
1175:
1176: getEyeToViewPlatform(ci);
1177: ci.viewPlatformToEye.invert(ci.eyeToViewPlatform);
1178:
1179: if (ci.useStereo) {
1180: if (ci.viewPlatformToRightEye == null)
1181: ci.viewPlatformToRightEye = new Transform3D();
1182:
1183: ci.viewPlatformToRightEye
1184: .invert(ci.rightEyeToViewPlatform);
1185: }
1186: ci.updateViewPlatformToEye = false;
1187: }
1188: }
1189:
1190: /**
1191: * Gets the current transforms from eye coordinates to virtual world
1192: * coordinates and copies them into the given Transform3Ds.<p>
1193: *
1194: * With a monoscopic canvas the eye transform is copied to the first
1195: * argument and the second argument is not used. For a stereo canvas the
1196: * first argument receives the left eye transform, and if the second
1197: * argument is non-null it receives the right eye transform.<p>
1198: *
1199: * The View must be attached to a ViewPlatform which is part of a live
1200: * scene graph, and the ViewPlatform node must have its
1201: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set.<p>
1202: *
1203: * This method requires a Canvas3D. The transforms returned may differ
1204: * across canvases for all the same reasons discussed in the description
1205: * of <code>getEyeToViewPlatform</code>.
1206: *
1207: * @param c3d the Canvas3D to use
1208: * @param e2vwl the Transform3D to receive the left transform
1209: * @param e2vwr the Transform3D to receive the right transform, or null
1210: * @see #getEyeToViewPlatform
1211: * getEyeToViewPlatform(Canvas3D, Transform3D, Transform3D)
1212: */
1213: public void getEyeToVworld(Canvas3D c3d, Transform3D e2vwl,
1214: Transform3D e2vwr) {
1215:
1216: CanvasInfo ci = updateCache(c3d, "getEyeToVworld", true);
1217: getEyeToVworld(ci);
1218: e2vwl.set(ci.eyeToVworld);
1219: if (ci.useStereo && e2vwr != null)
1220: e2vwr.set(ci.rightEyeToVworld);
1221: }
1222:
1223: private void getEyeToVworld(CanvasInfo ci) {
1224: if (ci.updateEyeToVworld) {
1225: if (verbose)
1226: System.err.println("updating EyeToVworld");
1227: if (ci.eyeToVworld == null)
1228: ci.eyeToVworld = new Transform3D();
1229:
1230: getEyeToViewPlatform(ci);
1231: ci.eyeToVworld.mul(vpi.viewPlatformToVworld,
1232: ci.eyeToViewPlatform);
1233:
1234: if (ci.useStereo) {
1235: if (ci.rightEyeToVworld == null)
1236: ci.rightEyeToVworld = new Transform3D();
1237:
1238: ci.rightEyeToVworld.mul(vpi.viewPlatformToVworld,
1239: ci.rightEyeToViewPlatform);
1240: }
1241: ci.updateEyeToVworld = false;
1242: }
1243: }
1244:
1245: /**
1246: * Gets the transforms from eye coordinates to clipping coordinates
1247: * and copies them into the given Transform3Ds. These transforms take
1248: * a viewing volume bounded by the physical canvas edges and the
1249: * physical front and back clip planes and project it into a range
1250: * bound to [-1.0 .. +1.0] on each of the X, Y, and Z axes. If a
1251: * perspective projection has been specified then the physical image
1252: * plate eye location defines the apex of a viewing frustum;
1253: * otherwise, the orientation of the image plate determines the
1254: * direction of a parallel projection.<p>
1255: *
1256: * With a monoscopic canvas the projection transform is copied to the
1257: * first argument and the second argument is not used. For a stereo
1258: * canvas the first argument receives the left projection transform,
1259: * and if the second argument is non-null it receives the right
1260: * projection transform.<p>
1261: *
1262: * If either of the clip policies <code>VIRTUAL_EYE</code> or
1263: * <code>VIRTUAL_SCREEN</code> are used, then the View should be attached
1264: * to a ViewPlatform that is part of a live scene graph and that has its
1265: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set; otherwise, a
1266: * scale factor of 1.0 will be used for the scale factor from virtual
1267: * world units to view platform units.
1268: *
1269: * @param c3d the Canvas3D to use
1270: * @param e2ccl the Transform3D to receive left transform
1271: * @param e2ccr the Transform3D to receive right transform, or null
1272: */
1273: public void getProjection(Canvas3D c3d, Transform3D e2ccl,
1274: Transform3D e2ccr) {
1275:
1276: CanvasInfo ci = updateCache(c3d, "getProjection", true);
1277: getProjection(ci);
1278: e2ccl.set(ci.projection);
1279: if (ci.useStereo && e2ccr != null)
1280: e2ccr.set(ci.rightProjection);
1281: }
1282:
1283: private void getProjection(CanvasInfo ci) {
1284: if (ci.updateProjection) {
1285: if (verbose)
1286: System.err.println("updating Projection");
1287: if (ci.projection == null)
1288: ci.projection = new Transform3D();
1289:
1290: getEyeToImagePlate(ci);
1291: getClipDistances(ci);
1292:
1293: // Note: core Java 3D code insists that the back clip plane
1294: // relative to the image plate must be the same left back clip
1295: // distance for both the left and right eye. Not sure why this
1296: // should be, but the same is done here for compatibility.
1297: double backClip = getBackClip(ci, ci.eyeInPlate);
1298: computeProjection(ci, ci.eyeInPlate, getFrontClip(ci,
1299: ci.eyeInPlate), backClip, ci.projection);
1300:
1301: if (ci.useStereo) {
1302: if (ci.rightProjection == null)
1303: ci.rightProjection = new Transform3D();
1304:
1305: computeProjection(ci, ci.rightEyeInPlate, getFrontClip(
1306: ci, ci.rightEyeInPlate), backClip,
1307: ci.rightProjection);
1308: }
1309: ci.updateProjection = false;
1310: if (verbose)
1311: t3dPrint(ci.projection, "projection");
1312: }
1313: }
1314:
1315: /**
1316: * Gets the transforms from clipping coordinates to eye coordinates
1317: * and copies them into the given Transform3Ds. These transforms take
1318: * the clip space volume bounded by the range [-1.0 .. + 1.0] on each
1319: * of the X, Y, and Z and project it into eye coordinates.<p>
1320: *
1321: * With a monoscopic canvas the projection transform is copied to the
1322: * first argument and the second argument is not used. For a stereo
1323: * canvas the first argument receives the left projection transform, and
1324: * if the second argument is non-null it receives the right projection
1325: * transform.<p>
1326: *
1327: * If either of the clip policies <code>VIRTUAL_EYE</code> or
1328: * <code>VIRTUAL_SCREEN</code> are used, then the View should be attached
1329: * to a ViewPlatform that is part of a live scene graph and that has its
1330: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set; otherwise, a
1331: * scale factor of 1.0 will be used for the scale factor from virtual
1332: * world units to view platform units.
1333: *
1334: * @param c3d the Canvas3D to use
1335: * @param cc2el the Transform3D to receive left transform
1336: * @param cc2er the Transform3D to receive right transform, or null
1337: */
1338: public void getInverseProjection(Canvas3D c3d, Transform3D cc2el,
1339: Transform3D cc2er) {
1340:
1341: CanvasInfo ci = updateCache(c3d, "getInverseProjection", true);
1342: getInverseProjection(ci);
1343: cc2el.set(ci.inverseProjection);
1344: if (ci.useStereo && cc2er != null)
1345: cc2er.set(ci.inverseRightProjection);
1346: }
1347:
1348: private void getInverseProjection(CanvasInfo ci) {
1349: if (ci.updateInverseProjection) {
1350: if (verbose)
1351: System.err.println("updating InverseProjection");
1352: if (ci.inverseProjection == null)
1353: ci.inverseProjection = new Transform3D();
1354:
1355: getProjection(ci);
1356: ci.inverseProjection.invert(ci.projection);
1357:
1358: if (ci.useStereo) {
1359: if (ci.inverseRightProjection == null)
1360: ci.inverseRightProjection = new Transform3D();
1361:
1362: ci.inverseRightProjection.invert(ci.rightProjection);
1363: }
1364: ci.updateInverseProjection = false;
1365: }
1366: }
1367:
1368: /**
1369: * Gets the transforms from clipping coordinates to view platform
1370: * coordinates and copies them into the given Transform3Ds. These
1371: * transforms take the clip space volume bounded by the range
1372: * [-1.0 .. +1.0] on each of the X, Y, and Z axes and project into
1373: * the view platform coordinate system.<p>
1374: *
1375: * With a monoscopic canvas the projection transform is copied to the
1376: * first argument and the second argument is not used. For a stereo
1377: * canvas the first argument receives the left projection transform, and
1378: * if the second argument is non-null it receives the right projection
1379: * transform.<p>
1380: *
1381: * If either of the clip policies <code>VIRTUAL_EYE</code> or
1382: * <code>VIRTUAL_SCREEN</code> are used, then the View should be attached
1383: * to a ViewPlatform that is part of a live scene graph and that has its
1384: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set; otherwise, a
1385: * scale factor of 1.0 will be used for the scale factor from virtual
1386: * world units to view platform units.
1387: *
1388: * @param c3d the Canvas3D to use
1389: * @param cc2vpl the Transform3D to receive left transform
1390: * @param cc2vpr the Transform3D to receive right transform, or null
1391: */
1392: public void getInverseViewPlatformProjection(Canvas3D c3d,
1393: Transform3D cc2vpl, Transform3D cc2vpr) {
1394:
1395: CanvasInfo ci = updateCache(c3d,
1396: "getInverseViewPlatformProjection", true);
1397:
1398: getInverseViewPlatformProjection(ci);
1399: cc2vpl.set(ci.inverseViewPlatformProjection);
1400: if (ci.useStereo & cc2vpr != null)
1401: cc2vpr.set(ci.inverseViewPlatformRightProjection);
1402: }
1403:
1404: private void getInverseViewPlatformProjection(CanvasInfo ci) {
1405: if (ci.updateInverseViewPlatformProjection) {
1406: if (verbose)
1407: System.err.println("updating InverseVpProjection");
1408: if (ci.inverseViewPlatformProjection == null)
1409: ci.inverseViewPlatformProjection = new Transform3D();
1410:
1411: getInverseProjection(ci);
1412: getEyeToViewPlatform(ci);
1413: ci.inverseViewPlatformProjection.mul(ci.eyeToViewPlatform,
1414: ci.inverseProjection);
1415:
1416: if (ci.useStereo) {
1417: if (ci.inverseViewPlatformRightProjection == null)
1418: ci.inverseViewPlatformRightProjection = new Transform3D();
1419:
1420: ci.inverseViewPlatformRightProjection.mul(
1421: ci.rightEyeToViewPlatform,
1422: ci.inverseRightProjection);
1423: }
1424: ci.updateInverseVworldProjection = false;
1425: }
1426: }
1427:
1428: /**
1429: * Gets the transforms from clipping coordinates to virtual world
1430: * coordinates and copies them into the given Transform3Ds. These
1431: * transforms take the clip space volume bounded by the range
1432: * [-1.0 .. +1.0] on each of the X, Y, and Z axes and project into
1433: * the virtual world.<p>
1434: *
1435: * With a monoscopic canvas the projection transform is copied to the
1436: * first argument and the second argument is not used. For a stereo
1437: * canvas the first argument receives the left projection transform, and
1438: * if the second argument is non-null it receives the right projection
1439: * transform.<p>
1440: *
1441: * The View must be attached to a ViewPlatform which is part of a live
1442: * scene graph, and the ViewPlatform node must have its
1443: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set.
1444: *
1445: * @param c3d the Canvas3D to use
1446: * @param cc2vwl the Transform3D to receive left transform
1447: * @param cc2vwr the Transform3D to receive right transform, or null
1448: */
1449: public void getInverseVworldProjection(Canvas3D c3d,
1450: Transform3D cc2vwl, Transform3D cc2vwr) {
1451:
1452: CanvasInfo ci = updateCache(c3d, "getInverseVworldProjection",
1453: true);
1454: getInverseVworldProjection(ci);
1455: cc2vwl.set(ci.inverseVworldProjection);
1456: if (ci.useStereo & cc2vwr != null)
1457: cc2vwr.set(ci.inverseVworldRightProjection);
1458: }
1459:
1460: private void getInverseVworldProjection(CanvasInfo ci) {
1461: if (ci.updateInverseVworldProjection) {
1462: if (verbose)
1463: System.err.println("updating InverseVwProjection");
1464: if (ci.inverseVworldProjection == null)
1465: ci.inverseVworldProjection = new Transform3D();
1466:
1467: getInverseViewPlatformProjection(ci);
1468: ci.inverseVworldProjection.mul(vpi.viewPlatformToVworld,
1469: ci.inverseViewPlatformProjection);
1470:
1471: if (ci.useStereo) {
1472: if (ci.inverseVworldRightProjection == null)
1473: ci.inverseVworldRightProjection = new Transform3D();
1474:
1475: ci.inverseVworldRightProjection.mul(
1476: vpi.viewPlatformToVworld,
1477: ci.inverseViewPlatformRightProjection);
1478: }
1479: ci.updateInverseVworldProjection = false;
1480: }
1481: }
1482:
1483: //
1484: // Compute a projection matrix from the given eye position in image plate,
1485: // the front and back clip Z positions in image plate, and the current
1486: // canvas position in image plate.
1487: //
1488: private void computeProjection(CanvasInfo ci, Point3d eye,
1489: double front, double back, Transform3D p) {
1490:
1491: // Convert everything to eye coordinates.
1492: double lx = ci.canvasX - eye.x; // left (low x)
1493: double ly = ci.canvasY - eye.y; // bottom (low y)
1494: double hx = (ci.canvasX + ci.canvasWidth) - eye.x; // right (high x)
1495: double hy = (ci.canvasY + ci.canvasHeight) - eye.y; // top (high y)
1496: double nz = front - eye.z; // front (near z)
1497: double fz = back - eye.z; // back (far z)
1498: double iz = -eye.z; // plate (image z)
1499:
1500: if (projectionPolicy == View.PERSPECTIVE_PROJECTION)
1501: computePerspectiveProjection(lx, ly, hx, hy, iz, nz, fz,
1502: m16d);
1503: else
1504: computeParallelProjection(lx, ly, hx, hy, nz, fz, m16d);
1505:
1506: p.set(m16d);
1507: }
1508:
1509: //
1510: // Compute a perspective projection from the given eye-space bounds.
1511: //
1512: private void computePerspectiveProjection(double lx, double ly,
1513: double hx, double hy, double iz, double nz, double fz,
1514: double[] m) {
1515: //
1516: // We first derive the X and Y projection components without regard
1517: // for Z scaling. The Z scaling or perspective depth is handled by
1518: // matrix elements expressed solely in terms of the near and far clip
1519: // planes.
1520: //
1521: // Since the eye is at the origin, the projector for any point V in
1522: // eye space is just V. Any point along this ray can be expressed in
1523: // parametric form as P = tV. To find the projection onto the plane
1524: // containing the canvas, find t such that P.z = iz; ie, t = iz/V.z.
1525: // The projection P is thus [V.x*iz/V.z, V.y*iz/V.z, iz].
1526: //
1527: // This projection can expressed as the following matrix equation:
1528: //
1529: // -iz 0 0 0 V.x
1530: // 0 -iz 0 0 X V.y
1531: // 0 0 -iz 0 V.z
1532: // 0 0 -1 0 1 {matrix 1}
1533: //
1534: // where the matrix elements have been negated so that w is positive.
1535: // This is mostly by convention, although some hardware won't handle
1536: // clipping in the -w half-space.
1537: //
1538: // After the point has been projected to the image plate, the
1539: // canvas bounds need to be mapped to the [-1..1] of Java 3D's
1540: // clipping space. The scale factor for X is thus 2/(hx - lx); adding
1541: // the translation results in (V.x - lx)(2/(hx - lx)) - 1, which after
1542: // some algebra can be confirmed to the same as the following
1543: // canonical scale/offset form:
1544: //
1545: // V.x*2/(hx - lx) - (hx + lx)/(hx - lx)
1546: //
1547: // Similarly for Y:
1548: //
1549: // V.y*2/(hy - ly) - (hy + ly)/(hy - ly)
1550: //
1551: // If we set idx = 1/(hx - lx) and idy = 1/(hy - ly), then we get:
1552: //
1553: // 2*V.x*idx - (hx + lx)idx
1554: // 2*V.y*idy - (hy + ly)idy
1555: //
1556: // These scales and offsets are represented by the following matrix:
1557: //
1558: // 2*idx 0 0 -(hx + lx)*idx
1559: // 0 2*idy 0 -(hy + ly)*idy
1560: // 0 0 1 0
1561: // 0 0 0 1 {matrix 2}
1562: //
1563: // The result after concatenating the projection transform
1564: // ({matrix 2} X {matrix 1}):
1565: //
1566: // -2*iz*idx 0 (hx + lx)*idx 0
1567: // 0 -2*iz*idy (hy + ly)*idy 0
1568: // 0 0 -iz {a} 0 {b}
1569: // 0 0 -1 0 {matrix 3}
1570: //
1571: // The Z scaling is handled by m[10] ("a") and m[11] ("b"), which must
1572: // map the range [front..back] to [1..-1] in clipping space. If ze is
1573: // the Z coordinate in eye space, and zc is the Z coordinate in
1574: // clipping space after division by w, then from {matrix 3}:
1575: //
1576: // zc = (a*ze + b)/-ze = -(a + b/ze)
1577: //
1578: // We want this to map to +1 when ze is at the near clip plane, and
1579: // to -1 when ze is at the far clip plane:
1580: //
1581: // -(a + b/nz) = +1
1582: // -(a + b/fz) = -1
1583: //
1584: // Solving results in:
1585: //
1586: // a = -(nz + fz)/(nz - fz)
1587: // b = (2*nz*fz)/(nz - fz).
1588: //
1589: // NOTE: this produces a perspective transform that has matrix
1590: // components with a different scale than the matrix computed by the
1591: // Java 3D core. They do in fact effect the equivalent clipping in 4D
1592: // homogeneous coordinates and project to the same 3D Euclidean
1593: // coordinates. m[14] is always -1 in our derivation above. If the
1594: // matrix components produced by Java 3D core are divided by its value
1595: // of -m[14], then both matrices are the same.
1596: //
1597: double idx = 1.0 / (hx - lx);
1598: double idy = 1.0 / (hy - ly);
1599: double idz = 1.0 / (nz - fz);
1600:
1601: m[0] = -2.0 * iz * idx;
1602: m[5] = -2.0 * iz * idy;
1603: m[2] = (hx + lx) * idx;
1604: m[6] = (hy + ly) * idy;
1605: m[10] = -(nz + fz) * idz;
1606: m[11] = 2.0 * fz * nz * idz;
1607: m[14] = -1.0;
1608: m[1] = m[3] = m[4] = m[7] = m[8] = m[9] = m[12] = m[13] = m[15] = 0.0;
1609: }
1610:
1611: //
1612: // Compute a parallel projection from the given eye-space bounds.
1613: //
1614: private void computeParallelProjection(double lx, double ly,
1615: double hx, double hy, double nz, double fz, double[] m) {
1616: //
1617: // A parallel projection in eye space just involves scales and offsets
1618: // with no w division. We can use {matrix 2} for the X and Y scales
1619: // and offsets and then use a linear mapping of the front and back
1620: // clip distances to the [1..-1] Z clip range.
1621: //
1622: double idx = 1.0 / (hx - lx);
1623: double idy = 1.0 / (hy - ly);
1624: double idz = 1.0 / (nz - fz);
1625:
1626: m[0] = 2.0 * idx;
1627: m[5] = 2.0 * idy;
1628: m[10] = 2.0 * idz;
1629: m[3] = -(hx + lx) * idx;
1630: m[7] = -(hy + ly) * idy;
1631: m[11] = -(nz + fz) * idz;
1632: m[15] = 1.0;
1633: m[1] = m[2] = m[4] = m[6] = m[8] = m[9] = m[12] = m[13] = m[14] = 0.0;
1634: }
1635:
1636: //
1637: // Get front clip plane Z coordinate in image plate space.
1638: //
1639: private double getFrontClip(CanvasInfo ci, Point3d eye) {
1640: if (frontClipPolicy == View.PHYSICAL_EYE
1641: || frontClipPolicy == View.VIRTUAL_EYE) {
1642: return eye.z - ci.frontClipDistance;
1643: } else {
1644: return -ci.frontClipDistance;
1645: }
1646: }
1647:
1648: //
1649: // Get back clip plane Z coordinate in image plate space.
1650: //
1651: private double getBackClip(CanvasInfo ci, Point3d eye) {
1652: //
1653: // Note: Clip node status is unavailable here. If a clip node is
1654: // active in the scene graph, it should override the view's back
1655: // clip plane.
1656: //
1657: if (backClipPolicy == View.PHYSICAL_EYE
1658: || backClipPolicy == View.VIRTUAL_EYE) {
1659: return eye.z - ci.backClipDistance;
1660: } else {
1661: return -ci.backClipDistance;
1662: }
1663: }
1664:
1665: //
1666: // Compute clip distance scale.
1667: //
1668: private double getClipScale(CanvasInfo ci, int clipPolicy) {
1669: if (clipPolicy == View.VIRTUAL_EYE
1670: || clipPolicy == View.VIRTUAL_SCREEN) {
1671: getScreenScale(ci);
1672: if (resizePolicy == View.PHYSICAL_WORLD)
1673: return vpi.vworldToViewPlatformScale * ci.screenScale
1674: * ci.windowScale;
1675: else
1676: return vpi.vworldToViewPlatformScale * ci.screenScale;
1677: } else {
1678: if (resizePolicy == View.PHYSICAL_WORLD)
1679: return ci.windowScale; // see below
1680: else
1681: return 1.0;
1682: }
1683: }
1684:
1685: /**
1686: * Gets the front clip distance scaled to physical meters. This is useful
1687: * for ensuring that objects positioned relative to a physical coordinate
1688: * system (such as eye, image plate, or coexistence) will be within the
1689: * viewable Z depth. This distance will be relative to either the eye or
1690: * the image plate depending upon the front clip policy.<p>
1691: *
1692: * Note that this is not necessarily the clip distance as set by
1693: * <code>setFrontClipDistance</code>, even when the front clip policy
1694: * is <code>PHYSICAL_SCREEN</code> or <code>PHYSICAL_EYE</code>. <i>If
1695: * the window resize policy is <code>PHYSICAL_WORLD</code>, then physical
1696: * clip distances as specified are in fact scaled by the ratio of the
1697: * window width to the screen width.</i> The Java 3D view model does this
1698: * to prevent the physical clip planes from moving with respect to the
1699: * virtual world when the window is resized.<p>
1700: *
1701: * If either of the clip policies <code>VIRTUAL_EYE</code> or
1702: * <code>VIRTUAL_SCREEN</code> are used, then the View should be attached
1703: * to a ViewPlatform that is part of a live scene graph and that has its
1704: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set; otherwise, a
1705: * scale factor of 1.0 will be used for the scale factor from virtual
1706: * world units to view platform units.
1707: *
1708: * @param c3d the Canvas3D to use
1709: * @return the physical front clip distance
1710: */
1711: public double getPhysicalFrontClipDistance(Canvas3D c3d) {
1712: CanvasInfo ci = updateCache(c3d,
1713: "getPhysicalFrontClipDistance", true);
1714:
1715: getClipDistances(ci);
1716: return ci.frontClipDistance;
1717: }
1718:
1719: /**
1720: * Gets the back clip distance scaled to physical meters. This is useful
1721: * for ensuring that objects positioned relative to a physical coordinate
1722: * system (such as eye, image plate, or coexistence) will be within the
1723: * viewable Z depth. This distance will be relative to either the eye or
1724: * the image plate depending upon the back clip policy.<p>
1725: *
1726: * Note that this is not necessarily the clip distance as set by
1727: * <code>setBackClipDistance</code>, even when the back clip policy
1728: * is <code>PHYSICAL_SCREEN</code> or <code>PHYSICAL_EYE</code>. <i>If
1729: * the window resize policy is <code>PHYSICAL_WORLD</code>, then physical
1730: * clip distances as specified are in fact scaled by the ratio of the
1731: * window width to the screen width.</i> The Java 3D view model does this
1732: * to prevent the physical clip planes from moving with respect to the
1733: * virtual world when the window is resized.<p>
1734: *
1735: * If either of the clip policies <code>VIRTUAL_EYE</code> or
1736: * <code>VIRTUAL_SCREEN</code> are used, then the View should be attached
1737: * to a ViewPlatform that is part of a live scene graph and that has its
1738: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set; otherwise, a
1739: * scale factor of 1.0 will be used for the scale factor from virtual
1740: * world units to view platform units.
1741: *
1742: * @param c3d the Canvas3D to use
1743: * @return the physical back clip distance
1744: */
1745: public double getPhysicalBackClipDistance(Canvas3D c3d) {
1746: CanvasInfo ci = updateCache(c3d, "getPhysicalBackClipDistance",
1747: true);
1748: getClipDistances(ci);
1749: return ci.backClipDistance;
1750: }
1751:
1752: private void getClipDistances(CanvasInfo ci) {
1753: if (ci.updateClipDistances) {
1754: if (verbose)
1755: System.err.println("updating clip distances");
1756:
1757: ci.frontClipDistance = view.getFrontClipDistance()
1758: * getClipScale(ci, frontClipPolicy);
1759:
1760: ci.backClipDistance = view.getBackClipDistance()
1761: * getClipScale(ci, backClipPolicy);
1762:
1763: ci.updateClipDistances = false;
1764: if (verbose) {
1765: System.err.println(" front clip distance "
1766: + ci.frontClipDistance);
1767: System.err.println(" back clip distance "
1768: + ci.backClipDistance);
1769: }
1770: }
1771: }
1772:
1773: private void getScreenScale(CanvasInfo ci) {
1774: if (ci.updateScreenScale) {
1775: if (verbose)
1776: System.err.println("updating screen scale");
1777:
1778: if (scalePolicy == View.SCALE_SCREEN_SIZE)
1779: ci.screenScale = ci.si.screenWidth / 2.0;
1780: else
1781: ci.screenScale = view.getScreenScale();
1782:
1783: ci.updateScreenScale = false;
1784: if (verbose)
1785: System.err.println("screen scale " + ci.screenScale);
1786: }
1787: }
1788:
1789: /**
1790: * Gets the scale factor from physical meters to view platform units.<p>
1791: *
1792: * This method requires a Canvas3D. A different scale may be returned
1793: * for each canvas in the view if any of the following apply:<p><ul>
1794: *
1795: * <li>The window resize policy is <code>PHYSICAL_WORLD</code>, which
1796: * alters the scale depending upon the width of the canvas.</li><p>
1797: *
1798: * <li>The screen scale policy is <code>SCALE_SCREEN_SIZE</code>,
1799: * which alters the scale depending upon the width of the screen
1800: * associated with the canvas.</li></ul>
1801: *
1802: * @param c3d the Canvas3D to use
1803: * @return the physical to view platform scale
1804: */
1805: public double getPhysicalToViewPlatformScale(Canvas3D c3d) {
1806: CanvasInfo ci = updateCache(c3d,
1807: "getPhysicalToViewPlatformScale", false);
1808:
1809: getPhysicalToViewPlatformScale(ci);
1810: return ci.physicalToVpScale;
1811: }
1812:
1813: private void getPhysicalToViewPlatformScale(CanvasInfo ci) {
1814: if (ci.updatePhysicalToVpScale) {
1815: if (verbose)
1816: System.err.println("updating PhysicalToVp scale");
1817:
1818: getScreenScale(ci);
1819: if (resizePolicy == View.PHYSICAL_WORLD)
1820: ci.physicalToVpScale = 1.0 / (ci.screenScale * ci.windowScale);
1821: else
1822: ci.physicalToVpScale = 1.0 / ci.screenScale;
1823:
1824: ci.updatePhysicalToVpScale = false;
1825: if (verbose)
1826: System.err.println("PhysicalToVp scale "
1827: + ci.physicalToVpScale);
1828: }
1829: }
1830:
1831: /**
1832: * Gets the scale factor from physical meters to virtual units.<p>
1833: *
1834: * This method requires a Canvas3D. A different scale may be returned
1835: * across canvases for the same reasons as discussed in the description of
1836: * <code>getPhysicalToViewPlatformScale</code>.<p>
1837: *
1838: * The View must be attached to a ViewPlatform which is part of a live
1839: * scene graph, and the ViewPlatform node must have its
1840: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set.
1841: *
1842: * @param c3d the Canvas3D to use
1843: * @return the physical to virtual scale
1844: * @see #getPhysicalToViewPlatformScale
1845: * getPhysicalToViewPlatformScale(Canvas3D)
1846: */
1847: public double getPhysicalToVirtualScale(Canvas3D c3d) {
1848: CanvasInfo ci = updateCache(c3d, "getPhysicalToVirtualScale",
1849: true);
1850: getPhysicalToVirtualScale(ci);
1851: return ci.physicalToVirtualScale;
1852: }
1853:
1854: private void getPhysicalToVirtualScale(CanvasInfo ci) {
1855: if (ci.updatePhysicalToVirtualScale) {
1856: if (verbose)
1857: System.err.println("updating PhysicalToVirtual scale");
1858:
1859: getPhysicalToViewPlatformScale(ci);
1860: ci.physicalToVirtualScale = ci.physicalToVpScale
1861: / vpi.vworldToViewPlatformScale;
1862:
1863: ci.updatePhysicalToVirtualScale = false;
1864: if (verbose)
1865: System.err.println("PhysicalToVirtual scale "
1866: + ci.physicalToVirtualScale);
1867: }
1868: }
1869:
1870: /**
1871: * Gets the width of the specified canvas scaled to physical meters. This
1872: * is derived from the physical screen width as reported by the Screen3D
1873: * associated with the canvas. If the screen width is not explicitly set
1874: * using the <code>setPhysicalScreenWidth</code> method of Screen3D, then
1875: * Java 3D will derive the screen width based on a screen resolution of 90
1876: * pixels/inch.
1877: *
1878: * @param c3d the Canvas3D to use
1879: * @return the width of the canvas scaled to physical meters
1880: */
1881: public double getPhysicalWidth(Canvas3D c3d) {
1882: CanvasInfo ci = updateCache(c3d, "getPhysicalWidth", false);
1883: return ci.canvasWidth;
1884: }
1885:
1886: /**
1887: * Gets the height of the specified canvas scaled to physical meters. This
1888: * is derived from the physical screen height as reported by the Screen3D
1889: * associated with the canvas. If the screen height is not explicitly set
1890: * using the <code>setPhysicalScreenHeight</code> method of Screen3D, then
1891: * Java 3D will derive the screen height based on a screen resolution of 90
1892: * pixels/inch.
1893: *
1894: * @param c3d the Canvas3D to use
1895: * @return the height of the canvas scaled to physical meters
1896: */
1897: public double getPhysicalHeight(Canvas3D c3d) {
1898: CanvasInfo ci = updateCache(c3d, "getPhysicalHeight", false);
1899: return ci.canvasHeight;
1900: }
1901:
1902: /**
1903: * Gets the location of the specified canvas relative to the image plate
1904: * origin. This is derived from the physical screen parameters as
1905: * reported by the Screen3D associated with the canvas. If the screen
1906: * width and height are not explicitly set in Screen3D, then Java 3D will
1907: * derive those screen parameters based on a screen resolution of 90
1908: * pixels/inch.
1909: *
1910: * @param c3d the Canvas3D to use
1911: * @param location the output position, in meters, of the lower-left
1912: * corner of the canvas relative to the image plate lower-left corner; Z
1913: * is always 0.0
1914: */
1915: public void getPhysicalLocation(Canvas3D c3d, Point3d location) {
1916: CanvasInfo ci = updateCache(c3d, "getPhysicalLocation", false);
1917: location.set(ci.canvasX, ci.canvasY, 0.0);
1918: }
1919:
1920: /**
1921: * Gets the location of the AWT pixel value and copies it into the
1922: * specified Point3d.
1923: *
1924: * @param c3d the Canvas3D to use
1925: * @param x the X coordinate of the pixel relative to the upper-left
1926: * corner of the canvas
1927: * @param y the Y coordinate of the pixel relative to the upper-left
1928: * corner of the canvas
1929: * @param location the output position, in meters, relative to the
1930: * lower-left corner of the image plate; Z is always 0.0
1931: */
1932: public void getPixelLocationInImagePlate(Canvas3D c3d, int x,
1933: int y, Point3d location) {
1934:
1935: CanvasInfo ci = updateCache(c3d,
1936: "getPixelLocationInImagePlate", false);
1937:
1938: location.set(ci.canvasX + ((double) x * ci.si.metersPerPixelX),
1939: ci.canvasY - ((double) y * ci.si.metersPerPixelY)
1940: + ci.canvasHeight, 0.0);
1941: }
1942:
1943: /**
1944: * Gets a read from the specified sensor and transforms it to virtual
1945: * world coordinates. The View must be attached to a ViewPlatform which
1946: * is part of a live scene graph, and the ViewPlatform node must have its
1947: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set.<p>
1948: *
1949: * This method requires a Canvas3D. The returned transform may differ
1950: * across canvases for the same reasons as discussed in the description of
1951: * <code>getViewPlatformToCoexistence</code>.
1952: *
1953: * @param sensor the Sensor instance to read
1954: * @param s2vw the output transform
1955: * @see #getViewPlatformToCoexistence
1956: * getViewPlatformToCoexistence(Canvas3D, Transform3D)
1957: */
1958: public void getSensorToVworld(Canvas3D c3d, Sensor sensor,
1959: Transform3D s2vw) {
1960:
1961: CanvasInfo ci = updateCache(c3d, "getSensorToVworld", true);
1962: getTrackerBaseToVworld(ci);
1963: sensor.getRead(s2vw);
1964: s2vw.mul(ci.trackerBaseToVworld, s2vw);
1965: }
1966:
1967: /**
1968: * Gets the transform from tracker base coordinates to view platform
1969: * coordinates and copies it into the specified Transform3D.<p>
1970: *
1971: * This method requires a Canvas3D. The returned transform may differ
1972: * across canvases for the same reasons as discussed in the description of
1973: * <code>getViewPlatformToCoexistence</code>.
1974: *
1975: * @param c3d the Canvas3D to use
1976: * @param tb2vp the output transform
1977: * @see #getViewPlatformToCoexistence
1978: * getViewPlatformToCoexistence(Canvas3D, Transform3D)
1979: */
1980: public void getTrackerBaseToViewPlatform(Canvas3D c3d,
1981: Transform3D tb2vp) {
1982: CanvasInfo ci = updateCache(c3d,
1983: "getTrackerBaseToViewPlatform", false);
1984:
1985: getTrackerBaseToViewPlatform(ci);
1986: tb2vp.set(ci.trackerBaseToViewPlatform);
1987: }
1988:
1989: private void getTrackerBaseToViewPlatform(CanvasInfo ci) {
1990: if (ci.updateTrackerBaseToViewPlatform) {
1991: if (verbose)
1992: System.err.println("updating TrackerBaseToVp");
1993: if (ci.trackerBaseToViewPlatform == null)
1994: ci.trackerBaseToViewPlatform = new Transform3D();
1995:
1996: getViewPlatformToCoexistence(ci);
1997: ci.trackerBaseToViewPlatform.mul(coeToTrackerBase,
1998: ci.viewPlatformToCoe);
1999:
2000: ci.trackerBaseToViewPlatform.invert();
2001: ci.updateTrackerBaseToViewPlatform = false;
2002: if (verbose)
2003: t3dPrint(ci.trackerBaseToViewPlatform,
2004: "TrackerBaseToViewPlatform");
2005: }
2006: }
2007:
2008: /**
2009: * Gets the transform from tracker base coordinates to virtual world
2010: * coordinates and copies it into the specified Transform3D. The View
2011: * must be attached to a ViewPlatform which is part of a live scene graph,
2012: * and the ViewPlatform node must have its
2013: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set.<p>
2014: *
2015: * This method requires a Canvas3D. The returned transform may differ
2016: * across canvases for the same reasons as discussed in the description of
2017: * <code>getViewPlatformToCoexistence</code>.
2018: *
2019: * @param c3d the Canvas3D to use
2020: * @param tb2vw the output transform
2021: * @see #getViewPlatformToCoexistence
2022: * getViewPlatformToCoexistence(Canvas3D, Transform3D)
2023: */
2024: public void getTrackerBaseToVworld(Canvas3D c3d, Transform3D tb2vw) {
2025: CanvasInfo ci = updateCache(c3d, "getTrackerBaseToVworld", true);
2026: getTrackerBaseToVworld(ci);
2027: tb2vw.set(ci.trackerBaseToVworld);
2028: }
2029:
2030: private void getTrackerBaseToVworld(CanvasInfo ci) {
2031: if (ci.updateTrackerBaseToVworld) {
2032: if (verbose)
2033: System.err.println("updating TrackerBaseToVworld");
2034: if (ci.trackerBaseToVworld == null)
2035: ci.trackerBaseToVworld = new Transform3D();
2036: //
2037: // We compute trackerBaseToViewPlatform and compose with
2038: // viewPlatformToVworld instead of computing imagePlateToVworld
2039: // and composing with trackerBaseToImagePlate. That way it works
2040: // with HMD and avoids the issue of choosing the left image plate
2041: // or right image plate transform.
2042: //
2043: getTrackerBaseToViewPlatform(ci);
2044: ci.trackerBaseToVworld.mul(vpi.viewPlatformToVworld,
2045: ci.trackerBaseToViewPlatform);
2046:
2047: ci.updateTrackerBaseToVworld = false;
2048: }
2049: }
2050:
2051: /**
2052: * Release all static memory references held by ViewInfo, if any. These
2053: * are the Screen3D and ViewPlatform maps shared by all existing ViewInfo
2054: * instances if they're not provided by a constructor. Releasing the
2055: * screen references effectively releases all canvas references in all
2056: * ViewInfo instances as well.<p>
2057: *
2058: * It is safe to continue using existing ViewInfo instances after calling
2059: * this method; the data in the released maps will be re-derived as
2060: * needed.
2061: */
2062: public static synchronized void clear() {
2063: Iterator i = staticVpMap.values().iterator();
2064: while (i.hasNext())
2065: ((ViewPlatformInfo) i.next()).clear();
2066: staticVpMap.clear();
2067:
2068: i = staticSiMap.values().iterator();
2069: while (i.hasNext())
2070: ((ScreenInfo) i.next()).clear();
2071: staticSiMap.clear();
2072: }
2073:
2074: /**
2075: * Arrange for an update of cached screen parameters. If automatic update
2076: * has not been enabled, then this method should be called if any of the
2077: * attributes of the Screen3D have changed. This method should also be
2078: * called if the screen changes pixel resolution.
2079: *
2080: * @param s3d the Screen3D to update
2081: */
2082: public void updateScreen(Screen3D s3d) {
2083: if (verbose)
2084: System.err.println("updateScreen");
2085: ScreenInfo si = (ScreenInfo) screenMap.get(s3d);
2086: if (si != null)
2087: si.updateScreen = true;
2088: }
2089:
2090: /**
2091: * Arrange for an update of cached canvas parameters. If automatic update
2092: * has not been enabled, then this method should be called if any of the
2093: * attributes of the Canvas3D have changed. These attributes include the
2094: * canvas position and size, but do <i>not</i> include the attributes of
2095: * the associated Screen3D, which are cached separately.
2096: *
2097: * @param c3d the Canvas3D to update
2098: */
2099: public void updateCanvas(Canvas3D c3d) {
2100: if (verbose)
2101: System.err.println("updateCanvas");
2102: CanvasInfo ci = (CanvasInfo) canvasMap.get(c3d);
2103: if (ci != null)
2104: ci.updateCanvas = true;
2105: }
2106:
2107: /**
2108: * Arrange for an update of cached view parameters. If automatic update
2109: * has not been enabled for the View, then this method should be called if
2110: * any of the attributes of the View associated with this object have
2111: * changed.<p>
2112: *
2113: * These do <i>not</i> include the attributes of the existing Canvas3D or
2114: * Screen3D components of the View, but do include the attributes of all
2115: * other components such as the PhysicalEnvironment and PhysicalBody, and
2116: * all attributes of the attached ViewPlatform except for its
2117: * <code>localToVworld</code> transform. The screen and canvas components
2118: * as well as the ViewPlatform's <code>localToVworld</code> are cached
2119: * separately.<p>
2120: *
2121: * This method should also be called if the ViewPlatform is replaced with
2122: * another using the View's <code>attachViewPlatform</code> method, or if
2123: * any of the <code>setCanvas3D</code>, <code>addCanvas3D</code>,
2124: * <code>insertCanvas3D</code>, <code>removeCanvas3D</code>, or
2125: * <code>removeAllCanvas3Ds</code> methods of View are called to change
2126: * the View's canvas list.<p>
2127: *
2128: * Calling this method causes most transforms to be re-derived. It should
2129: * be used only when necessary.
2130: */
2131: public void updateView() {
2132: if (verbose)
2133: System.err.println("updateView");
2134: this .updateView = true;
2135: }
2136:
2137: /**
2138: * Arrange for an update of the cached head position if head tracking is
2139: * enabled. If automatic update has not enabled for the head position,
2140: * then this method should be called anytime a new head position is to be
2141: * read.
2142: */
2143: public void updateHead() {
2144: if (verbose)
2145: System.err.println("updateHead");
2146: this .updateHead = true;
2147: }
2148:
2149: /**
2150: * Arrange for an update of the cached <code>localToVworld</code>
2151: * transform of the view platform. If automatic update has not been
2152: * enabled for this transform, then this method should be called anytime
2153: * the view platform has been repositioned in the virtual world and a
2154: * transform involving virtual world coordinates is desired.<p>
2155: *
2156: * The View must be attached to a ViewPlatform which is part of a live
2157: * scene graph, and the ViewPlatform node must have its
2158: * <code>ALLOW_LOCAL_TO_VWORLD_READ</code> capability set.
2159: */
2160: public void updateViewPlatform() {
2161: if (verbose)
2162: System.err.println("updateViewPlatform");
2163: vpi.updateViewPlatformToVworld = true;
2164: }
2165:
2166: //
2167: // Set cache update bits based on auto update flags.
2168: // VIEW_AUTO_UPDATE is handled in updateCache().
2169: //
2170: private void getAutoUpdate(CanvasInfo ci) {
2171: if ((autoUpdateFlags & SCREEN_AUTO_UPDATE) != 0)
2172: ci.si.updateScreen = true;
2173:
2174: if ((autoUpdateFlags & CANVAS_AUTO_UPDATE) != 0)
2175: ci.updateCanvas = true;
2176:
2177: if ((autoUpdateFlags & PLATFORM_AUTO_UPDATE) != 0)
2178: vpi.updateViewPlatformToVworld = true;
2179:
2180: if ((autoUpdateFlags & HEAD_AUTO_UPDATE) != 0)
2181: this .updateHead = true;
2182: }
2183:
2184: //
2185: // Update any changed cached data. This takes a Canvas3D instance. The
2186: // cache mechanism could have used a Canvas3D index into the View instead,
2187: // but the direct reference is probably more convenient for applications.
2188: //
2189: private CanvasInfo updateCache(Canvas3D c3d, String name,
2190: boolean vworld) {
2191: if (verbose) {
2192: System.err.println("updateCache: " + name + " in "
2193: + hashCode());
2194: System.err.println(" canvas " + c3d.hashCode());
2195: }
2196:
2197: // The View may have had Canvas3D instances added or removed, or may
2198: // have been attached to a different ViewPlatform, so update the view
2199: // before anything else.
2200: if (updateView || (autoUpdateFlags & VIEW_AUTO_UPDATE) != 0)
2201: getViewInfo();
2202:
2203: // Now get the CanvasInfo to update.
2204: CanvasInfo ci = (CanvasInfo) canvasMap.get(c3d);
2205: if (ci == null)
2206: throw new IllegalArgumentException(
2207: "Specified Canvas3D is not a component of the View");
2208:
2209: // Check rest of autoUpdateFlags.
2210: if (autoUpdate)
2211: getAutoUpdate(ci);
2212:
2213: // Update the screen, canvas, view platform, and head caches.
2214: if (ci.si.updateScreen)
2215: ci.si.getScreenInfo();
2216:
2217: if (ci.updateCanvas)
2218: ci.getCanvasInfo();
2219:
2220: if (vworld && vpi.updateViewPlatformToVworld)
2221: vpi.getViewPlatformToVworld();
2222:
2223: if (useTracking && updateHead)
2224: getHeadInfo();
2225:
2226: // Return the CanvasInfo instance.
2227: return ci;
2228: }
2229:
2230: //
2231: // Get physical view parameters and derived data. This is a fairly
2232: // heavyweight method -- everything gets marked for update since we don't
2233: // currently track changes in individual view attributes. Fortunately
2234: // there shouldn't be a need to call it very often.
2235: //
2236: private void getViewInfo() {
2237: if (verbose)
2238: System.err.println(" getViewInfo");
2239:
2240: // Check if an update of the Canvas3D collection is needed.
2241: if (this .canvasCount != view.numCanvas3Ds()) {
2242: this .canvasCount = view.numCanvas3Ds();
2243: getCanvases();
2244: } else {
2245: for (int i = 0; i < canvasCount; i++) {
2246: if (canvasMap.get(view.getCanvas3D(i)) != canvasInfo[i]) {
2247: getCanvases();
2248: break;
2249: }
2250: }
2251: }
2252:
2253: // Update the ViewPlatform.
2254: getViewPlatform();
2255:
2256: // Update the PhysicalBody and PhysicalEnvironment.
2257: this .body = view.getPhysicalBody();
2258: this .env = view.getPhysicalEnvironment();
2259:
2260: // Use the result of the possibly overridden method useHeadTracking()
2261: // to determine if head tracking is to be used within ViewInfo.
2262: this .useTracking = useHeadTracking();
2263:
2264: // Get the head tracker only if really available.
2265: if (view.getTrackingEnable() && env.getTrackingAvailable()) {
2266: int headIndex = env.getHeadIndex();
2267: this .headTracker = env.getSensor(headIndex);
2268: }
2269:
2270: // Get the new policies and update data derived from them.
2271: this .viewPolicy = view.getViewPolicy();
2272: this .projectionPolicy = view.getProjectionPolicy();
2273: this .resizePolicy = view.getWindowResizePolicy();
2274: this .movementPolicy = view.getWindowMovementPolicy();
2275: this .eyePolicy = view.getWindowEyepointPolicy();
2276: this .scalePolicy = view.getScreenScalePolicy();
2277: this .backClipPolicy = view.getBackClipPolicy();
2278: this .frontClipPolicy = view.getFrontClipPolicy();
2279:
2280: if (useTracking || viewPolicy == View.HMD_VIEW) {
2281: if (this .headToHeadTracker == null)
2282: this .headToHeadTracker = new Transform3D();
2283: if (this .headTrackerToTrackerBase == null)
2284: this .headTrackerToTrackerBase = new Transform3D();
2285:
2286: if (viewPolicy == View.HMD_VIEW) {
2287: if (this .trackerBaseToHeadTracker == null)
2288: this .trackerBaseToHeadTracker = new Transform3D();
2289: if (this .coeToHeadTracker == null)
2290: this .coeToHeadTracker = new Transform3D();
2291: } else {
2292: if (this .headToTrackerBase == null)
2293: this .headToTrackerBase = new Transform3D();
2294: }
2295:
2296: body.getLeftEyePosition(this .leftEyeInHead);
2297: body.getRightEyePosition(this .rightEyeInHead);
2298: body.getHeadToHeadTracker(this .headToHeadTracker);
2299:
2300: if (verbose) {
2301: System.err.println(" leftEyeInHead "
2302: + leftEyeInHead);
2303: System.err.println(" rightEyeInHead "
2304: + rightEyeInHead);
2305: t3dPrint(headToHeadTracker, " headToHeadTracker");
2306: }
2307: }
2308:
2309: if (eyePolicy == View.RELATIVE_TO_WINDOW
2310: || eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) {
2311: body.getLeftEyePosition(this .leftEyeInHead);
2312: body.getRightEyePosition(this .rightEyeInHead);
2313: if (verbose) {
2314: System.err.println(" leftEyeInHead "
2315: + leftEyeInHead);
2316: System.err.println(" rightEyeInHead "
2317: + rightEyeInHead);
2318: }
2319: }
2320:
2321: if ((env.getCoexistenceCenterInPworldPolicy() != View.NOMINAL_SCREEN)
2322: || (viewPolicy == View.HMD_VIEW))
2323: this .coeCentering = false;
2324: else
2325: this .coeCentering = view.getCoexistenceCenteringEnable();
2326:
2327: if (!coeCentering || useTracking) {
2328: if (this .coeToTrackerBase == null)
2329: this .coeToTrackerBase = new Transform3D();
2330:
2331: env.getCoexistenceToTrackerBase(this .coeToTrackerBase);
2332: if (verbose)
2333: t3dPrint(coeToTrackerBase, " coeToTrackerBase");
2334: }
2335:
2336: if (backClipPolicy == View.VIRTUAL_EYE
2337: || backClipPolicy == View.VIRTUAL_SCREEN
2338: || frontClipPolicy == View.VIRTUAL_EYE
2339: || frontClipPolicy == View.VIRTUAL_SCREEN) {
2340: this .clipVirtual = true;
2341: } else {
2342: this .clipVirtual = false;
2343: }
2344:
2345: // Propagate view updates to each canvas.
2346: for (int i = 0; i < canvasCount; i++)
2347: this .canvasInfo[i].updateViewDependencies();
2348:
2349: this .updateView = false;
2350: if (verbose) {
2351: System.err.println(" tracking " + useTracking);
2352: System.err.println(" coeCentering " + coeCentering);
2353: System.err.println(" clipVirtual " + clipVirtual);
2354: }
2355: }
2356:
2357: //
2358: // Each view can have multiple canvases, each with an associated screen.
2359: // Each canvas is associated with only one view. Each screen can have
2360: // multiple canvases that are used across multiple views. We rebuild the
2361: // canvas info instead of trying to figure out what canvases have been
2362: // added or removed from the view.
2363: //
2364: private void getCanvases() {
2365: if (this .canvasInfo.length < canvasCount) {
2366: this .canvasInfo = new CanvasInfo[canvasCount];
2367: }
2368:
2369: for (int i = 0; i < canvasCount; i++) {
2370: Canvas3D c3d = view.getCanvas3D(i);
2371: Screen3D s3d = c3d.getScreen3D();
2372:
2373: // Check if we have a new screen.
2374: ScreenInfo si = (ScreenInfo) screenMap.get(s3d);
2375: if (si == null) {
2376: si = new ScreenInfo(s3d, c3d.getGraphicsConfiguration());
2377: screenMap.put(s3d, si);
2378: }
2379:
2380: // Check to see if we've encountered the screen so far in this
2381: // loop over the view's canvases. If not, clear the screen's list
2382: // of canvases for this ViewInfo.
2383: if (newSet.add(si))
2384: si.clear(this );
2385:
2386: // Check if this is a new canvas.
2387: CanvasInfo ci = (CanvasInfo) canvasMap.get(c3d);
2388: if (ci == null)
2389: ci = new CanvasInfo(c3d, si);
2390:
2391: // Add this canvas to the screen's list for this ViewInfo.
2392: si.addCanvasInfo(this , ci);
2393:
2394: // Add this canvas to the new canvas map and canvas array.
2395: this .newMap.put(c3d, ci);
2396: this .canvasInfo[i] = ci;
2397: }
2398:
2399: // Null out old references if canvas count shrinks.
2400: for (int i = canvasCount; i < canvasInfo.length; i++)
2401: this .canvasInfo[i] = null;
2402:
2403: // Update the CanvasInfo map.
2404: Map tmp = canvasMap;
2405: this .canvasMap = newMap;
2406: this .newMap = tmp;
2407:
2408: // Clear the temporary collections.
2409: this .newMap.clear();
2410: this .newSet.clear();
2411: }
2412:
2413: //
2414: // Force the creation of new CanvasInfo instances. This is called when a
2415: // screen is removed from the screen map.
2416: //
2417: private void clearCanvases() {
2418: this .canvasCount = 0;
2419: this .canvasMap.clear();
2420: this .updateView = true;
2421: }
2422:
2423: //
2424: // Update the view platform. Each view can be attached to only one, but
2425: // each view platform can have many views attached.
2426: //
2427: private void getViewPlatform() {
2428: ViewPlatform vp = view.getViewPlatform();
2429: if (vp == null)
2430: throw new IllegalStateException(
2431: "The View must be attached to a ViewPlatform");
2432:
2433: ViewPlatformInfo tmpVpi = (ViewPlatformInfo) viewPlatformMap
2434: .get(vp);
2435:
2436: if (tmpVpi == null) {
2437: // We haven't encountered this ViewPlatform before.
2438: tmpVpi = new ViewPlatformInfo(vp);
2439: viewPlatformMap.put(vp, tmpVpi);
2440: }
2441:
2442: if (this .vpi != tmpVpi) {
2443: // ViewPlatform has changed. Could set an update flag here if it
2444: // would be used, but updating the view updates everything anyway.
2445: if (this .vpi != null) {
2446: // Remove this ViewInfo from the list of Views attached to the
2447: // old ViewPlatform.
2448: this .vpi.removeViewInfo(this );
2449: }
2450: this .vpi = tmpVpi;
2451: this .vpi.addViewInfo(this );
2452:
2453: // updateViewPlatformToVworld is initially set false since the
2454: // capability to read the vworld transform may not be
2455: // available. If it is, set it here.
2456: if (vp.getCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ)) {
2457: this .vpi.updateViewPlatformToVworld = true;
2458: if (verbose)
2459: System.err.println(" vworld read allowed");
2460: } else if (verbose)
2461: System.err.println(" vworld read disallowed");
2462: }
2463: }
2464:
2465: //
2466: // Force the creation of a new ViewPlatformInfo when a view platform is
2467: // removed from the view platform map.
2468: //
2469: private void clearViewPlatform() {
2470: this .updateView = true;
2471: }
2472:
2473: //
2474: // Update vworld dependencies for this ViewInfo -- called by
2475: // ViewPlatformInfo.getViewPlatformToVworld().
2476: //
2477: private void updateVworldDependencies() {
2478: for (int i = 0; i < canvasCount; i++)
2479: this .canvasInfo[i].updateVworldDependencies();
2480: }
2481:
2482: /**
2483: * Returns a reference to a Transform3D containing the current transform
2484: * from head tracker coordinates to tracker base coordinates. It is only
2485: * called if <code>useHeadTracking</code> returns true and a head position
2486: * update is specified with <code>updateHead</code> or the
2487: * <code>HEAD_AUTO_UPDATE</code> constructor flag.<p>
2488: *
2489: * The default implementation uses the head tracking sensor specified by
2490: * the View's PhysicalEnvironment, and reads it by calling the sensor's
2491: * <code>getRead</code> method directly. The result is a sensor reading
2492: * that may have been taken at a slightly different time from the one used
2493: * by the renderer. This method can be overridden to synchronize the two
2494: * readings through an external mechanism.
2495: *
2496: * @return current head tracker to tracker base transform
2497: * @see #useHeadTracking
2498: * @see #updateHead
2499: * @see #HEAD_AUTO_UPDATE
2500: */
2501: protected Transform3D getHeadTrackerToTrackerBase() {
2502: headTracker.getRead(this .headTrackerToTrackerBase);
2503: return this .headTrackerToTrackerBase;
2504: }
2505:
2506: /**
2507: * Returns <code>true</code> if head tracking should be used.<p>
2508: *
2509: * The default implementation returns <code>true</code> if the View's
2510: * <code>getTrackingEnable</code> method and the PhysicalEnvironment's
2511: * <code>getTrackingAvailable</code> method both return <code>true</code>.
2512: * These are the same conditions under which the Java 3D renderer uses
2513: * head tracking. This method can be overridden if there is any need to
2514: * decouple the head tracking status of ViewInfo from the renderer.
2515: *
2516: * @return <code>true</code> if ViewInfo should use head tracking
2517: */
2518: protected boolean useHeadTracking() {
2519: return view.getTrackingEnable() && env.getTrackingAvailable();
2520: }
2521:
2522: //
2523: // Cache the current tracked head position and derived data.
2524: //
2525: private void getHeadInfo() {
2526: if (verbose)
2527: System.err.println(" getHeadInfo");
2528:
2529: this .headTrackerToTrackerBase = getHeadTrackerToTrackerBase();
2530: if (viewPolicy == View.HMD_VIEW) {
2531: this .trackerBaseToHeadTracker
2532: .invert(headTrackerToTrackerBase);
2533: this .coeToHeadTracker.mul(trackerBaseToHeadTracker,
2534: coeToTrackerBase);
2535: } else {
2536: this .headToTrackerBase.mul(headTrackerToTrackerBase,
2537: headToHeadTracker);
2538: }
2539: for (int i = 0; i < canvasCount; i++)
2540: this .canvasInfo[i].updateHeadDependencies();
2541:
2542: this .updateHead = false;
2543: //
2544: // The head position used by the Java 3D renderer isn't accessible
2545: // in the public API. A head tracker generates continuous data, so
2546: // getting the same sensor read as the renderer is unlikely.
2547: //
2548: // Possible workaround: for fixed screens, get the Java 3D
2549: // renderer's version of plateToVworld and headToVworld by calling
2550: // Canvas3D.getImagePlateToVworld() and View.getUserHeadToVworld().
2551: // Although the vworld components will have frame latency, they can
2552: // be cancelled out by inverting the former transform and
2553: // multiplying by the latter, resulting in userHeadToImagePlate,
2554: // which can then be transformed to tracker base coordinates.
2555: //
2556: // For head mounted displays, the head to image plate transforms are
2557: // just calibration constants, so they're of no use. There are more
2558: // involved workarounds possible, but one that may work for both fixed
2559: // screens and HMD is to define a SensorInterposer class that extends
2560: // Sensor. Take the View's head tracking sensor, use it to construct
2561: // a SensorInterposer, and then replace the head tracking sensor with
2562: // the SensorInterposer. SensorInterposer can then override the
2563: // getRead() methods and thus control what the Java 3D renderer gets.
2564: // getHeadTrackerToTrackerBase() is a protected method in ViewInfo
2565: // which can be overridden to call a variant of getRead() so that
2566: // calls from ViewInfo and from the renderer can be distinguished.
2567: //
2568: // Even if getting the same head position as used by the renderer is
2569: // achieved, tracked eye space interactions with objects in the
2570: // virtual world still can't be synchronized with rendering. This
2571: // means that objects in the virtual world cannot be made to appear in
2572: // a fixed position relative to the tracked head position without a
2573: // frame lag between them.
2574: //
2575: // The reason for this is that the tracked head position used by the
2576: // Java 3D renderer is updated asynchronously from scene graph
2577: // updates. This is done to reduce latency between the user's
2578: // position and the rendered image, which is directly related to the
2579: // quality of the immersive virtual reality experience. So while an
2580: // update to the scene graph may have a frame latency before it gets
2581: // rendered, a change to the user's tracked position is always
2582: // reflected in the current frame.
2583: //
2584: // This problem can't be fixed without eliminating the frame latency
2585: // in the Java 3D internal state, although there are possible
2586: // workarounds at the expense of increased user position latency.
2587: // These involve disabling tracking, reading the head sensor directly,
2588: // performing whatever eye space interactions are necessary with the
2589: // virtual world (using the view platform's current localToVworld),
2590: // and then propagating the head position change to the renderer
2591: // manually through a behavior post mechanism that delays it by a
2592: // frame.
2593: //
2594: // For example, with head tracking in a fixed screen environment (such
2595: // as a CAVE), disable Java 3D head tracking and set the View's window
2596: // eyepoint policy to RELATIVE_TO_COEXISTENCE. Read the sensor to get
2597: // the head position relative to the tracker base, transform it to
2598: // coexistence coordinates using the inverse of the value of the
2599: // coexistenceToTrackerBase transform, and then set the eye positions
2600: // manually with the View's set{Left,Right}ManualEyeInCoexistence
2601: // methods. If these method calls are delayed through a behavior post
2602: // mechanism, then they will be synchronized with the rendering of the
2603: // scene graph updates.
2604: //
2605: // With a head mounted display the sensor can be read directly to get
2606: // the head position relative to the tracker base. If Java 3D's head
2607: // tracking is disabled, it uses identity for the current
2608: // headTrackerToTrackerBase transform. It concatenates its inverse,
2609: // trackerBaseToHeadTracker, with coexistenceToTrackerBase to get the
2610: // image plate positions in coexistence; the former transform is
2611: // inaccessible, but the latter can be set through the
2612: // PhysicalEnvironment. So the workaround is to maintain a local copy
2613: // with the real value of coexistenceToTrackerBase, but set the
2614: // PhysicalEnvironment copy to the product of the real value and the
2615: // trackerBaseToHeadTracker inverted from the sensor read. Like the
2616: // CAVE example, this update to the View would have to be delayed in
2617: // order to synchronize with scene graph updates.
2618: //
2619: // Another possibility is to put the Java 3D view model in
2620: // compatibility mode, where it accepts vpcToEye and eyeToCc
2621: // (projection) directly. The various view attributes can still be
2622: // set and accessed, but will be ignored by the Java 3D view model.
2623: // The ViewInfo methods can be used to compute the view and projection
2624: // matrices, which can then be delayed to synchronize with the scene
2625: // graph.
2626: //
2627: // Note that these workarounds could be used to make view-dependent
2628: // scene graph updates consistent, but they still can't do anything
2629: // about synchronizing the actual physical position of the user with
2630: // the rendered images. That requires zero latency between position
2631: // update and scene graph state.
2632: //
2633: // Still another possibility: extrapolate the position of the user
2634: // into the next few frames from a sample of recently recorded
2635: // positions. Unfortunately, that is also a very hard problem. The
2636: // Java 3D Sensor API is designed to support prediction but it was
2637: // never realized successfully in the sample implementation.
2638: }
2639:
2640: //
2641: // A per-screen cache, shared between ViewInfo instances. In the Java 3D
2642: // view model a single screen can be associated with multiple canvas
2643: // and view instances.
2644: //
2645: private static class ScreenInfo {
2646: private Screen3D s3d = null;
2647: private GraphicsConfiguration graphicsConfiguration = null;
2648: private boolean updateScreen = true;
2649:
2650: private Map viewInfoMap = new HashMap();
2651: private List viewInfoList = new LinkedList();
2652: private Transform3D t3d = new Transform3D();
2653:
2654: private double screenWidth = 0.0;
2655: private double screenHeight = 0.0;
2656: private boolean updateScreenSize = true;
2657:
2658: private Rectangle screenBounds = null;
2659: private double metersPerPixelX = 0.0;
2660: private double metersPerPixelY = 0.0;
2661: private boolean updatePixelSize = true;
2662:
2663: // These transforms are pre-allocated here since they are required by
2664: // some view policies and we don't know what views this screen will be
2665: // attached to. Their default identity values are used if not
2666: // explicitly set. TODO: allocate if needed in getCanvasInfo(), where
2667: // view information will be available.
2668: private Transform3D trackerBaseToPlate = new Transform3D();
2669: private Transform3D headTrackerToLeftPlate = new Transform3D();
2670: private Transform3D headTrackerToRightPlate = new Transform3D();
2671: private boolean updateTrackerBaseToPlate = false;
2672: private boolean updateHeadTrackerToPlate = false;
2673:
2674: private ScreenInfo(Screen3D s3d, GraphicsConfiguration gc) {
2675: this .s3d = s3d;
2676: this .graphicsConfiguration = gc;
2677: if (verbose)
2678: System.err.println(" ScreenInfo: init "
2679: + s3d.hashCode());
2680: }
2681:
2682: private List getCanvasList(ViewInfo vi) {
2683: List canvasList = (List) viewInfoMap.get(vi);
2684: if (canvasList == null) {
2685: canvasList = new LinkedList();
2686: viewInfoMap.put(vi, canvasList);
2687: viewInfoList.add(canvasList);
2688: }
2689: return canvasList;
2690: }
2691:
2692: private synchronized void clear(ViewInfo vi) {
2693: getCanvasList(vi).clear();
2694: }
2695:
2696: private synchronized void clear() {
2697: Iterator i = viewInfoMap.keySet().iterator();
2698: while (i.hasNext())
2699: ((ViewInfo) i.next()).clearCanvases();
2700: viewInfoMap.clear();
2701:
2702: i = viewInfoList.iterator();
2703: while (i.hasNext())
2704: ((List) i.next()).clear();
2705: viewInfoList.clear();
2706: }
2707:
2708: private synchronized void addCanvasInfo(ViewInfo vi,
2709: CanvasInfo ci) {
2710: getCanvasList(vi).add(ci);
2711: }
2712:
2713: //
2714: // Get all relevant screen information, find out what changed, and
2715: // flag derived data. With normal use it's unlikely that any of the
2716: // Screen3D attributes will change after the first time this method is
2717: // called. It's possible that the screen resolution changed or some
2718: // sort of interactive screen calibration is in process.
2719: //
2720: private synchronized void getScreenInfo() {
2721: if (verbose)
2722: System.err.println(" getScreenInfo " + s3d.hashCode());
2723:
2724: // This is used for positioning screens in relation to each other
2725: // and must be accurate for good results with multi-screen
2726: // displays. By default the coexistence to tracker base transform
2727: // is identity so in that case this transform will also set the
2728: // image plate in coexistence coordinates.
2729: s3d.getTrackerBaseToImagePlate(t3d);
2730: if (!t3d.equals(trackerBaseToPlate)) {
2731: this .trackerBaseToPlate.set(t3d);
2732: this .updateTrackerBaseToPlate = true;
2733: if (verbose)
2734: t3dPrint(trackerBaseToPlate,
2735: " trackerBaseToPlate");
2736: }
2737:
2738: // This transform and the following are used for head mounted
2739: // displays. They should be based on the *apparent* position of
2740: // the screens as viewed through the HMD optics.
2741: s3d.getHeadTrackerToLeftImagePlate(t3d);
2742: if (!t3d.equals(headTrackerToLeftPlate)) {
2743: this .headTrackerToLeftPlate.set(t3d);
2744: this .updateHeadTrackerToPlate = true;
2745: if (verbose)
2746: t3dPrint(headTrackerToLeftPlate,
2747: " headTrackerToLeftPlate");
2748: }
2749:
2750: s3d.getHeadTrackerToRightImagePlate(t3d);
2751: if (!t3d.equals(headTrackerToRightPlate)) {
2752: this .headTrackerToRightPlate.set(t3d);
2753: this .updateHeadTrackerToPlate = true;
2754: if (verbose)
2755: t3dPrint(headTrackerToRightPlate,
2756: " headTrackerToRightPlate");
2757: }
2758:
2759: // If the screen width and height in meters are not explicitly set
2760: // through the Screen3D, then the Screen3D will assume a pixel
2761: // resolution of 90 pixels/inch and compute the dimensions from
2762: // the screen resolution. These dimensions should be measured
2763: // accurately for multi-screen displays. For HMD, these
2764: // dimensions should be the *apparent* width and height as viewed
2765: // through the HMD optics.
2766: double w = s3d.getPhysicalScreenWidth();
2767: double h = s3d.getPhysicalScreenHeight();
2768: if (w != screenWidth || h != screenHeight) {
2769: this .screenWidth = w;
2770: this .screenHeight = h;
2771: this .updateScreenSize = true;
2772: if (verbose) {
2773: System.err.println(" screen width "
2774: + screenWidth);
2775: System.err.println(" screen height "
2776: + screenHeight);
2777: }
2778: }
2779:
2780: GraphicsConfiguration gc1 = graphicsConfiguration;
2781: // Workaround for Issue 316 - use the default config for screen 0
2782: // if the graphics config is null
2783: if (gc1 == null) {
2784: gc1 = GraphicsEnvironment.getLocalGraphicsEnvironment()
2785: .getDefaultScreenDevice()
2786: .getDefaultConfiguration();
2787: }
2788: this .screenBounds = gc1.getBounds();
2789: double mpx = screenWidth / (double) screenBounds.width;
2790: double mpy = screenHeight / (double) screenBounds.height;
2791: if ((mpx != metersPerPixelX) || (mpy != metersPerPixelY)) {
2792: this .metersPerPixelX = mpx;
2793: this .metersPerPixelY = mpy;
2794: this .updatePixelSize = true;
2795: if (verbose) {
2796: System.err.println(" screen bounds "
2797: + screenBounds);
2798: System.err.println(" pixel size X "
2799: + metersPerPixelX);
2800: System.err.println(" pixel size Y "
2801: + metersPerPixelY);
2802: }
2803: }
2804:
2805: // Propagate screen updates to each canvas in each ViewInfo.
2806: Iterator vi = viewInfoList.iterator();
2807: while (vi.hasNext()) {
2808: Iterator ci = ((List) vi.next()).iterator();
2809: while (ci.hasNext())
2810: ((CanvasInfo) ci.next()).updateScreenDependencies();
2811: }
2812:
2813: this .updateTrackerBaseToPlate = false;
2814: this .updateHeadTrackerToPlate = false;
2815: this .updateScreenSize = false;
2816: this .updatePixelSize = false;
2817: this .updateScreen = false;
2818: }
2819: }
2820:
2821: //
2822: // A per-ViewPlatform cache, shared between ViewInfo instances. In the
2823: // Java 3D view model, a view platform may have several views attached to
2824: // it. The only view platform data cached here is its localToVworld, the
2825: // inverse of its localToVworld, and the scale from vworld to view
2826: // platform coordinates. The view platform to coexistence transform is
2827: // cached by the CanvasInfo instances associated with the ViewInfo.
2828: //
2829: private static class ViewPlatformInfo {
2830: private ViewPlatform vp = null;
2831: private List viewInfo = new LinkedList();
2832: private double[] m = new double[16];
2833:
2834: // These transforms are pre-allocated since we don't know what views
2835: // will be attached. Their default identity values are used if a
2836: // vworld dependent computation is requested and no initial update of
2837: // the view platform was performed; this occurs if the local to vworld
2838: // read capability isn't set. TODO: rationalize this and allocate
2839: // only if necessary.
2840: private Transform3D viewPlatformToVworld = new Transform3D();
2841: private Transform3D vworldToViewPlatform = new Transform3D();
2842: private double vworldToViewPlatformScale = 1.0;
2843:
2844: // Set these update flags initially false since we might not have the
2845: // capability to read the vworld transform.
2846: private boolean updateViewPlatformToVworld = false;
2847: private boolean updateVworldScale = false;
2848:
2849: private ViewPlatformInfo(ViewPlatform vp) {
2850: this .vp = vp;
2851: if (verbose)
2852: System.err.println(" ViewPlatformInfo: init "
2853: + vp.hashCode());
2854: }
2855:
2856: private synchronized void addViewInfo(ViewInfo vi) {
2857: this .viewInfo.add(vi);
2858: }
2859:
2860: private synchronized void removeViewInfo(ViewInfo vi) {
2861: this .viewInfo.remove(vi);
2862: }
2863:
2864: private synchronized void clear() {
2865: Iterator i = viewInfo.iterator();
2866: while (i.hasNext())
2867: ((ViewInfo) i.next()).clearViewPlatform();
2868: viewInfo.clear();
2869: }
2870:
2871: //
2872: // Get the view platform's current <code>localToVworld</code> and
2873: // force the update of derived data.
2874: //
2875: private synchronized void getViewPlatformToVworld() {
2876: if (verbose)
2877: System.err.println(" getViewPlatformToVworld "
2878: + vp.hashCode());
2879:
2880: vp.getLocalToVworld(this .viewPlatformToVworld);
2881: this .vworldToViewPlatform.invert(viewPlatformToVworld);
2882:
2883: // Get the scale factor from the virtual world to view platform
2884: // transform. Note that this is always a congruent transform.
2885: vworldToViewPlatform.get(m);
2886: double newScale = Math.sqrt(m[0] * m[0] + m[1] * m[1]
2887: + m[2] * m[2]);
2888:
2889: // May need to update clip plane distances if scale changed. We'll
2890: // check with an epsilon commensurate with single precision float.
2891: // It would be more efficient to check the square of the distance
2892: // and then compute the square root only if different, but that
2893: // makes choosing an epsilon difficult.
2894: if ((newScale > vworldToViewPlatformScale + 0.0000001)
2895: || (newScale < vworldToViewPlatformScale - 0.0000001)) {
2896: this .vworldToViewPlatformScale = newScale;
2897: this .updateVworldScale = true;
2898: if (verbose)
2899: System.err.println(" vworld scale "
2900: + vworldToViewPlatformScale);
2901: }
2902:
2903: // All virtual world transforms must be updated.
2904: Iterator i = viewInfo.iterator();
2905: while (i.hasNext())
2906: ((ViewInfo) i.next()).updateVworldDependencies();
2907:
2908: this .updateVworldScale = false;
2909: this .updateViewPlatformToVworld = false;
2910: }
2911: }
2912:
2913: //
2914: // A per-canvas cache.
2915: //
2916: private class CanvasInfo {
2917: private Canvas3D c3d = null;
2918: private ScreenInfo si = null;
2919: private boolean updateCanvas = true;
2920:
2921: private double canvasX = 0.0;
2922: private double canvasY = 0.0;
2923: private boolean updatePosition = true;
2924:
2925: private double canvasWidth = 0.0;
2926: private double canvasHeight = 0.0;
2927: private double windowScale = 0.0;
2928: private boolean updateWindowScale = true;
2929:
2930: private double screenScale = 0.0;
2931: private boolean updateScreenScale = true;
2932:
2933: private boolean useStereo = false;
2934: private boolean updateStereo = true;
2935:
2936: //
2937: // coeToPlate is the same for each Canvas3D in a Screen3D unless
2938: // coexistence centering is enabled and the window movement policy is
2939: // PHYSICAL_WORLD.
2940: //
2941: private Transform3D coeToPlate = null;
2942: private Transform3D coeToRightPlate = null;
2943: private boolean updateCoeToPlate = true;
2944:
2945: //
2946: // viewPlatformToCoe is the same for each Canvas3D in a View unless
2947: // the window resize policy is PHYSICAL_WORLD, in which case the scale
2948: // factor includes the window scale; or if the screen scale policy is
2949: // SCALE_SCREEN_SIZE, in which case the scale factor depends upon the
2950: // width of the screen associated with the canvas; or if the window
2951: // eyepoint policy is RELATIVE_TO_FIELD_OF_VIEW and the view attach
2952: // policy is not NOMINAL_SCREEN, which will set the view platform
2953: // origin in coexistence based on the width of the canvas.
2954: //
2955: private Transform3D viewPlatformToCoe = null;
2956: private Transform3D coeToViewPlatform = null;
2957: private boolean updateViewPlatformToCoe = true;
2958: private boolean updateCoeToViewPlatform = true;
2959:
2960: //
2961: // plateToViewPlatform is composed from viewPlatformToCoe and
2962: // coeToPlate.
2963: //
2964: private Transform3D plateToViewPlatform = null;
2965: private Transform3D rightPlateToViewPlatform = null;
2966: private boolean updatePlateToViewPlatform = true;
2967:
2968: //
2969: // trackerBaseToViewPlatform is computed from viewPlatformToCoe and
2970: // coeToTrackerBase.
2971: //
2972: private Transform3D trackerBaseToViewPlatform = null;
2973: private boolean updateTrackerBaseToViewPlatform = true;
2974:
2975: //
2976: // Eye position in image plate is always different for each Canvas3D
2977: // in a View, unless two or more Canvas3D instances are the same
2978: // position and size, or two or more Canvas3D instances are using a
2979: // window eyepoint policy of RELATIVE_TO_SCREEN and have the same
2980: // settings for the manual eye positions.
2981: //
2982: private Point3d eyeInPlate = new Point3d();
2983: private Point3d rightEyeInPlate = new Point3d();
2984: private Transform3D eyeToPlate = null;
2985: private Transform3D rightEyeToPlate = null;
2986: private boolean updateEyeInPlate = true;
2987:
2988: private Point3d leftManualEyeInPlate = new Point3d();
2989: private Point3d rightManualEyeInPlate = new Point3d();
2990: private boolean updateManualEye = true;
2991:
2992: private int monoscopicPolicy = -1;
2993: private boolean updateMonoPolicy = true;
2994:
2995: //
2996: // eyeToViewPlatform is computed from eyeToPlate and
2997: // plateToViewPlatform.
2998: //
2999: private Transform3D eyeToViewPlatform = null;
3000: private Transform3D rightEyeToViewPlatform = null;
3001: private boolean updateEyeToViewPlatform = true;
3002:
3003: private Transform3D viewPlatformToEye = null;
3004: private Transform3D viewPlatformToRightEye = null;
3005: private boolean updateViewPlatformToEye = true;
3006:
3007: //
3008: // The projection transform depends upon eye position in image plate.
3009: //
3010: private Transform3D projection = null;
3011: private Transform3D rightProjection = null;
3012: private boolean updateProjection = true;
3013:
3014: private Transform3D inverseProjection = null;
3015: private Transform3D inverseRightProjection = null;
3016: private boolean updateInverseProjection = true;
3017:
3018: private Transform3D inverseViewPlatformProjection = null;
3019: private Transform3D inverseViewPlatformRightProjection = null;
3020: private boolean updateInverseViewPlatformProjection = true;
3021:
3022: //
3023: // The physical clip distances can be affected by the canvas width
3024: // with the PHYSICAL_WORLD resize policy.
3025: //
3026: private double frontClipDistance = 0.0;
3027: private double backClipDistance = 0.0;
3028: private boolean updateClipDistances = true;
3029:
3030: //
3031: // The physical to view platform scale can be affected by the canvas
3032: // width with the PHYSICAL_WORLD resize policy.
3033: //
3034: private double physicalToVpScale = 0.0;
3035: private double physicalToVirtualScale = 0.0;
3036: private boolean updatePhysicalToVpScale = true;
3037: private boolean updatePhysicalToVirtualScale = true;
3038:
3039: //
3040: // The vworld transforms require reading the ViewPlaform's
3041: // localToVworld tranform.
3042: //
3043: private Transform3D plateToVworld = null;
3044: private Transform3D rightPlateToVworld = null;
3045: private boolean updatePlateToVworld = true;
3046:
3047: private Transform3D coeToVworld = null;
3048: private boolean updateCoeToVworld = true;
3049:
3050: private Transform3D eyeToVworld = null;
3051: private Transform3D rightEyeToVworld = null;
3052: private boolean updateEyeToVworld = true;
3053:
3054: private Transform3D trackerBaseToVworld = null;
3055: private boolean updateTrackerBaseToVworld = true;
3056:
3057: private Transform3D inverseVworldProjection = null;
3058: private Transform3D inverseVworldRightProjection = null;
3059: private boolean updateInverseVworldProjection = true;
3060:
3061: private CanvasInfo(Canvas3D c3d, ScreenInfo si) {
3062: this .si = si;
3063: this .c3d = c3d;
3064: if (verbose)
3065: System.err.println(" CanvasInfo: init "
3066: + c3d.hashCode());
3067: }
3068:
3069: private void getCanvasInfo() {
3070: if (verbose)
3071: System.err.println(" getCanvasInfo " + c3d.hashCode());
3072: boolean newStereo = c3d.getStereoEnable()
3073: && c3d.getStereoAvailable();
3074:
3075: if (useStereo != newStereo) {
3076: this .useStereo = newStereo;
3077: this .updateStereo = true;
3078: if (verbose)
3079: System.err.println(" stereo " + useStereo);
3080: }
3081:
3082: this .canvasWidth = c3d.getWidth() * si.metersPerPixelX;
3083: this .canvasHeight = c3d.getHeight() * si.metersPerPixelY;
3084: double newScale = canvasWidth / si.screenWidth;
3085:
3086: if (windowScale != newScale) {
3087: this .windowScale = newScale;
3088: this .updateWindowScale = true;
3089: if (verbose) {
3090: System.err.println(" width " + canvasWidth);
3091: System.err.println(" height " + canvasHeight);
3092: System.err.println(" scale " + windowScale);
3093: }
3094: }
3095:
3096: // For multiple physical screens, AWT returns the canvas location
3097: // relative to the origin of the aggregated virtual screen. We
3098: // need the location relative to the physical screen origin.
3099: Point awtLocation = c3d.getLocationOnScreen();
3100: int x = awtLocation.x - si.screenBounds.x;
3101: int y = awtLocation.y - si.screenBounds.y;
3102:
3103: double newCanvasX = si.metersPerPixelX * x;
3104: double newCanvasY = si.metersPerPixelY
3105: * (si.screenBounds.height - (y + c3d.getHeight()));
3106:
3107: if (canvasX != newCanvasX || canvasY != newCanvasY) {
3108: this .canvasX = newCanvasX;
3109: this .canvasY = newCanvasY;
3110: this .updatePosition = true;
3111: if (verbose) {
3112: System.err.println(" lower left X " + canvasX);
3113: System.err.println(" lower left Y " + canvasY);
3114: }
3115: }
3116:
3117: int newMonoPolicy = c3d.getMonoscopicViewPolicy();
3118: if (monoscopicPolicy != newMonoPolicy) {
3119: this .monoscopicPolicy = newMonoPolicy;
3120: this .updateMonoPolicy = true;
3121:
3122: if (verbose && !useStereo) {
3123: if (monoscopicPolicy == View.LEFT_EYE_VIEW)
3124: System.err.println(" left eye view");
3125: else if (monoscopicPolicy == View.RIGHT_EYE_VIEW)
3126: System.err.println(" right eye view");
3127: else
3128: System.err.println(" cyclopean view");
3129: }
3130: }
3131:
3132: c3d.getLeftManualEyeInImagePlate(leftEye);
3133: c3d.getRightManualEyeInImagePlate(rightEye);
3134:
3135: if (!leftEye.equals(leftManualEyeInPlate)
3136: || !rightEye.equals(rightManualEyeInPlate)) {
3137:
3138: this .leftManualEyeInPlate.set(leftEye);
3139: this .rightManualEyeInPlate.set(rightEye);
3140: this .updateManualEye = true;
3141:
3142: if (verbose
3143: && (eyePolicy == View.RELATIVE_TO_WINDOW || eyePolicy == View.RELATIVE_TO_SCREEN)) {
3144: System.err.println(" left manual eye in plate "
3145: + leftManualEyeInPlate);
3146: System.err.println(" right manual eye in plate "
3147: + rightManualEyeInPlate);
3148: }
3149: }
3150:
3151: updateCanvasDependencies();
3152: this .updateStereo = false;
3153: this .updateWindowScale = false;
3154: this .updatePosition = false;
3155: this .updateMonoPolicy = false;
3156: this .updateManualEye = false;
3157: this .updateCanvas = false;
3158: }
3159:
3160: private double getFieldOfViewOffset() {
3161: return 0.5 * canvasWidth
3162: / Math.tan(0.5 * view.getFieldOfView());
3163: }
3164:
3165: private void updateScreenDependencies() {
3166: if (si.updatePixelSize || si.updateScreenSize) {
3167: if (eyePolicy == View.RELATIVE_TO_WINDOW
3168: || eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) {
3169: // Physical location of the canvas might change without
3170: // changing the pixel location.
3171: updateEyeInPlate = true;
3172: }
3173: if (resizePolicy == View.PHYSICAL_WORLD
3174: || eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) {
3175: // Could change the window scale or view platform Z offset.
3176: updateViewPlatformToCoe = true;
3177: }
3178: if (resizePolicy == View.PHYSICAL_WORLD) {
3179: // Window scale affects the clip distance and the physical
3180: // to viewplatform scale.
3181: updateClipDistances = true;
3182: updatePhysicalToVpScale = true;
3183: updatePhysicalToVirtualScale = true;
3184: }
3185: // Upper right corner of canvas may have moved from eye.
3186: updateProjection = true;
3187: }
3188: if (si.updateScreenSize
3189: && scalePolicy == View.SCALE_SCREEN_SIZE) {
3190: // Screen scale affects the clip distances and physical to
3191: // view platform scale. The screen scale is also a component
3192: // of viewPlatformToCoe.
3193: updateScreenScale = true;
3194: updateClipDistances = true;
3195: updatePhysicalToVpScale = true;
3196: updatePhysicalToVirtualScale = true;
3197: updateViewPlatformToCoe = true;
3198: }
3199:
3200: if (viewPolicy == View.HMD_VIEW) {
3201: if (si.updateHeadTrackerToPlate) {
3202: // Plate moves with respect to the eye and coexistence.
3203: updateEyeInPlate = true;
3204: updateCoeToPlate = true;
3205: }
3206: } else if (coeCentering) {
3207: if (movementPolicy == View.PHYSICAL_WORLD) {
3208: // Coexistence is centered on the canvas.
3209: if (si.updatePixelSize || si.updateScreenSize)
3210: // Physical location of the canvas might change
3211: // without changing the pixel location.
3212: updateCoeToPlate = true;
3213: } else if (si.updateScreenSize)
3214: // Coexistence is centered on the screen.
3215: updateCoeToPlate = true;
3216: } else if (si.updateTrackerBaseToPlate) {
3217: // Image plate has possibly changed location. Could be
3218: // offset by an update to coeToTrackerBase in the
3219: // PhysicalEnvironment though.
3220: updateCoeToPlate = true;
3221: }
3222:
3223: if (updateCoeToPlate
3224: && eyePolicy == View.RELATIVE_TO_COEXISTENCE) {
3225: // Coexistence has moved with respect to plate.
3226: updateEyeInPlate = true;
3227: }
3228: if (updateViewPlatformToCoe) {
3229: // Derived transforms. trackerBaseToViewPlatform is composed
3230: // from viewPlatformToCoe and coexistenceToTrackerBase.
3231: updateCoeToViewPlatform = true;
3232: updateCoeToVworld = true;
3233: updateTrackerBaseToViewPlatform = true;
3234: updateTrackerBaseToVworld = true;
3235: }
3236: if (updateCoeToPlate || updateViewPlatformToCoe) {
3237: // The image plate to view platform transform is composed from
3238: // the coexistence to image plate and view platform to
3239: // coexistence transforms, so these need updates as well.
3240: updatePlateToViewPlatform = true;
3241: updatePlateToVworld = true;
3242: }
3243: updateEyeDependencies();
3244: }
3245:
3246: private void updateEyeDependencies() {
3247: if (updateEyeInPlate) {
3248: updateEyeToVworld = true;
3249: updateProjection = true;
3250: }
3251: if (updateProjection) {
3252: updateInverseProjection = true;
3253: updateInverseViewPlatformProjection = true;
3254: updateInverseVworldProjection = true;
3255: }
3256: if (updateEyeInPlate || updatePlateToViewPlatform) {
3257: updateViewPlatformToEye = true;
3258: updateEyeToViewPlatform = true;
3259: }
3260: }
3261:
3262: private void updateCanvasDependencies() {
3263: if (updateStereo
3264: || updateMonoPolicy
3265: || (updateManualEye && (eyePolicy == View.RELATIVE_TO_WINDOW || eyePolicy == View.RELATIVE_TO_SCREEN))) {
3266: updateEyeInPlate = true;
3267: }
3268: if (updateWindowScale || updatePosition) {
3269: if (coeCentering
3270: && movementPolicy == View.PHYSICAL_WORLD) {
3271: // Coexistence is centered on the canvas.
3272: updateCoeToPlate = true;
3273: if (eyePolicy == View.RELATIVE_TO_COEXISTENCE)
3274: updateEyeInPlate = true;
3275: }
3276: if (eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW
3277: || eyePolicy == View.RELATIVE_TO_WINDOW)
3278: // Eye depends on canvas position and size.
3279: updateEyeInPlate = true;
3280: }
3281: if (updateWindowScale) {
3282: if (resizePolicy == View.PHYSICAL_WORLD
3283: || eyePolicy == View.RELATIVE_TO_FIELD_OF_VIEW) {
3284: // View platform scale and its origin Z offset changed.
3285: // trackerBaseToViewPlatform needs viewPlatformToCoe.
3286: updateViewPlatformToCoe = true;
3287: updateCoeToViewPlatform = true;
3288: updateCoeToVworld = true;
3289: updateTrackerBaseToViewPlatform = true;
3290: updateTrackerBaseToVworld = true;
3291: }
3292: if (resizePolicy == View.PHYSICAL_WORLD) {
3293: // Clip distance and physical to view platform scale are
3294: // affected by the window size.
3295: updateClipDistances = true;
3296: updateProjection = true;
3297: updatePhysicalToVpScale = true;
3298: updatePhysicalToVirtualScale = true;
3299: }
3300: }
3301: if (updateViewPlatformToCoe || updateCoeToPlate) {
3302: // The image plate to view platform transform is composed from
3303: // the coexistence to image plate and the view platform to
3304: // coexistence transforms, so these need updates.
3305: updatePlateToViewPlatform = true;
3306: updatePlateToVworld = true;
3307: }
3308: if (coeCentering && !updateManualEye && !updateWindowScale
3309: && (movementPolicy == View.PHYSICAL_WORLD)
3310: && (eyePolicy != View.RELATIVE_TO_SCREEN)) {
3311: // The canvas may have moved, but the eye, coexistence, and
3312: // view platform moved with it. updateEyeDependencies()
3313: // isn't called since it would unnecessarily update the
3314: // projection and eyeToViewPlatform transforms. The tested
3315: // policies are all true by default.
3316: return;
3317: }
3318: updateEyeDependencies();
3319: }
3320:
3321: //
3322: // TODO: A brave soul could refine cache updates here. There are a
3323: // lot of attributes to monitor, so we just update everything for now.
3324: //
3325: private void updateViewDependencies() {
3326: // View policy, physical body eye positions, head to head
3327: // tracker, window eyepoint policy, field of view, coexistence
3328: // centering, or coexistence to image plate may have changed.
3329: updateEyeInPlate = true;
3330:
3331: // If the eye position in image plate has changed, then the
3332: // projection transform may need to be updated. The projection
3333: // policy and clip plane distances and policies may have changed.
3334: // The window resize policy and screen scale may have changed,
3335: // which affects clip plane distance scaling.
3336: updateProjection = true;
3337: updateClipDistances = true;
3338: updatePhysicalToVpScale = true;
3339: updatePhysicalToVirtualScale = true;
3340:
3341: // View policy, coexistence to tracker base, coexistence centering
3342: // enable, or window movement policy may have changed.
3343: updateCoeToPlate = true;
3344:
3345: // Screen scale, resize policy, view policy, view platform,
3346: // physical body, physical environment, eyepoint policy, or field
3347: // of view may have changed.
3348: updateViewPlatformToCoe = true;
3349: updateCoeToViewPlatform = true;
3350: updateCoeToVworld = true;
3351:
3352: // The image plate to view platform transform is composed from the
3353: // coexistence to image plate and view platform to coexistence
3354: // transforms, so these need updates.
3355: updatePlateToViewPlatform = true;
3356: updatePlateToVworld = true;
3357:
3358: // View platform to coexistence or coexistence to tracker base may
3359: // have changed.
3360: updateTrackerBaseToViewPlatform = true;
3361: updateTrackerBaseToVworld = true;
3362:
3363: // Screen scale policy or explicit screen scale may have changed.
3364: updateScreenScale = true;
3365:
3366: // Update transforms derived from eye info.
3367: updateEyeDependencies();
3368: }
3369:
3370: private void updateHeadDependencies() {
3371: if (viewPolicy == View.HMD_VIEW) {
3372: // Image plates are fixed relative to the head, so their
3373: // positions have changed with respect to coexistence, the
3374: // view platform, and the virtual world. The eyes are fixed
3375: // with respect to the image plates, so the projection doesn't
3376: // change with respect to them.
3377: updateCoeToPlate = true;
3378: updatePlateToViewPlatform = true;
3379: updatePlateToVworld = true;
3380: updateViewPlatformToEye = true;
3381: updateEyeToViewPlatform = true;
3382: updateEyeToVworld = true;
3383: updateInverseViewPlatformProjection = true;
3384: updateInverseVworldProjection = true;
3385: } else {
3386: // Eye positions have changed with respect to the fixed
3387: // screens, so the projections must be updated as well as the
3388: // positions.
3389: updateEyeInPlate = true;
3390: updateEyeDependencies();
3391: }
3392: }
3393:
3394: private void updateVworldDependencies() {
3395: updatePlateToVworld = true;
3396: updateCoeToVworld = true;
3397: updateEyeToVworld = true;
3398: updateTrackerBaseToVworld = true;
3399: updateInverseVworldProjection = true;
3400:
3401: if (vpi.updateVworldScale)
3402: updatePhysicalToVirtualScale = true;
3403:
3404: if (vpi.updateVworldScale && clipVirtual) {
3405: // vworldToViewPlatformScale changed and clip plane distances
3406: // are in virtual units.
3407: updateProjection = true;
3408: updateClipDistances = true;
3409: updateInverseProjection = true;
3410: updateInverseViewPlatformProjection = true;
3411: }
3412: }
3413: }
3414:
3415: /**
3416: * Prints out the specified transform in a readable format.
3417: *
3418: * @param t3d transform to be printed
3419: * @param name the name of the transform
3420: */
3421: private static void t3dPrint(Transform3D t3d, String name) {
3422: double[] m = new double[16];
3423: t3d.get(m);
3424: String[] sa = formatMatrixRows(4, 4, m);
3425: System.err.println(name);
3426: for (int i = 0; i < 4; i++)
3427: System.err.println(sa[i]);
3428: }
3429:
3430: /**
3431: * Formats a matrix with fixed fractional digits and integer padding to
3432: * align the decimal points in columns. Non-negative numbers print up to
3433: * 7 integer digits, while negative numbers print up to 6 integer digits
3434: * to account for the negative sign. 6 fractional digits are printed.
3435: *
3436: * @param rowCount number of rows in the matrix
3437: * @param colCount number of columns in the matrix
3438: * @param m matrix to be formatted
3439: * @return matrix rows formatted into strings
3440: */
3441: private static String[] formatMatrixRows(int rowCount,
3442: int colCount, double[] m) {
3443:
3444: DecimalFormat df = new DecimalFormat("0.000000");
3445: FieldPosition fp = new FieldPosition(
3446: DecimalFormat.INTEGER_FIELD);
3447: StringBuffer sb0 = new StringBuffer();
3448: StringBuffer sb1 = new StringBuffer();
3449: String[] rows = new String[rowCount];
3450:
3451: for (int i = 0; i < rowCount; i++) {
3452: sb0.setLength(0);
3453: for (int j = 0; j < colCount; j++) {
3454: sb1.setLength(0);
3455: df.format(m[i * colCount + j], sb1, fp);
3456: int pad = 8 - fp.getEndIndex();
3457: for (int k = 0; k < pad; k++) {
3458: sb1.insert(0, " ");
3459: }
3460: sb0.append(sb1);
3461: }
3462: rows[i] = sb0.toString();
3463: }
3464: return rows;
3465: }
3466: }
|