package org.andork.j3d.camera;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
public class CameraPositionUtils
{
/**
* Finds the optimal camera position for a given view azimuth (with no tilt) that is as close to a group of points as possible without any being
* outside the field of view.
*
* @param vsAzimuth
* the vertical section azimuth
* @param fov
* the camera's field of view angle in radians
* @param aspectRatio
* the aspect ratio of the canvas (width / height)
* @param points
* the points to fit to screen
* @param result
* the {@link CameraPosition} to set
* @return {@code result} after setting it to the optimal position.
*/
public static CameraPosition fitToScreen( double vsAzimuth , double fov , double aspectRatio , Iterable<Point3d> points , CameraPosition result )
{
result.setPanTilt( vsAzimuth - Math.PI / 2 , 0 );
return fitToScreen( result , fov , aspectRatio , points , result );
}
/**
* Finds the optimal camera location for a given orientation (view direction) that is as close to a group of points as possible without any being
* outside the field of view.
*
* @param initOrientation
* the initial camera orientation to use. Only the right, down, and forward vectors will be used; the location will be ignored.
* @param vsAzimuth
* the vertical section azimuth
* @param fov
* the camera's field of view angle in radians
* @param aspectRatio
* the aspect ratio of the canvas (width / height)
* @param points
* the points to fit to screen
* @param result
* the {@link CameraPosition} to set
* @return {@code result} after setting it to the optimal position.
*/
public static CameraPosition fitToScreen( CameraPosition initOrientation , double fov , double aspectRatio , Iterable<Point3d> points , CameraPosition result )
{
Vector3d rightVector = new Vector3d( );
Vector3d forwardVector = new Vector3d( );
Vector3d downVector = new Vector3d( );
initOrientation.getRight( rightVector );
initOrientation.getForward( forwardVector );
initOrientation.getDown( downVector );
double fovratio = Math.tan( fov / 2 );
double rightRatio;
double downRatio;
rightRatio = fovratio;
downRatio = fovratio / aspectRatio;
Vector3d toPoint = new Vector3d( );
// compute the optimal "downing location" where all points fit optimally within the top/bottom field of view
// but not necessarily the left/right field of view.
Vector3d downingLoc = new Vector3d( );
int count = 0;
for( Point3d p : points )
{
if( count++ == 0 )
{
downingLoc.set( p );
}
else
{
double forwarding;
toPoint.sub( p , downingLoc );
double downing = downVector.dot( toPoint );
forwarding = forwardVector.dot( toPoint );
// is the point vertically within view?
if( forwarding <= 0 || Math.abs( downing / forwarding ) > downRatio )
{
//translate the camera backward and up/down along the view frustrum planes to where the point is just within view
double extraDowning = downRatio * forwarding;
double base = Math.abs( downing ) + extraDowning;
double height = base / 2 / downRatio;
double backUp = height - forwarding;
downingLoc.scaleAdd( -backUp , forwardVector , downingLoc );
downingLoc.scaleAdd( backUp * downRatio * Math.signum( downing ) , downVector , downingLoc );
}
}
}
// compute the optimal "righting location" where all points fit optimally within the left/right field of view
// but not necessarily the top/bottom field of view.
Vector3d rightingLoc = new Vector3d( );
count = 0;
for( Point3d p : points )
{
if( count++ == 0 )
{
rightingLoc.set( p );
}
else
{
toPoint.sub( p , rightingLoc );
double righting = rightVector.dot( toPoint );
double forwarding = forwardVector.dot( toPoint );
// is the point horizontally within view?
if( forwarding <= 0 || Math.abs( righting / forwarding ) > rightRatio )
{
//translate the camera backward and left/right along the view frustrum planes to where the point is just within view
double extraRighting = rightRatio * forwarding;
double base = Math.abs( righting ) + extraRighting;
double height = base / 2 / rightRatio;
double backUp = height - forwarding;
rightingLoc.scaleAdd( -backUp , forwardVector , rightingLoc );
rightingLoc.scaleAdd( backUp * rightRatio * Math.signum( righting ) , rightVector , rightingLoc );
}
}
}
// combine the two into one location where the points fit within the whole field of view
// use the righting location for left/right position, downing location for up/down position, and whichever is farther back for the
// forward/backward position
double righting = rightingLoc.dot( rightVector );
double downing = downingLoc.dot( downVector );
double forwarding = Math.min( rightingLoc.dot( forwardVector ) , downingLoc.dot( forwardVector ) );
Point3d loc = new Point3d( );
loc.scale( righting , rightVector );
loc.scaleAdd( downing , downVector , loc );
loc.scaleAdd( forwarding , forwardVector , loc );
// compute lookAt using forward vector
Point3d lookAt = new Point3d( );
lookAt.add( loc , forwardVector );
result.setLocation( loc );
result.lookAt( lookAt );
return result;
}
}