gt:     Transform3D getRightVpcToEc() {
context: /*
   * Copyright 1997-2008 Sun Microsystems, Inc.  All Rights Reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Sun designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Sun in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
   * CA 95054 USA or visit www.sun.com if you need additional information or
   * have any questions.
   *
   */
  
  package javax.media.j3d;
  
  import java.awt.Rectangle;
  
  import javax.vecmath.Matrix4d;
  import javax.vecmath.Point2d;
  import javax.vecmath.Point3d;
  import javax.vecmath.Point4d;
  import javax.vecmath.SingularMatrixException;
  import javax.vecmath.Vector3d;
  import javax.vecmath.Vector4d;
  
  /**
   * The CanvasViewCache class is used to cache all data, both API data
   * and derived data, that is dependent on the Canvas3D or Screen3D.
   * The final view and projection matrices are stored here.
   */
  
  class CanvasViewCache extends Object {
      // Used for debugging only
      private static Object debugLock = new Object();
  
      // The canvas associated with this canvas view cache
      private Canvas3D canvas;
  
      // Mask that indicates this CanvasViewCache view dependence info. has changed,
      // and CanvasViewCache may need to recompute the final view matries.
      int cvcDirtyMask = 0;
  
      // The screen view cache associated with this canvas view cache
      private ScreenViewCache screenViewCache;
  
      // The view cache associated with this canvas view cache
      private ViewCache viewCache;
  
      // *************
      // API/INPUT DATA
      // *************
  
      // The position and size of the canvas (in pixels)
      private int awtCanvasX;
      private int awtCanvasY;
      private int awtCanvasWidth;
      private int awtCanvasHeight;
  
      // The current RenderBin used for rendering during the frame
      // associated with this snapshot.
      private RenderBin renderBin;
  
      // Flag indicating whether or not stereo will be used.  Computed by
      // Canvas3D as: useStereo = stereoEnable && stereoAvailable
      private boolean useStereo;
  
      // Current monoscopic view policy from canvas
      private int monoscopicViewPolicy;
  
      // The manual positions of the left and right eyes in image-plate
      // coordinates.
      // Note that these values are only used in non-head-tracked mode
      // when the view's window eyepoint policy is one of RELATIVE_TO_SCREEN
      // or RELATIVE_TO_WINDOW.
      private Point3d leftManualEyeInImagePlate = new Point3d();
      private Point3d rightManualEyeInImagePlate = new Point3d();
  
      // *************
      // DERIVED DATA
      // *************
  
      // The width and height of the screen in meters (from ScreenViewCache)
      double physicalScreenWidth;
      double physicalScreenHeight;
  
      // The width and height of the screen in pixels (from ScreenViewCache)
      int screenWidth;
      int screenHeight;
  
      // Meters per pixel in the X and Y dimension (from ScreenViewCache)
      double metersPerPixelX;
      double metersPerPixelY;
  
      // The position and size of the canvas (in pixels)
      private int canvasX;
      private int canvasY;
      private int canvasWidth;
      private int canvasHeight;
  
      // Either the Canvas' or the View's monoscopicViewPolicy
      private int effectiveMonoscopicViewPolicy;
  
      // The current cached projection transforms.
      private Transform3D leftProjection = new Transform3D();
      private Transform3D rightProjection = new Transform3D();
      private Transform3D infLeftProjection = new Transform3D();
      private Transform3D infRightProjection = new Transform3D();
  
      // The current cached viewing transforms.
      private Transform3D leftVpcToEc = new Transform3D();
      private Transform3D rightVpcToEc = new Transform3D();
      private Transform3D infLeftVpcToEc = new Transform3D();
      private Transform3D infRightVpcToEc = new Transform3D();
  
      // The current cached inverse viewing transforms.
      private Transform3D leftEcToVpc = new Transform3D();
      private Transform3D rightEcToVpc = new Transform3D();
      private Transform3D infLeftEcToVpc = new Transform3D();
      private Transform3D infRightEcToVpc = new Transform3D();
  
      // Arrays of Vector4d objects that represent the plane equations for
      // the 6 planes in the viewing frustum in ViewPlatform coordinates.
      private Vector4d[] leftFrustumPlanes = new Vector4d[6];
      private Vector4d[] rightFrustumPlanes = new Vector4d[6];
  
      // Arrays of Vector4d objects that represent the volume of viewing frustum
      private Point4d leftFrustumPoints[] = new Point4d[8];
      private Point4d rightFrustumPoints[] = new Point4d[8];
  
      // Calibration matrix from Screen object for HMD mode using
      // non-field-sequential stereo
  
      private Transform3D headTrackerToLeftImagePlate = new Transform3D();
      private Transform3D headTrackerToRightImagePlate = new Transform3D();
  
      // Head tracked version of eye in imageplate
      private Point3d leftTrackedEyeInImagePlate = new Point3d();
      private Point3d rightTrackedEyeInImagePlate = new Point3d();
  
      // Derived version of eye in image plate coordinates
      private Point3d leftEyeInImagePlate = new Point3d();
      private Point3d rightEyeInImagePlate = new Point3d();
      private Point3d centerEyeInImagePlate = new Point3d();
  
      // Derived version of nominalEyeOffsetFromNominalScreen
      private double nominalEyeOffset;
  
      // Physical window position,size and center (in image plate coordinates)
      private double physicalWindowXLeft;
      private double physicalWindowYBottom;
      private double physicalWindowXRight;
      private double physicalWindowYTop;
      private double physicalWindowWidth;
      private double physicalWindowHeight;
      private Point3d physicalWindowCenter = new Point3d();
  
      // Screen scale value from viewCache or from screen size.
      private double screenScale;
  
      // Window scale value that compensates for window size if
      // the window resize policy is PHYSICAL_WORLD.
      private double windowScale;
  
      // ViewPlatform scale that takes coordinates from view platform
      // coordinates and scales them to physical coordinates
      private double viewPlatformScale;
  
      // Various derived transforms
  
      private Transform3D leftCcToVworld = new Transform3D();
      private Transform3D rightCcToVworld = new Transform3D();
  
      private Transform3D coexistenceToLeftPlate = new Transform3D();
      private Transform3D coexistenceToRightPlate = new Transform3D();
  
      private Transform3D vpcToCoexistence = new Transform3D();
  
      private Transform3D vpcToLeftPlate = new Transform3D();
      private Transform3D vpcToRightPlate = new Transform3D();
      private Transform3D leftPlateToVpc = new Transform3D();
      private Transform3D rightPlateToVpc = new Transform3D();
      private Transform3D vworldToLeftPlate = new Transform3D();
      private Transform3D lastVworldToLeftPlate = new Transform3D();
      private Transform3D vworldToRightPlate = new Transform3D();
      private Transform3D leftPlateToVworld = new Transform3D();
      private Transform3D rightPlateToVworld = new Transform3D();
      private Transform3D headToLeftImagePlate = new Transform3D();
      private Transform3D headToRightImagePlate = new Transform3D();
  
      private Transform3D vworldToTrackerBase = new Transform3D();
      private Transform3D tempTrans = new Transform3D();
      private Transform3D headToVworld = new Transform3D();
      private Vector3d coexistenceCenter = new Vector3d();
  
      // scale for transformimg clip and fog distances
      private double vworldToCoexistenceScale;
      private double infVworldToCoexistenceScale;
  
      //
      // Temporary matrices and vectors, so we dont generate garbage
      //
      private Transform3D tMat1 = new Transform3D();
      private Transform3D tMat2 = new Transform3D();
      private Vector3d tVec1 = new Vector3d();
      private Vector3d tVec2 = new Vector3d();
      private Vector3d tVec3 = new Vector3d();
      private Point3d tPnt1 = new Point3d();
      private Point3d tPnt2 = new Point3d();
  
      private Matrix4d tMatrix = new Matrix4d();
  
      /**
       * The view platform transforms.
       */
      private Transform3D vworldToVpc = new Transform3D();
      private Transform3D vpcToVworld = new Transform3D();
      private Transform3D infVworldToVpc = new Transform3D();
  
      // This flag is used to remember the last time doInfinite flag
      // is true or not.
      // If this cache is updated twice, the first time in RenderBin
      // updateViewCache() and the second time in Renderer with
      // geometryBackground. The first time will reset the vcDirtyMask
      // to 0 so that geometry background will not get updated the
      // second time doComputeDerivedData() is invoked when view change.
      private boolean lastDoInfinite = false;
      private boolean updateLastTime = false;
  
      void getCanvasPositionAndSize() {
  	if(J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2) {
  	    System.err.println("Get canvas position and size");
  	    System.err.println("Before");
  	    System.err.println("Canvas pos = (" + awtCanvasX + ", " +
  			       awtCanvasY + "), size = " + awtCanvasWidth +
  			       "x" + awtCanvasHeight);
  	    System.err.println("After");
  	}
  	awtCanvasX = canvas.newPosition.x;
  	awtCanvasY = canvas.newPosition.y;
  	awtCanvasWidth = canvas.newSize.width;
  	awtCanvasHeight = canvas.newSize.height;
  
  	// The following works around problem when awt creates 0-size
  	// window at startup
  	if ((awtCanvasWidth <= 0) || (awtCanvasHeight <= 0)) {
  	    awtCanvasWidth = 1;
  	    awtCanvasHeight = 1;
  	}
  
  	if (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1) {
  	    System.err.println("Canvas pos = (" + awtCanvasX + ", " +
  			       awtCanvasY + "), size = " + awtCanvasWidth +
  			       "x" + awtCanvasHeight);
  	}
      }
  
      void computefrustumBBox(BoundingBox frustumBBox) {
  	int i;
  
  	for(i = 0; i < leftFrustumPoints.length; i++) {
  	    if(frustumBBox.lower.x > leftFrustumPoints[i].x)
  		frustumBBox.lower.x = leftFrustumPoints[i].x;
  	    if(frustumBBox.lower.y > leftFrustumPoints[i].y)
  		frustumBBox.lower.y = leftFrustumPoints[i].y;
  	    if(frustumBBox.lower.z > leftFrustumPoints[i].z)
  		frustumBBox.lower.z = leftFrustumPoints[i].z;
  
  	    if(frustumBBox.upper.x < leftFrustumPoints[i].x)
  		frustumBBox.upper.x = leftFrustumPoints[i].x;
  	    if(frustumBBox.upper.y < leftFrustumPoints[i].y)
  		frustumBBox.upper.y = leftFrustumPoints[i].y;
  	    if(frustumBBox.upper.z < leftFrustumPoints[i].z)
  		frustumBBox.upper.z = leftFrustumPoints[i].z;
  	}
  
  	if(useStereo) {
  
  	    for(i = 0; i< rightFrustumPoints.length; i++) {
  		if(frustumBBox.lower.x > rightFrustumPoints[i].x)
  		    frustumBBox.lower.x = rightFrustumPoints[i].x;
  		if(frustumBBox.lower.y > rightFrustumPoints[i].y)
  		    frustumBBox.lower.y = rightFrustumPoints[i].y;
  		if(frustumBBox.lower.z > rightFrustumPoints[i].z)
  		    frustumBBox.lower.z = rightFrustumPoints[i].z;
  
  		if(frustumBBox.upper.x < rightFrustumPoints[i].x)
  		    frustumBBox.upper.x = rightFrustumPoints[i].x;
  		if(frustumBBox.upper.y < rightFrustumPoints[i].y)
  		    frustumBBox.upper.y = rightFrustumPoints[i].y;
  		if(frustumBBox.upper.z < rightFrustumPoints[i].z)
  		    frustumBBox.upper.z = rightFrustumPoints[i].z;
  	    }
  
  	}
      }
  
  
      void copyComputedCanvasViewCache(CanvasViewCache cvc, boolean doInfinite) {
  	// For performance reason, only data needed by renderer are copied.
  	// useStereo,
  	// canvasWidth,
  	// canvasHeight,
  	// leftProjection,
  	// rightProjection,
  	// leftVpcToEc,
  	// rightVpcToEc,
  	// leftFrustumPlanes,
  	// rightFrustumPlanes,
  	// vpcToVworld,
  	// vworldToVpc.
  
  	cvc.useStereo = useStereo;
  	cvc.canvasWidth = canvasWidth;
  	cvc.canvasHeight = canvasHeight;
  	cvc.leftProjection.set(leftProjection);
  	cvc.rightProjection.set(rightProjection);
  	cvc.leftVpcToEc.set(leftVpcToEc) ;
  	cvc.rightVpcToEc.set(rightVpcToEc) ;
  
  	cvc.vpcToVworld = vpcToVworld;
  	cvc.vworldToVpc.set(vworldToVpc);
  
          if (doInfinite) {
              cvc.infLeftProjection.set(infLeftProjection);
              cvc.infRightProjection.set(infRightProjection);
              cvc.infLeftVpcToEc.set(infLeftVpcToEc) ;
              cvc.infRightVpcToEc.set(infRightVpcToEc) ;
              cvc.infVworldToVpc.set(infVworldToVpc);
          }
  
  	for (int i = 0; i < leftFrustumPlanes.length; i++) {
  	    cvc.leftFrustumPlanes[i].x = leftFrustumPlanes[i].x;
  	    cvc.leftFrustumPlanes[i].y = leftFrustumPlanes[i].y;
  	    cvc.leftFrustumPlanes[i].z = leftFrustumPlanes[i].z;
  	    cvc.leftFrustumPlanes[i].w = leftFrustumPlanes[i].w;
  
  	    cvc.rightFrustumPlanes[i].x = rightFrustumPlanes[i].x;
  	    cvc.rightFrustumPlanes[i].y = rightFrustumPlanes[i].y;
  	    cvc.rightFrustumPlanes[i].z = rightFrustumPlanes[i].z;
  	    cvc.rightFrustumPlanes[i].w = rightFrustumPlanes[i].w;
  	}
      }
  
  
      /**
       * Take snapshot of all per-canvas API parameters and input values.
       * NOTE: This is probably not needed, but we'll do it for symmetry
       * with the ScreenViewCache and ViewCache objects.
       */
      synchronized void snapshot(boolean computeFrustum) {
          // Issue 109 : determine the the correct index to use -- either the
          // Renderer or RenderBin
          int dirtyIndex = computeFrustum ?
              Canvas3D.RENDER_BIN_DIRTY_IDX : Canvas3D.RENDERER_DIRTY_IDX;
  
          synchronized (canvas.dirtyMaskLock) {
              // Issue 109 : read/clear the dirty bits for the correct index
              cvcDirtyMask = canvas.cvDirtyMask[dirtyIndex];
              canvas.cvDirtyMask[dirtyIndex] = 0;
          }
  
          useStereo = canvas.useStereo;
  	monoscopicViewPolicy = canvas.monoscopicViewPolicy;
  	leftManualEyeInImagePlate.set(canvas.leftManualEyeInImagePlate);
  	rightManualEyeInImagePlate.set(canvas.rightManualEyeInImagePlate);
  
  	if(( cvcDirtyMask & Canvas3D.MOVED_OR_RESIZED_DIRTY) != 0) {
  	    getCanvasPositionAndSize();
  	}
  
  	renderBin = canvas.view.renderBin;
  
      }
  
      /**
       * Compute derived data using the snapshot of the per-canvas,
       * per-screen and per-view data.
       */
      synchronized void computeDerivedData(boolean currentFlag,
  	CanvasViewCache cvc, BoundingBox frustumBBox, boolean doInfinite) {
  
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) {
  	    synchronized(debugLock) {
  		System.err.println("------------------------------");
  		doComputeDerivedData(currentFlag,cvc,frustumBBox,doInfinite);
  	    }
  	}
  	else {
  	    doComputeDerivedData(currentFlag,cvc,frustumBBox,doInfinite);
  	}
      }
  
      /**
       * Compute derived data using the snapshot of the per-canvas,
       * per-screen and per-view data.  Caller must synchronize before
       * calling this method.
       */
      private void doComputeDerivedData(boolean currentFlag,
  	CanvasViewCache cvc, BoundingBox frustumBBox, boolean doInfinite) {
  
          // Issue 109 : determine the the correct index to use -- either the
          // Renderer or RenderBin
          int dirtyIndex = (frustumBBox != null) ?
              Canvas3D.RENDER_BIN_DIRTY_IDX : Canvas3D.RENDERER_DIRTY_IDX;
          int scrvcDirtyMask;
  
          // Issue 109 : read/clear the dirty bits for the correct index
          synchronized (screenViewCache) {
              scrvcDirtyMask = screenViewCache.scrvcDirtyMask[dirtyIndex];
              // reset screen view dirty mask if canvas is offScreen. Note:
              // there is only one canvas per offscreen, so it is ok to
              // do the reset here.
              if (canvas.offScreen) {
                  screenViewCache.scrvcDirtyMask[dirtyIndex] = 0;
              }
          }
  
          if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    if(cvcDirtyMask != 0)
  		System.err.println("cvcDirtyMask : " +  cvcDirtyMask);
  
  	    if(scrvcDirtyMask != 0)
  		System.err.println("scrvcDirtyMask : "+ scrvcDirtyMask);
  
  	    if(viewCache.vcDirtyMask != 0)
  		System.err.println("vcDirtyMask : " +  viewCache.vcDirtyMask);
  	}
  
  
  	// NOTE: This fix is only fixing the symptoms, but not the
  	// root of the bug.  We shouldn't have to check for null here.
  	if(viewCache.vpRetained == null) {
  	     System.err.println("CanvasViewCache : Error! viewCache.vpRetained is null");
  	    return;
  	}
  
  	// This flag is use to force a computation when a ViewPlatformTransform
  	// is detected. No sync. needed. We're doing a read of t/f.
  	// XXXX: Peeking at the dirty flag is a hack. Need to revisit this.
  	boolean vprNotDirty = (viewCache.vpRetained.vprDirtyMask == 0);
  
          // Issue 131: If not manual, it has to be considered as an onscreen canvas.
  	if(!canvas.manualRendering &&
  	   (vprNotDirty) &&
  	   (cvcDirtyMask == 0) &&
  	   (scrvcDirtyMask == 0) &&
  	   (viewCache.vcDirtyMask == 0) &&
  	    !(updateLastTime && (doInfinite != lastDoInfinite))) {
  	    if(frustumBBox != null)
  		computefrustumBBox(frustumBBox);
  
  	    // Copy the computed data into cvc.
  	    if(cvc != null) {
  		copyComputedCanvasViewCache(cvc, doInfinite);
  	    }
  	    lastDoInfinite = doInfinite;
  	    updateLastTime = false;
  	    return;
  	}
  
  	lastDoInfinite = doInfinite;
  	updateLastTime = true;
  
  	if(currentFlag) {
  	    vpcToVworld.set(viewCache.vpRetained.getCurrentLocalToVworld(null));
  	}
  	else {
  	    vpcToVworld.set(viewCache.vpRetained.getLastLocalToVworld(null));
  	}
  
  	// System.err.println("vpcToVworld is \n" + vpcToVworld);
  
          try {
  	    vworldToVpc.invert(vpcToVworld);
  	}
  	catch (SingularMatrixException e) {
  	    vworldToVpc.setIdentity();
  	    //System.err.println("SingularMatrixException encountered when doing vworldToVpc invert");
  	}
          if (doInfinite) {
              vworldToVpc.getRotation(infVworldToVpc);
  	}
  
  	// Compute global flags
  	if (monoscopicViewPolicy == View.CYCLOPEAN_EYE_VIEW)
  	    effectiveMonoscopicViewPolicy = viewCache.monoscopicViewPolicy;
  	else
  	    effectiveMonoscopicViewPolicy = monoscopicViewPolicy;
  
  	// Recompute info about current canvas window
  	computeCanvasInfo();
  
  	// Compute coexistence center (in plate coordinates)
  	computeCoexistenceCenter();
  
  	// Get Eye position in image-plate coordinates
  	cacheEyePosition();
  
  	// Compute VPC to COE and COE to PLATE transforms
  	computeVpcToCoexistence();
  	computeCoexistenceToPlate();
  
  	// Compute view and projection matrices
  	computeView(doInfinite);
  
  
  	computePlateToVworld();
  
  	if (!currentFlag) {
  	    // save the result for use in RasterRetained computeWinCoord
  	    lastVworldToLeftPlate.set(vworldToLeftPlate);
  	}
  	computeHeadToVworld();
  
  	if (frustumBBox != null)
  	    computefrustumBBox(frustumBBox);
  
  	// Issue 109: cvc should *always* be null
          assert cvc == null;
  	if(cvc != null)
  	    copyComputedCanvasViewCache(cvc, doInfinite);
  
  	canvas.canvasDirty |= Canvas3D.VIEW_MATRIX_DIRTY;
  
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) {
  	    // Print some data :
  	    System.err.println("useStereo = " + useStereo);
  	    System.err.println("leftProjection:\n" + leftProjection);
  	    System.err.println("rightProjection:\n " + rightProjection);
  	    System.err.println("leftVpcToEc:\n" + leftVpcToEc);
  	    System.err.println("rightVpcToEc:\n" + rightVpcToEc);
  	    System.err.println("vpcToVworld:\n" + vpcToVworld);
  	    System.err.println("vworldToVpc:\n" + vworldToVpc);
  
  	    if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  		int i;
  		for (i = 0; i < leftFrustumPlanes.length; i++) {
  		    System.err.println("leftFrustumPlanes " + i + " is " +
  				       leftFrustumPlanes[i]);
  		}
  
  		for (i = 0; i < rightFrustumPlanes.length; i++) {
  		    System.err.println("rightFrustumPlanes " + i + " is " +
  				       rightFrustumPlanes[i]);
  		}
  	    }
  	}
  
      }
  
      private void computeCanvasInfo() {
  	// Copy the screen width and height info into derived parameters
  	physicalScreenWidth = screenViewCache.physicalScreenWidth;
  	physicalScreenHeight = screenViewCache.physicalScreenHeight;
  
  	screenWidth = screenViewCache.screenWidth;
  	screenHeight = screenViewCache.screenHeight;
  
  	metersPerPixelX = screenViewCache.metersPerPixelX;
  	metersPerPixelY = screenViewCache.metersPerPixelY;
  
  	// If a multi-screen virtual device (e.g. Xinerama) is being used,
  	// then awtCanvasX and awtCanvasY are relative to the origin of that
  	// virtual screen.  Subtract the origin of the physical screen to
  	// compute the origin in physical (image plate) coordinates.
  	Rectangle screenBounds = canvas.graphicsConfiguration.getBounds();
  	canvasX = awtCanvasX - screenBounds.x;
  	canvasY = awtCanvasY - screenBounds.y;
  
  	// Use awtCanvasWidth and awtCanvasHeight as reported.
  	canvasWidth = awtCanvasWidth;
  	canvasHeight = awtCanvasHeight;
  
  	// Convert the window system ``pixel'' coordinate location and size
  	// of the window into physical units (meters) and coordinate system.
  
  	// Window width and Height in meters
  	physicalWindowWidth = canvasWidth * metersPerPixelX;
  	physicalWindowHeight = canvasHeight * metersPerPixelY;
  
  	// Compute the 4 corners of the window in physical units
  	physicalWindowXLeft = metersPerPixelX *
  	    (double) canvasX;
  	physicalWindowYBottom = metersPerPixelY *
  	    (double)(screenHeight - canvasHeight - canvasY);
  
  	physicalWindowXRight = physicalWindowXLeft + physicalWindowWidth;
  	physicalWindowYTop = physicalWindowYBottom + physicalWindowHeight;
  
  	//  Cache the physical location of the center of the window
  	physicalWindowCenter.x =
  	    physicalWindowXLeft + physicalWindowWidth / 2.0;
  	physicalWindowCenter.y =
  	    physicalWindowYBottom + physicalWindowHeight / 2.0;
  	physicalWindowCenter.z = 0.0;
  
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    System.err.println("Canvas pos = (" + awtCanvasX + ", " +
  			       awtCanvasY + "), size = " + awtCanvasWidth +
  			       "x" + awtCanvasHeight);
  
  	    System.err.println("Window LL corner (in plate coordinates): " +
  		"(" + physicalWindowXLeft + "," + physicalWindowYBottom + ")");
  
  	    System.err.println("Window size (in plate coordinates): " +
  		"(" + physicalWindowWidth + "," + physicalWindowHeight + ")");
  
  	    System.err.println("Window center (in plate coordinates): " +
  			       physicalWindowCenter);
  
  	    System.err.println();
  	}
  
  	// Compute the view platform scale.  This combines
  	// the screen scale and the window scale.
  	computeViewPlatformScale();
  
  	if (!viewCache.compatibilityModeEnable &&
  	    viewCache.viewPolicy == View.HMD_VIEW) {
  	    if (!useStereo) {
  		switch(effectiveMonoscopicViewPolicy) {
  		case View.CYCLOPEAN_EYE_VIEW:
  		    if(J3dDebug.devPhase) {
  			System.err.println("CanvasViewCache : Should never reach here.\n" +
  					   "HMD_VIEW with CYCLOPEAN_EYE_VIEW is not allowed");
  		    }
  		    break;
  
  		case View.LEFT_EYE_VIEW:
  		    headTrackerToLeftImagePlate.set(screenViewCache.
  						    headTrackerToLeftImagePlate);
  		    break;
  
  		case View.RIGHT_EYE_VIEW:
   		    headTrackerToLeftImagePlate.set(screenViewCache.
  						    headTrackerToRightImagePlate);
  		    break;
  		}
  	    }
  	    else {
  		headTrackerToLeftImagePlate.set(screenViewCache.
  						headTrackerToLeftImagePlate);
  
  		headTrackerToRightImagePlate.set(screenViewCache.
  						 headTrackerToRightImagePlate);
   	    }
  
  	}
      }
  
      // Routine to compute the center of coexistence coordinates in
      // imageplate coordinates.  Also compute the scale from Vpc
      private void computeViewPlatformScale() {
  	windowScale = screenScale = 1.0;
  
  	if (!viewCache.compatibilityModeEnable) {
  	    switch (viewCache.screenScalePolicy) {
  	    case View.SCALE_SCREEN_SIZE:
  		screenScale = physicalScreenWidth / 2.0;
  		break;
  	    case View.SCALE_EXPLICIT:
  		screenScale = viewCache.screenScale;
  		break;
  	    }
  
  	    if (viewCache.windowResizePolicy == View.PHYSICAL_WORLD) {
  		windowScale = physicalWindowWidth / physicalScreenWidth;
  	    }
  	}
  
  	viewPlatformScale = windowScale * screenScale;
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    System.err.println("viewCache.windowResizePolicy = " +
  			       viewCache.windowResizePolicy);
  	    System.err.println("physicalWindowWidth = " + physicalWindowWidth);
  	    System.err.println("physicalScreenWidth = " + physicalScreenWidth);
  	    System.err.println("windowScale = " + windowScale);
  	    System.err.println("screenScale = " + screenScale);
  	    System.err.println("viewPlatformScale = " + viewPlatformScale);
  	}
      }
  
      private void cacheEyePosFixedField() {
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1))
  	    System.err.println("cacheEyePosFixedField:");
  
  	// y is always the window center
  	rightEyeInImagePlate.y =
  	    leftEyeInImagePlate.y =
  	    physicalWindowCenter.y;
  
  	if (!useStereo) {
  	    switch(effectiveMonoscopicViewPolicy) {
  	    case View.CYCLOPEAN_EYE_VIEW:
  		leftEyeInImagePlate.x = physicalWindowCenter.x;
  		break;
  
  	    case View.LEFT_EYE_VIEW:
  		leftEyeInImagePlate.x =
  		    physicalWindowCenter.x + viewCache.leftEyePosInHead.x;
  		break;
  
  	    case View.RIGHT_EYE_VIEW:
  		leftEyeInImagePlate.x =
  		    physicalWindowCenter.x + viewCache.rightEyePosInHead.x;
  		break;
  	    }
  
  	    // Set right as well just in case
  	    rightEyeInImagePlate.x = leftEyeInImagePlate.x;
  	}
  	else {
  	    leftEyeInImagePlate.x =
  		physicalWindowCenter.x + viewCache.leftEyePosInHead.x;
  
  	    rightEyeInImagePlate.x =
  		physicalWindowCenter.x + viewCache.rightEyePosInHead.x;
  	}
  
  	//
  	// Derive the z distance by constraining the field of view of the
  	// window width to be constant.
  	//
  	rightEyeInImagePlate.z =
  	    leftEyeInImagePlate.z =
  	    physicalWindowWidth /
  	    (2.0 * Math.tan(viewCache.fieldOfView / 2.0));
  
          // Denote that eyes-in-ImagePlate fields have changed so that
  	// these new values can be sent to the AudioDevice
          if (this.viewCache.view.soundScheduler != null)
              this.viewCache.view.soundScheduler.setListenerFlag(
                   SoundScheduler.EYE_POSITIONS_CHANGED);
      }
  
      /**
       *  Case of view eye position contrainted to center of window, but
       *  with z distance from plate eye pos.
       */
      private void cacheEyePosWindowRelative() {
  
  	if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1))
  	    System.err.println("cacheEyePosWindowRelative:");
  
  	// y is always the window center
  	rightEyeInImagePlate.y =
  	    leftEyeInImagePlate.y =
  	    physicalWindowCenter.y;
  
  	// z is always from the existing eye pos
  	rightEyeInImagePlate.z =
  	    leftEyeInImagePlate.z =
  	    leftManualEyeInImagePlate.z;
  
  	if (!useStereo) {
  
  	    switch(effectiveMonoscopicViewPolicy) {
  
  	    case View.CYCLOPEAN_EYE_VIEW:
  		leftEyeInImagePlate.x =
  		    physicalWindowCenter.x;
  		break;
  
  	    case View.LEFT_EYE_VIEW:
  		leftEyeInImagePlate.x =
  		    physicalWindowCenter.x +
  		    viewCache.leftEyePosInHead.x;
  		break;
  
  	    case View.RIGHT_EYE_VIEW:
  		leftEyeInImagePlate.x =
  		    physicalWindowCenter.x +
  		    viewCache.rightEyePosInHead.x;
  		    break;
  
  	    }
  
  	    // Set right as well just in case
  	    rightEyeInImagePlate.x =
  		leftEyeInImagePlate.x;
  
  	}
  	else {
  
  	    leftEyeInImagePlate.x =
  		physicalWindowCenter.x +
  		viewCache.leftEyePosInHead.x;
  
  	    rightEyeInImagePlate.x =
  		physicalWindowCenter.x +
  		viewCache.rightEyePosInHead.x;
  
  	    // Right z gets its own value
  	    rightEyeInImagePlate.z =
  		rightManualEyeInImagePlate.z;
  	}
  
  	// Denote that eyes-in-ImagePlate fields have changed so that
  	// these new values can be sent to the AudioDevice
          if (this.viewCache.view.soundScheduler != null)
              this.viewCache.view.soundScheduler.setListenerFlag(
                   SoundScheduler.EYE_POSITIONS_CHANGED);
      }
  
      /**
       * Common routine used when head tracking and when using manual
       * relative_to_screen eyepoint policy.
       */
      private void cacheEyePosScreenRelative(Point3d leftEye, Point3d rightEye) {
  	if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1))
  	    System.err.println("cacheEyePosScreenRelative:");
  
  	if (!useStereo) {
  	    switch(effectiveMonoscopicViewPolicy) {
  
  	    case View.CYCLOPEAN_EYE_VIEW:
  		leftEyeInImagePlate.x = (leftEye.x + rightEye.x) / 2.0;
  		leftEyeInImagePlate.y = (leftEye.y + rightEye.y) / 2.0;
  		leftEyeInImagePlate.z = (leftEye.z + rightEye.z) / 2.0;
  		break;
  
  	    case View.LEFT_EYE_VIEW:
  		leftEyeInImagePlate.set(leftEye);
  		break;
  
  	    case View.RIGHT_EYE_VIEW:
  		leftEyeInImagePlate.set(rightEye);
  		break;
  
  	    }
  
  	    // Set right as well just in case
  	    rightEyeInImagePlate.set(leftEyeInImagePlate);
  	}
  	else {
  	    leftEyeInImagePlate.set(leftEye);
  	    rightEyeInImagePlate.set(rightEye);
  	}
  
  	// Denote that eyes-in-ImagePlate fields have changed so that
  	// these new values can be sent to the AudioDevice
          if (this.viewCache.view.soundScheduler != null)
              this.viewCache.view.soundScheduler.setListenerFlag(
                   SoundScheduler.EYE_POSITIONS_CHANGED);
      }
  
      private void cacheEyePosCoexistenceRelative(Point3d leftManualEyeInCoexistence,
  						Point3d rightManualEyeInCoexistence) {
  
  	tPnt1.set(leftManualEyeInCoexistence);
  	viewCache.coexistenceToTrackerBase.transform(tPnt1);
  	screenViewCache.trackerBaseToImagePlate.transform(tPnt1);
  	tPnt1.add(coexistenceCenter);
  
  	tPnt2.set(rightManualEyeInCoexistence);
  	viewCache.coexistenceToTrackerBase.transform(tPnt2);
  	screenViewCache.trackerBaseToImagePlate.transform(tPnt2);
  	tPnt2.add(coexistenceCenter);
  
  	cacheEyePosScreenRelative(tPnt1, tPnt2);
  
      }
  
      /**
       * Compute the head-tracked eye position for the right and
       * left eyes.
       */
      private void computeTrackedEyePosition() {
  	if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    System.err.println("computeTrackedEyePosition:");
  	    System.err.println("viewCache.headTrackerToTrackerBase:");
  	    System.err.println(viewCache.headTrackerToTrackerBase);
  
  	    System.err.println("viewCache.headToHeadTracker:");
  	    System.err.println(viewCache.headToHeadTracker);
  	}
  
  	if (viewCache.viewPolicy != View.HMD_VIEW) {
  	    if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  		System.err.println("screenViewCache.trackerBaseToImagePlate:");
  		System.err.println(screenViewCache.trackerBaseToImagePlate);
  	    }
  
  	    headToLeftImagePlate.set(coexistenceCenter);
  	    headToLeftImagePlate.mul(screenViewCache.trackerBaseToImagePlate);
  	    headToLeftImagePlate.mul(viewCache.headTrackerToTrackerBase);
  	    headToLeftImagePlate.mul(viewCache.headToHeadTracker);
  
  	    headToLeftImagePlate.transform(viewCache.leftEyePosInHead,
  					   leftTrackedEyeInImagePlate);
  
  	    headToLeftImagePlate.transform(viewCache.rightEyePosInHead,
  					   rightTrackedEyeInImagePlate);
  	}
  	else {
  	    if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  		System.err.println("headTrackerToLeftImagePlate:");
  		System.err.println(headTrackerToLeftImagePlate);
  	    }
  
  	    headToLeftImagePlate.mul(headTrackerToLeftImagePlate,
  				     viewCache.headToHeadTracker);
  
  	    headToLeftImagePlate.transform(viewCache.leftEyePosInHead,
  					   leftTrackedEyeInImagePlate);
  
  	    if(useStereo) {
  		headToRightImagePlate.mul(headTrackerToRightImagePlate,
  					  viewCache.headToHeadTracker);
  
  		headToRightImagePlate.transform(viewCache.rightEyePosInHead,
  						rightTrackedEyeInImagePlate);
  	    }
  	    else { // HMD_VIEW with no stereo.
  		headToLeftImagePlate.transform(viewCache.rightEyePosInHead,
  					       rightTrackedEyeInImagePlate);
  	    }
  
  	}
  
  	if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    System.err.println("headToLeftImagePlate:");
  	    System.err.println(headToLeftImagePlate);
  	    System.err.println("headToRightImagePlate:");
  	    System.err.println(headToRightImagePlate);
  
  	}
      }
  
      /**
       * Routine to cache the current eye position in image plate
       * coordinates.
       */
      private void cacheEyePosition() {
  	if (viewCache.compatibilityModeEnable) {
  	    // XXXX: Compute compatibility mode eye position in ImagePlate???
  	    cacheEyePosScreenRelative(leftManualEyeInImagePlate,
  				      rightManualEyeInImagePlate);
  	}
  	else if (viewCache.getDoHeadTracking()) {
  	    computeTrackedEyePosition();
  	    cacheEyePosScreenRelative(leftTrackedEyeInImagePlate,
  				      rightTrackedEyeInImagePlate);
  	}
  	else {
  	    switch (viewCache.windowEyepointPolicy) {
  
  	    case View.RELATIVE_TO_FIELD_OF_VIEW:
  		cacheEyePosFixedField();
  		break;
  
  	    case View.RELATIVE_TO_WINDOW:
  		cacheEyePosWindowRelative();
  		break;
  
  	    case View.RELATIVE_TO_SCREEN:
  		cacheEyePosScreenRelative(leftManualEyeInImagePlate,
  					  rightManualEyeInImagePlate);
  		break;
  
  	    case View.RELATIVE_TO_COEXISTENCE:
   		cacheEyePosCoexistenceRelative(viewCache.leftManualEyeInCoexistence,
  					       viewCache.rightManualEyeInCoexistence);
  		break;
  	    }
  	}
  
  	// Compute center eye
  	centerEyeInImagePlate.add(leftEyeInImagePlate, rightEyeInImagePlate);
  	centerEyeInImagePlate.scale(0.5);
  
  	// Compute derived value of nominalEyeOffsetFromNominalScreen
  	if (viewCache.windowEyepointPolicy == View.RELATIVE_TO_FIELD_OF_VIEW)
  	    nominalEyeOffset = centerEyeInImagePlate.z;
  	else
  	    nominalEyeOffset = viewCache.nominalEyeOffsetFromNominalScreen;
  
  	if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) {
  	    System.err.println("leftEyeInImagePlate = " +
  			       leftEyeInImagePlate);
  	    System.err.println("rightEyeInImagePlate = " +
  			       rightEyeInImagePlate);
  	    System.err.println("centerEyeInImagePlate = " +
  			       centerEyeInImagePlate);
  	    System.err.println("nominalEyeOffset = " +
  			       nominalEyeOffset);
  	    System.err.println();
  	}
      }
  
      private void computePlateToVworld() {
  	if (viewCache.compatibilityModeEnable) {
  	    // XXXX: implement this correctly for compat mode
  	    leftPlateToVworld.setIdentity();
  	    vworldToLeftPlate.setIdentity();
  	}
  	else {
  	    try {
  		leftPlateToVpc.invert(vpcToLeftPlate);
  	    }
  	    catch (SingularMatrixException e) {
  		leftPlateToVpc.setIdentity();
  		/*
  		  System.err.println("SingularMatrixException encountered when doing" +
  		  " leftPlateToVpc invert");
  		  */
  	    }
  
  	    leftPlateToVworld.mul(vpcToVworld, leftPlateToVpc);
  	    vworldToLeftPlate.mul(vpcToLeftPlate, vworldToVpc);
  
  	    if(useStereo) {
  		try {
  		    rightPlateToVpc.invert(vpcToRightPlate);
  		}
  		catch (SingularMatrixException e) {
  		    rightPlateToVpc.setIdentity();
  		    /*
  		      System.err.println("SingularMatrixException encountered when doing" +
  		      " rightPlateToVpc invert");
  		      */
  		}
  
  		rightPlateToVworld.mul(vpcToVworld, rightPlateToVpc);
  		vworldToRightPlate.mul(vpcToRightPlate, vworldToVpc);
  
  	    }
  
  	    if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  		System.err.println("vpcToVworld:");
  		System.err.println(vpcToVworld);
  		System.err.println("vpcToLeftPlate:");
  		System.err.println(vpcToLeftPlate);
  		if(useStereo) {
  		    System.err.println("vpcToRightPlate:");
  		    System.err.println(vpcToRightPlate);
  
  		}
  
  	    }
  	}
  
  	// Denote that eyes-in-ImagePlate fields have changed so that
  	// these new values can be sent to the AudioDevice
          if (this.viewCache.view.soundScheduler != null)
              this.viewCache.view.soundScheduler.setListenerFlag(
                   SoundScheduler.IMAGE_PLATE_TO_VWORLD_CHANGED);
      }
  
  
      private void computeHeadToVworld() {
          // Concatenate headToLeftImagePlate with leftPlateToVworld
  
  	if (viewCache.compatibilityModeEnable) {
  	    // XXXX: implement this correctly for compat mode
  	    headToVworld.setIdentity();
  	}
  	else {
  	    headToVworld.mul(leftPlateToVworld, headToLeftImagePlate);
  
  	    if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  		System.err.println("leftPlateToVworld:");
  		System.err.println(leftPlateToVworld);
  		System.err.println("headToLeftImagePlate:");
  		System.err.println(headToLeftImagePlate);
  		System.err.println("...gives -> headToVworld:");
  		System.err.println(headToVworld);
  	    }
  	}
  
  	// Denote that eyes-in-ImagePlate fields have changed so that
  	// these new values can be sent to the AudioDevice
          if (this.viewCache.view.soundScheduler != null)
              this.viewCache.view.soundScheduler.setListenerFlag(
                   SoundScheduler.HEAD_TO_VWORLD_CHANGED);
      }
  
      private void computeVpcToCoexistence() {
  	// Create a transform with the view platform to coexistence scale
  	tMat1.set(viewPlatformScale);
  
  	// XXXX: Is this really correct to ignore HMD?
  
  	if (viewCache.viewPolicy != View.HMD_VIEW) {
  	    switch (viewCache.coexistenceCenterInPworldPolicy) {
  	    case View.NOMINAL_SCREEN :
  		switch (viewCache.viewAttachPolicy) {
  		case View.NOMINAL_SCREEN:
  		    tMat2.setIdentity();
  		    break;
  		case View.NOMINAL_HEAD:
  		    tVec1.set(0.0, 0.0, nominalEyeOffset);
  		    tMat2.set(tVec1);
  		    break;
  		case View.NOMINAL_FEET:
  		    tVec1.set(0.0, -viewCache.nominalEyeHeightFromGround,
  			      nominalEyeOffset);
  		    tMat2.set(tVec1);
  		    break;
  		}
  
  		break;
  	    case View.NOMINAL_HEAD :
  		switch (viewCache.viewAttachPolicy) {
  		case View.NOMINAL_SCREEN:
  		    tVec1.set(0.0, 0.0, -nominalEyeOffset);
  		    tMat2.set(tVec1);
  		    break;
  		case View.NOMINAL_HEAD:
  		    tMat2.setIdentity();
  		    break;
  		case View.NOMINAL_FEET:
  		    tVec1.set(0.0, -viewCache.nominalEyeHeightFromGround,
  			      0.0);
  		    tMat2.set(tVec1);
  		    break;
  		}
  		break;
  	      case View.NOMINAL_FEET:
  		switch (viewCache.viewAttachPolicy) {
  		case View.NOMINAL_SCREEN:
  		    tVec1.set(0.0,
  			      viewCache.nominalEyeHeightFromGround, -nominalEyeOffset);
  		    tMat2.set(tVec1);
  		    break;
  		case View.NOMINAL_HEAD:
  		    tVec1.set(0.0, viewCache.nominalEyeHeightFromGround,
  			      0.0);
  		    tMat2.set(tVec1);
  
  		    break;
  		case View.NOMINAL_FEET:
  		    tMat2.setIdentity();
  		    break;
  		}
  		break;
  	    }
  
  	    vpcToCoexistence.mul(tMat2, tMat1);
  	}
  	else {
  	    vpcToCoexistence.set(tMat1);
  	}
  
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    System.err.println("vpcToCoexistence:");
  	    System.err.println(vpcToCoexistence);
  	}
      }
  
      private void computeCoexistenceCenter() {
  
  	if ((!viewCache.compatibilityModeEnable) &&
  	    (viewCache.viewPolicy != View.HMD_VIEW) &&
  	    (viewCache.coexistenceCenteringEnable) &&
  	    (viewCache.coexistenceCenterInPworldPolicy == View.NOMINAL_SCREEN)) {
  
  	    // Compute the coexistence center in image plate coordinates
  
  	    // Image plate cordinates have their orgin in the lower
  	    // left hand corner of the CRT visiable raster.
  	    // The nominal coexistence center is at the *center* of
  	    // targeted area: either the window or screen, depending
  	    // on policy.
  	    if (viewCache.windowMovementPolicy == View.VIRTUAL_WORLD) {
  		coexistenceCenter.x = physicalScreenWidth / 2.0;
  		coexistenceCenter.y = physicalScreenHeight / 2.0;
  		coexistenceCenter.z = 0.0;
  	    }
  	    else { // windowMovementPolicy == PHYSICAL_WORLD
  		coexistenceCenter.x = physicalWindowCenter.x;
  		coexistenceCenter.y = physicalWindowCenter.y;
  		coexistenceCenter.z = 0.0;
  	    }
  	}
  	else {
  	    coexistenceCenter.set(0.0, 0.0, 0.0);
  	}
  
  	if(J3dDebug.devPhase) {
  	    if (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1) {
  		System.err.println("coexistenceCenter = " + coexistenceCenter);
  	    }
  	}
      }
  
      private void computeCoexistenceToPlate() {
  	if (viewCache.compatibilityModeEnable) {
  	    // XXXX: implement this correctly
  	    coexistenceToLeftPlate.setIdentity();
  	    return;
  	}
  
  	if (viewCache.viewPolicy != View.HMD_VIEW) {
  	    coexistenceToLeftPlate.set(coexistenceCenter);
  	    coexistenceToLeftPlate.mul(screenViewCache.trackerBaseToImagePlate);
  	    coexistenceToLeftPlate.mul(viewCache.coexistenceToTrackerBase);
  
  	    if(useStereo) {
  		coexistenceToRightPlate.set(coexistenceToLeftPlate);
  	    }
  	}
  	else {
  	    coexistenceToLeftPlate.mul(headTrackerToLeftImagePlate,
  				       viewCache.trackerBaseToHeadTracker);
  	    coexistenceToLeftPlate.mul(viewCache.coexistenceToTrackerBase);
  
  	    if(useStereo) {
  		coexistenceToRightPlate.mul(headTrackerToRightImagePlate,
  					    viewCache.trackerBaseToHeadTracker);
  		coexistenceToRightPlate.mul(viewCache.coexistenceToTrackerBase);
  	    }
  	}
  
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    System.err.println("coexistenceToLeftPlate:");
  	    System.err.println(coexistenceToLeftPlate);
  	    if(useStereo) {
  		System.err.println("coexistenceToRightPlate:");
  		System.err.println(coexistenceToRightPlate);
  
  	    }
  	}
      }
  
      /**
       * Computes the viewing matrices.
       *
       * computeView computes the following:
       *
       * <ul>
       * left (& right) eye viewing matrices (only left is valid for mono view)
       * </ul>
       *
       * This call works for both fixed screen and HMD displays.
       */
      private void computeView(boolean doInfinite) {
  	int		backClipPolicy;
  	double		Fl, Fr, B, scale, backClipDistance;
  
          // compute scale used for transforming clip and fog distances
          vworldToCoexistenceScale = vworldToVpc.getDistanceScale()
                  * vpcToCoexistence.getDistanceScale();
          if(doInfinite) {
              infVworldToCoexistenceScale = infVworldToVpc.getDistanceScale()
                  * vpcToCoexistence.getDistanceScale();
          }
  
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    System.err.println("vworldToCoexistenceScale = " +
  			       vworldToCoexistenceScale);
  	}
  
          // compute coexistenceToVworld transform -- dirty bit candidate!!
          tempTrans.mul(viewCache.coexistenceToTrackerBase, vpcToCoexistence);
          vworldToTrackerBase.mul(tempTrans, vworldToVpc);
  
  	// If we are in compatibility mode, compute the view and
  	// projection matrices accordingly
  	if (viewCache.compatibilityModeEnable) {
  	    leftProjection.set(viewCache.compatLeftProjection);
  	    leftVpcToEc.set(viewCache.compatVpcToEc);
  
  	    if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) {
  		System.err.println("Left projection and view matrices");
  		System.err.println("ecToCc (leftProjection) :");
  		System.err.println(leftProjection);
  		System.err.println("vpcToEc:");
  		System.err.println(leftVpcToEc);
  	    }
  
  	    computeFrustumPlanes(leftProjection, leftVpcToEc,
  				 leftFrustumPlanes, leftFrustumPoints,
                                   leftCcToVworld);
  
  	    if(useStereo) {
  		rightProjection.set(viewCache.compatRightProjection);
  		rightVpcToEc.set(viewCache.compatVpcToEc);
  
  		if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) {
  		    System.err.println("Right projection and view matrices");
  		    System.err.println("ecToCc:");
  		    System.err.println("vpcToEc:");
  		    System.err.println(rightVpcToEc);
  		}
  
  		computeFrustumPlanes(rightProjection, rightVpcToEc,
  				     rightFrustumPlanes, rightFrustumPoints,
                                       rightCcToVworld);
  	    }
  
  	    return;
  	}
  
  	//
  	//  The clipping plane distances are set from the internal policy.
  	//
  	//  Note that the plane distance follows the standard Z axis
  	//  convention, e.g. negative numbers further away.
  	//  Note that for policy from eye, the distance is negative in
  	//  the direction of z in front of the eye.
  	//  Note that for policy from screen, the distance is negative for
  	//  locations behind the screen, and positive in front.
  	//
  	//  The distance attributes are measured either in physical (plate)
  	//  units, or vworld units.
  	//
  
  	// Compute scale factor for front clip plane computation
  	if (viewCache.frontClipPolicy == View.VIRTUAL_EYE ||
  	    viewCache.frontClipPolicy == View.VIRTUAL_SCREEN) {
              scale = vworldToCoexistenceScale;
  	}
  	else {
  	    scale = windowScale;
  	}
  
  	// Set left and right front clipping plane distances.
  	if(viewCache.frontClipPolicy == View.PHYSICAL_EYE ||
  	   viewCache.frontClipPolicy == View.VIRTUAL_EYE) {
  	    Fl = leftEyeInImagePlate.z +
  		scale * -viewCache.frontClipDistance;
  	    Fr = rightEyeInImagePlate.z +
  		scale * -viewCache.frontClipDistance;
  	}
  	else {
  	    Fl = scale * -viewCache.frontClipDistance;
  	    Fr = scale * -viewCache.frontClipDistance;
  	}
  
          // if there is an active clip node, use it and ignore the view's
          // backclip
          if ((renderBin != null) && (renderBin.backClipActive)) {
              backClipPolicy = View.VIRTUAL_EYE;
              backClipDistance = renderBin.backClipDistanceInVworld;
          } else {
              backClipPolicy = viewCache.backClipPolicy;
              backClipDistance = viewCache.backClipDistance;
          }
  
  	// Compute scale factor for rear clip plane computation
  	if (backClipPolicy == View.VIRTUAL_EYE ||
  	    backClipPolicy == View.VIRTUAL_SCREEN) {
              scale = vworldToCoexistenceScale;
  	}
  	else {
  	    scale = windowScale;
  	}
  
  	// Set left and right rear clipping plane distnaces.
  	if(backClipPolicy == View.PHYSICAL_EYE ||
  	   backClipPolicy == View.VIRTUAL_EYE) {
  	    // Yes, left for both left and right rear.
  	    B = leftEyeInImagePlate.z +
  		scale * -backClipDistance;
  	}
  	else {
  	    B = scale * -backClipDistance;
  	}
  
  	// XXXX: Can optimize for HMD case.
  	if (true /*viewCache.viewPolicy != View.HMD_VIEW*/) {
  
  	    // Call buildProjView to build the projection and view matrices.
  
  	    if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  		System.err.println("Left projection and view matrices");
  		System.err.println("Fl " + Fl + " B " + B);
  		System.err.println("leftEyeInImagePlate\n" + leftEyeInImagePlate);
  		System.err.println("Before : leftProjection\n" + leftProjection);
  		System.err.println("Before leftVpcToEc\n" + leftVpcToEc);
  	    }
  
  	    buildProjView(leftEyeInImagePlate, coexistenceToLeftPlate,
  			  vpcToLeftPlate, Fl, B, leftProjection, leftVpcToEc, false);
  
  
  	    if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  		System.err.println("After : leftProjection\n" + leftProjection);
  		System.err.println("After leftVpcToEc\n" + leftVpcToEc);
  	    }
  
  	    computeFrustumPlanes(leftProjection, leftVpcToEc,
  				 leftFrustumPlanes, leftFrustumPoints,
                                   leftCcToVworld);
  
  	    if(useStereo) {
  		if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2))
  		    System.err.println("Right projection and view matrices");
  
  		buildProjView(rightEyeInImagePlate, coexistenceToRightPlate,
  			      vpcToRightPlate, Fr, B, rightProjection,
  			      rightVpcToEc, false);
  
  		computeFrustumPlanes(rightProjection, rightVpcToEc,
  				     rightFrustumPlanes, rightFrustumPoints,
                                       rightCcToVworld);
  	    }
  
  	    //
  	    // Now to compute the left (& right) eye (and infinite)
  	    // viewing matrices.
  	    if(doInfinite) {
  		// Call buildProjView separately for infinite view
  		buildProjView(leftEyeInImagePlate, coexistenceToLeftPlate,
  			      vpcToLeftPlate, leftEyeInImagePlate.z - 0.05,
  			      leftEyeInImagePlate.z - 1.5,
  			      infLeftProjection, infLeftVpcToEc, true);
  
  		if(useStereo) {
  		    buildProjView(rightEyeInImagePlate, coexistenceToRightPlate,
  				  vpcToRightPlate, rightEyeInImagePlate.z - 0.05,
  				  rightEyeInImagePlate.z - 1.5,
  				  infRightProjection, infRightVpcToEc, true);
  
  		}
  	    }
  	}
  	// XXXX: The following code has never been ported
  //      else {
  //	    Point3d cen_eye;
  //
  //	    // HMD case.  Just concatenate the approprate matrices together.
  //	    // Additional work just for now
  //
  //	    compute_lr_plate_to_cc( &cen_eye, Fl, B, 0, &vb, 0);
  //
  //	    if(useStereo) {
  //		mat_mul_dpt(&right_eye_pos_in_head,
  //			    head_to_right_plate, &cen_eye);
  //		compute_lr_plate_to_cc( &cen_eye, Fr, B,
  //				       1, &vb, 0);
  //	    }
  //
  // 	    //  Make sure that coexistence_to_plate is current.
  // 	    //  (It is usually constant for fixed plates, always varies for HMDs.)
  // 	    //  For HMD case, computes finial matrices that will be used.
  // 	    //
  // 	    computeCoexistenceToPlate();
  //	}
  
      }
  
      /**
       * Debugging routine to analyze the projection matrix.
       */
      private void analyzeProjection(Transform3D p, double xMax) {
  	if (viewCache.projectionPolicy == View.PARALLEL_PROJECTION)
  	    System.err.println("PARALLEL_PROJECTION =");
  	else
  	    System.err.println("PERSPECTIVE_PROJECTION =");
  
  	System.err.println(p);
  
  	double projectionPlaneZ = ((p.mat[0] * xMax + p.mat[3] - p.mat[15]) /
  				   (p.mat[14] - p.mat[2]));
  
  	System.err.println("projection plane at z = " + projectionPlaneZ);
      }
  
      /**
       *  buildProjView creates a projection and viewing matrix.
       *
       *  Inputs:
       *     ep :		    eye point, in plate coordinates
       * coe2Plate :          matrix from coexistence to image plate.
       *     F, B :	    front, back clipping planes, in plate coordinates
       *     doInfinite :	    flag to indicate ``at infinity'' view desired
       *
       *  Output:
       *   vpc2Plate :       matric from vpc to image plate.
       *     ecToCc :	    projection matrix from Eye Coordinates (EC)
       *			    to Clipping Coordinates (CC)
       *     vpcToEc :        view matrix from ViewPlatform Coordinates (VPC)
       *			    to Eye Coordinates (EC)
       */
      private void buildProjView(Point3d		ep,
  			       Transform3D      coe2Plate,
  			       Transform3D      vpc2Plate,
  			       double		F,
  			       double		B,
  			       Transform3D	ecToCc,
  			       Transform3D	vpcToEc,
  			       boolean		doInfinite) {
  
  	// Lx,Ly Hx,Hy will be adjusted window boundaries
  	double		Lx, Hx, Ly, Hy;
  	Lx = physicalWindowXLeft; Hx = physicalWindowXRight;
  	Ly = physicalWindowYBottom; Hy = physicalWindowYTop;
  
  	ecToCc.setIdentity();
  
  
  	// XXXX: we have no concept of glass correction in the Java 3D API
  	//
  	// Correction in apparent 3D position of window due to glass/CRT
  	// and spherical/cylinderical curvarure of CRT.
  	// This boils down to producing modified values of Lx Ly Hx Hy
  	// and is different for hot spot vs. window center corrections.
  	//
  	/* XXXX:
  	double		cx, cy;
  	if(viewPolicy != HMD_VIEW && enable_crt_glass_correction) {
  	    if (correction_point == CORRECTION_POINT_WINDOW_CENTER) {
  		correct_crt( ep, Lx, Ly, &cx, &cy); Lx = cx; Ly = cy;
  		correct_crt( ep, Hx, Hy, &cx, &cy); Hx = cx; Hy = cy;
  	    }
  	    else {  // must be hot spot correction
  		// Not real code yet, for now just do same as above.
  		correct_crt( ep, Lx, Ly, &cx, &cy); Lx = cx; Ly = cy;
  		correct_crt( ep, Hx, Hy, &cx, &cy); Hx = cx; Hy = cy;
  	    }
  	}
  	*/
  
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    System.err.println("ep = " + ep);
  	    System.err.println("Lx = " + Lx + ", Hx = " + Hx);
  	    System.err.println("Ly = " + Ly + ", Hy = " + Hy);
  	    System.err.println("F = " + F + ", B = " + B);
  	}
  
  	// Compute the proper projection equation.  Note that we
  	// do this in two steps: first we generate ImagePlateToCc,
  	// then we translate this by EcToPlate, resulting in a
  	// projection from EctoCc.
  	//
  	// A more efficient (and more accurate) approach would be to
  	// modify the equations below to directly project from EcToCc.
  
  	if (viewCache.projectionPolicy == View.PARALLEL_PROJECTION) {
  	    double inv_dx, inv_dy, inv_dz;
  	    inv_dx = 1.0 / (Hx - Lx);
  	    inv_dy = 1.0 / (Hy - Ly);
  	    inv_dz = 1.0 / (F - B);
  
  	    ecToCc.mat[0] = 2.0 * inv_dx;
  	    ecToCc.mat[3] = -(Hx + Lx) * inv_dx;
  	    ecToCc.mat[5] = 2.0 * inv_dy;
  	    ecToCc.mat[7] = -(Hy + Ly) * inv_dy;
  	    ecToCc.mat[10] = 2.0 * inv_dz;
  	    ecToCc.mat[11] = -(F + B) * inv_dz;
  	}
  	else {
  	    double sxy, rzb, inv_dx, inv_dy;
  
  	    inv_dx = 1.0 / (Hx - Lx);
  	    inv_dy = 1.0 / (Hy - Ly);
  	    rzb = 1.0/(ep.z - B);
  	    sxy = ep.z*rzb;
  
  	    ecToCc.mat[0] = sxy*2.0*inv_dx;
  	    ecToCc.mat[5] = sxy*2.0*inv_dy;
  
  	    ecToCc.mat[2] = rzb*(Hx+Lx - 2.0*ep.x)*inv_dx;
  	    ecToCc.mat[6] = rzb*(Hy+Ly - 2.0*ep.y)*inv_dy;
  	    ecToCc.mat[10] = rzb*(B+F-2*ep.z)/(B-F);
  	    ecToCc.mat[14] = -rzb;
  
  	    ecToCc.mat[3] = sxy*(-Hx-Lx)*inv_dx;
  	    ecToCc.mat[7] = sxy*(-Hy-Ly)*inv_dy;
  	    ecToCc.mat[11] = rzb*(B - ep.z - B*(B+F - 2*ep.z)/(B-F));
  	    ecToCc.mat[15] = sxy;
  	}
  
  	// Since we set the matrix elements ourselves, we need to set the
  	// type field.  A value of 0 means a non-affine matrix.
  	ecToCc.setOrthoDirtyBit();
  
  	// EC to ImagePlate matrix is a simple translation.
  	tVec1.set(ep.x, ep.y, ep.z);
  	tMat1.set(tVec1);
  	ecToCc.mul(tMat1);
  
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    System.err.println("ecToCc:");
  	    analyzeProjection(ecToCc, Hx);
  	}
  
  	if(!doInfinite) {
  	    // View matrix is:
  	    //  [plateToEc] [coexistence_to_plate] [vpc_to_coexistence]
  	    //  where vpc_to_coexistence includes the viewPlatformScale
  
  	    // First compute ViewPlatform to Plate
  	    vpc2Plate.mul(coe2Plate, vpcToCoexistence);
  
  	    // ImagePlate to EC matrix is a simple translation.
  	    tVec1.set(-ep.x, -ep.y, -ep.z);
  	    tMat1.set(tVec1);
  	    vpcToEc.mul(tMat1, vpc2Plate);
  
  	    if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  		System.err.println("vpcToEc:");
  		System.err.println(vpcToEc);
  	    }
  	}
  	else {
  	    // Final infinite composite is:
  	    //   [coexistence_to_eye] [vpc_to_coexistence (vom)]
  	    //   (does vworld_to_coe_scale_factor get used here??? )
  	    //
  	    // The method is to relocate the coexistence org centered on
  	    // the eye rather than the window center (via coexistence_to_eye).
  	    // Computationaly simpler simplifed equation form may exist.
  
  	    // coexistence to eye is a simple translation.
  /*
  	    tVec1.set(ep.x, ep.y, ep.z);
  	    tMat1.set(tVec1);
  	    vpcToEc.mul(tMat1, vpcToCoexistence);
  	    // First compute ViewPlatform to Plate
  	    vpcToPlate.mul(coexistenceToPlatevpcToPlate, vpcToCoexistence);
  */
  
  	    // ImagePlate to EC matrix is a simple translation.
  	    tVec1.set(-ep.x, -ep.y, -ep.z);
  	    tMat1.set(tVec1);
  	    tMat1.mul(tMat1, vpc2Plate);
  	    tMat1.getRotation(vpcToEc); // use only rotation component of transform
  
  	}
  
      }
  
      /**
       * Compute the plane equations for the frustum in ViewPlatform
       * coordinates, plus its viewing frustum points.  ccToVworld will
       * be cached - used by Canavs3D.getInverseVworldProjection().
       */
      private void computeFrustumPlanes(Transform3D ecToCc,
  				      Transform3D vpcToEc,
  				      Vector4d [] frustumPlanes,
  				      Point4d [] frustumPoints,
                                        Transform3D ccToVworld) {
  
  	// Compute the inverse of the Vworld to Cc transform.  This
  	// gives us the Cc to Vworld transform.
  	tMat2.mul(ecToCc, vpcToEc);
  	ccToVworld.mul(tMat2, vworldToVpc);
  	// System.err.println("ccToVworld = " + ccToVworld);
  	try {
  	    ccToVworld.invert();
  	}
  	catch (SingularMatrixException e) {
  	    ccToVworld.setIdentity();
  	    // System.err.println("SingularMatrixException encountered when doing invert in computeFrustumPlanes");
  	}
  
  	if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) {
  	    Transform3D t = new Transform3D();
  	    t.mul(ecToCc, vpcToEc);
  	    t.mul(vworldToVpc);
  	    System.err.println("\nvworldToCc = " + t);
  	    System.err.println("ccToVworld = " + ccToVworld);
  	    t.mul(ccToVworld);
  	    System.err.println("vworldToCc * ccToVworld = " + t);
  	}
  
  	// Transform the 8 corners of the viewing frustum into Vpc
  	frustumPoints[0].set(-1.0, -1.0,  1.0, 1.0);  // lower-left-front
  	frustumPoints[1].set(-1.0,  1.0,  1.0, 1.0);  // upper-left-front
  	frustumPoints[2].set( 1.0,  1.0,  1.0, 1.0);  // upper-right-front
  	frustumPoints[3].set( 1.0, -1.0,  1.0, 1.0);  // lower-right-front
  	frustumPoints[4].set(-1.0, -1.0, -1.0, 1.0);  // lower-left-back
  	frustumPoints[5].set(-1.0,  1.0, -1.0, 1.0);  // upper-left-back
  	frustumPoints[6].set( 1.0,  1.0, -1.0, 1.0);  // upper-right-back
  	frustumPoints[7].set( 1.0, -1.0, -1.0, 1.0);  // lower-right-back
  
  	ccToVworld.get(tMatrix);
  	int i;
  	for (i = 0; i < frustumPoints.length; i++) {
  	    tMatrix.transform(frustumPoints[i]);
  	    double w_inv = 1.0 / frustumPoints[i].w;
  	    frustumPoints[i].x *= w_inv;
  	    frustumPoints[i].y *= w_inv;
  	    frustumPoints[i].z *= w_inv;
  	}
  
  	// Now compute the 6 plane equations
  	// left
  	computePlaneEq(frustumPoints[0], frustumPoints[4],
  		       frustumPoints[5], frustumPoints[1],
  		       frustumPlanes[0]);
  
  	// right
  	computePlaneEq(frustumPoints[3], frustumPoints[2],
  		       frustumPoints[6], frustumPoints[7],
  		       frustumPlanes[1]);
  
  	// top
  	computePlaneEq(frustumPoints[1], frustumPoints[5],
  		       frustumPoints[6], frustumPoints[2],
  		       frustumPlanes[2]);
  
  	// bottom
  	computePlaneEq(frustumPoints[0], frustumPoints[3],
  		       frustumPoints[7], frustumPoints[4],
  		       frustumPlanes[3]);
  
  	// front
  	computePlaneEq(frustumPoints[0], frustumPoints[1],
  		       frustumPoints[2], frustumPoints[3],
  		       frustumPlanes[4]);
  
  	// back
  	computePlaneEq(frustumPoints[4], frustumPoints[7],
  		       frustumPoints[6], frustumPoints[5],
  		       frustumPlanes[5]);
  
  	//System.err.println("left plane = "   + frustumPlanes[0]);
  	//System.err.println("right plane = "  + frustumPlanes[1]);
  	//System.err.println("top plane = "    + frustumPlanes[2]);
  	//System.err.println("bottom plane = " + frustumPlanes[3]);
  	//System.err.println("front plane = "  + frustumPlanes[4]);
  	//System.err.println("back plane = "   + frustumPlanes[5]);
      }
  
      private void computePlaneEq(Point4d p1, Point4d p2, Point4d p3, Point4d p4,
  				Vector4d planeEq) {
  	tVec1.x = p3.x - p1.x;
  	tVec1.y = p3.y - p1.y;
  	tVec1.z = p3.z - p1.z;
  
  	tVec2.x = p2.x - p1.x;
  	tVec2.y = p2.y - p1.y;
  	tVec2.z = p2.z - p1.z;
  
  	tVec3.cross(tVec2, tVec1);
  	tVec3.normalize();
  	planeEq.x = tVec3.x;
  	planeEq.y = tVec3.y;
  	planeEq.z = tVec3.z;
  	planeEq.w = -(planeEq.x * p1.x + planeEq.y * p1.y + planeEq.z * p1.z);
      }
  
      // Get methods for returning derived data values.
      // Eventually, these get functions will cause some of the parameters
      // to be lazily evaluated.
      //
      // NOTE: in the case of Transform3D, and Tuple objects, a reference
      // to the actual derived data is returned.  In these cases, the caller
      // must ensure that the returned data is not modified.
      //
      // NOTE: the snapshot and computeDerivedData methods are synchronized.
      // Callers of the following methods that can run asynchronously with
      // the renderer must call these methods and copy the data from within
      // a synchronized block on the canvas view cache object.
  
      int getCanvasX() {
  	return canvasX;
      }
  
      int getCanvasY() {
  	return canvasY;
      }
  
      int getCanvasWidth() {
  	return canvasWidth;
      }
  
      int getCanvasHeight() {
  	return canvasHeight;
      }
  
      double getPhysicalWindowWidth() {
  	return physicalWindowWidth;
      }
  
      double getPhysicalWindowHeight() {
  	return physicalWindowHeight;
      }
  
      boolean getUseStereo() {
  	return useStereo;
      }
  
      Transform3D getLeftProjection() {
  	return leftProjection;
      }
  
      Transform3D getRightProjection() {
  	return rightProjection;
      }
  
      Transform3D getLeftVpcToEc() {
  	return leftVpcToEc;
      }
  
  