/*
* 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;
}
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;
}
}