/* * 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: * * * * 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; } Transform3D getRightVpcToEc() { return rightVpcToEc; } Transform3D getLeftEcToVpc() { return leftEcToVpc; } Transform3D getRightEcToVpc() { return rightEcToVpc; } Transform3D getInfLeftProjection() { return infLeftProjection; } Transform3D getInfRightProjection() { return infLeftProjection; } Transform3D getInfLeftVpcToEc() { return infLeftVpcToEc; } Transform3D getInfRightVpcToEc() { return infRightVpcToEc; } Transform3D getInfLeftEcToVpc() { return infLeftEcToVpc; } Transform3D getInfgRightEcToVpc() { return infRightEcToVpc; } Transform3D getInfVworldToVpc() { return infVworldToVpc; } Transform3D getLeftCcToVworld() { return leftCcToVworld; } Transform3D getRightCcToVworld() { return rightCcToVworld; } Transform3D getImagePlateToVworld() { // XXXX: Document -- This will return the transform of left plate. return leftPlateToVworld; } Transform3D getLastVworldToImagePlate() { // XXXX: Document -- This will return the transform of left plate. return lastVworldToLeftPlate; } Transform3D getVworldToImagePlate() { // XXXX: Document -- This will return the transform of left plate. return vworldToLeftPlate; } Transform3D getVworldToTrackerBase() { return vworldToTrackerBase; } double getVworldToCoexistenceScale() { return vworldToCoexistenceScale; } double getInfVworldToCoexistenceScale() { return infVworldToCoexistenceScale; } Point3d getLeftEyeInImagePlate() { return leftEyeInImagePlate; } Point3d getRightEyeInImagePlate() { return rightEyeInImagePlate; } Point3d getCenterEyeInImagePlate() { return centerEyeInImagePlate; } Transform3D getHeadToVworld() { return headToVworld; } Transform3D getVpcToVworld() { return vpcToVworld; } Transform3D getVworldToVpc() { return vworldToVpc; } // Transform the specified X point in AWT window-relative coordinates // to image plate coordinates double getWindowXInImagePlate(double x) { double xScreen = x + (double)canvasX; return metersPerPixelX * xScreen; } // Transform the specified Y point in AWT window-relative coordinates // to image plate coordinates double getWindowYInImagePlate(double y) { double yScreen = y + (double)canvasY; return metersPerPixelY * ((double)(screenHeight - 1) - yScreen); } Vector4d[] getLeftFrustumPlanesInVworld() { return leftFrustumPlanes; } Vector4d[] getRightFrustumPlanesInVworld() { return rightFrustumPlanes; } void getPixelLocationInImagePlate(double x, double y, double z, Point3d imagePlatePoint) { double screenx = (x + canvasX)*metersPerPixelX; double screeny = (screenHeight - 1 - canvasY - y)*metersPerPixelY; if ((viewCache.projectionPolicy == View.PERSPECTIVE_PROJECTION) && (centerEyeInImagePlate.z != 0)) { double zScale = 1.0 - z/centerEyeInImagePlate.z; imagePlatePoint.x = (screenx - centerEyeInImagePlate.x)*zScale + centerEyeInImagePlate.x; imagePlatePoint.y = (screeny - centerEyeInImagePlate.y)*zScale + centerEyeInImagePlate.y; } else { imagePlatePoint.x = screenx; imagePlatePoint.y = screeny; } imagePlatePoint.z = z; } /** * Projects the specified point from image plate coordinates * into AWT pixel coordinates. */ void getPixelLocationFromImagePlate(Point3d imagePlatePoint, Point2d pixelLocation) { double screenX, screenY; if(viewCache.projectionPolicy == View.PERSPECTIVE_PROJECTION) { // get the vector from centerEyeInImagePlate to imagePlatePoint tVec1.sub(imagePlatePoint, centerEyeInImagePlate); // Scale this vector to make it end at the projection plane. // Scale is ratio : // eye->imagePlate Plane dist / eye->imagePlatePt dist // eye dist to plane is eyePos.z (eye is in +z space) // image->eye dist is -tVec1.z (image->eye is in -z dir) //System.err.println("eye dist = " + (centerEyeInImagePlate.z)); //System.err.println("image dist = " + (-tVec1.z)); if (tVec1.z != 0) { double zScale = centerEyeInImagePlate.z / (-tVec1.z); screenX = centerEyeInImagePlate.x + tVec1.x * zScale; screenY = centerEyeInImagePlate.y + tVec1.y * zScale; } else { screenX = imagePlatePoint.x; screenY = imagePlatePoint.y; } } else { screenX = imagePlatePoint.x; screenY = imagePlatePoint.y; } //System.err.println("screenX = " + screenX + " screenY = " + screenY); // Note: screenPt is in image plate coords, at z=0 // Transform from image plate coords to screen coords pixelLocation.x = (screenX / screenViewCache.metersPerPixelX) - canvasX; pixelLocation.y = screenViewCache.screenHeight - 1 - (screenY / screenViewCache.metersPerPixelY) - canvasY; //System.err.println("pixelLocation = " + pixelLocation); } /** * Constructs and initializes a CanvasViewCache object. * Note that the canvas, screen, screenCache, view, and * viewCache parameters are all fixed at construction time * and must be non-null. */ CanvasViewCache(Canvas3D canvas, ScreenViewCache screenViewCache, ViewCache viewCache) { this.canvas = canvas; this.screenViewCache = screenViewCache; this.viewCache = viewCache; // Set up the initial plane equations int i; for (i = 0; i < leftFrustumPlanes.length; i++) { leftFrustumPlanes[i] = new Vector4d(); rightFrustumPlanes[i] = new Vector4d(); } for (i = 0; i < leftFrustumPoints.length; i++) { leftFrustumPoints[i] = new Point4d(); rightFrustumPoints[i] = new Point4d(); } // canvas is null in Renderer copyOfCvCache if (canvas != null) { leftEyeInImagePlate.set(canvas.leftManualEyeInImagePlate); rightEyeInImagePlate.set(canvas.rightManualEyeInImagePlate); centerEyeInImagePlate.add(leftEyeInImagePlate, rightEyeInImagePlate); centerEyeInImagePlate.scale(0.5); } if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) System.err.println("Constructed a CanvasViewCache"); } synchronized void setCanvas(Canvas3D c) { canvas = c; } synchronized void setScreenViewCache(ScreenViewCache svc) { screenViewCache = svc; } synchronized void setViewCache(ViewCache vc) { viewCache = vc; } }