/*============================================================================= | | NAME | | TransformMatrix.java | | DESCRIPTION | | Set of 4 x 4 matrices for 3D homogeneous coordinate transforms, | including rotation, translation, scaling and perspective. | | PUBLIC MEMBER FUNCTIONS | | TransformMatrix( ) Creates identity transform. | TransformMatrix( m ) Copies the matrix m. | get() Returns the entire matrix. | getElement(i, j) Returns element in row i, column j. | setElement(i, j) Returns element in row i, column j. | multiply( m1, m2 ) Returns m1 * m2. | multiply( v ) Return m * v. | print() Debug prints the matrix. | | EXCEPTIONS | | Description | | NOTES | | Algorithm_References_Tricks | | BUGS | | Bugs_Enhancements | | AUTHOR | | Sean E. O'Connor | | LEGAL | | Prentice Version 1.1 - An Artist's Software Apprentice. | Copyright (C) 2003-2008 by Sean Erik O'Connor. All Rights Reserved. | | This program is free software; you can redistribute it and/or | modify it under the terms of the GNU General Public License | as published by the Free Software Foundation; version 2 | of the License. | | This program 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 for more details. | | You should have received a copy of the GNU General Public License | along with this program; if not, write to the Free Software | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | | The author's address is artifex@seanerikoconnor.freeservers.com. | +============================================================================*/ package Model ; // Package name for this project. import java.text.* ; // DecimalFormat import Prentice.* ; // Prentice stuff. import Model.* ; /** * @see theory * @author seanoconnor */ class TransformMatrix implements PrenticeConstants { // 4 x 4 matrix holding the homogeneous transform coordinates. // Let it be accessed by subclasses for convenience. protected double[][] matrix_ ; // // Default constructor creates zero transform matrix. // public TransformMatrix() { // Allocate the whole matrix. matrix_ = new double[ TRANSFORM_MATRIX_SIZE ][ TRANSFORM_MATRIX_SIZE ] ; // Zero it out. for (int row = 0 ; row < TRANSFORM_MATRIX_SIZE ; ++row) { for (int col = 0 ; col < TRANSFORM_MATRIX_SIZE ; ++col) { matrix_[ row ][ col ] = 0 ; } } } // // Default constructor which creates a copy of the matrix m. // public TransformMatrix( TransformMatrix m ) { // Allocate a new matrix. matrix_ = new double[ TRANSFORM_MATRIX_SIZE ][ TRANSFORM_MATRIX_SIZE ] ; // Copy its elements. for (int row = 0 ; row < TRANSFORM_MATRIX_SIZE ; ++row) { for (int col = 0 ; col < TRANSFORM_MATRIX_SIZE ; ++col) { matrix_[ row ][ col ] = m.get()[ row ][ col ] ; } } } // // Return the entire matrix. // public double[][] get() { return matrix_ ; } // // Return m element of the matrix. // ij public double getElement( int row, int col ) { return matrix_[ row ][ col ] ; } // // Set m element of the matrix. // ij public void setElement( int row, int col, double element ) { matrix_[ row ][ col ] = element ; } // // Matrix multiply. // public TransformMatrix multiply( TransformMatrix m1, TransformMatrix m2 ) { TransformMatrix productMatrix = new TransformMatrix() ; // Multiply out. for (int i = 0 ; i < TRANSFORM_MATRIX_SIZE ; ++i) { for (int k = 0 ; k < TRANSFORM_MATRIX_SIZE ; ++k) { double sum = 0.0 ; for (int j = 0 ; j < TRANSFORM_MATRIX_SIZE ; ++j) { sum += (m1.get()[ i ][ j ] * m2.get()[ j ][ k ]) ; } productMatrix.matrix_[ i ][ k ] = sum ; } } return productMatrix ; } // // Matrix vector multiply. // public TransformVector multiply( TransformVector v ) { TransformVector productVector = new TransformVector() ; // Multiply out. for (int row = 0 ; row < TRANSFORM_MATRIX_SIZE ; ++row) { double sum = 0.0 ; for (int col = 0 ; col < TRANSFORM_MATRIX_SIZE ; ++col) { sum += matrix_[ row ][ col ] * v.get()[ col ] ; } productVector.get()[ row ] = sum ; } return productVector ; } // // Print out the matrix. // public void print() { for (int row = 0 ; row < TRANSFORM_MATRIX_SIZE ; ++row) { DebugLog.print( "( " ) ; for (int col = 0 ; col < TRANSFORM_MATRIX_SIZE ; ++col) { DecimalFormat floatFormat = new DecimalFormat( " 0.000E000;-0.000E000" ) ; String num = floatFormat.format( matrix_[ row ][ col ] ) ; DebugLog.print( num + " " ) ; } } } } /*============================================================================= | | NAME | | PerspectiveMatrix | | DESCRIPTION | | Invertible perspective transformation. | | PUBLIC MEMBER FUNCTIONS | | PerspectiveMatrix() | PerspectiveMatrix( double eyeToProjectionPlaneDistance ) | | EXCEPTIONS | | Description | | NOTES | | BUGS | | Bugs_Enhancements | | AUTHOR | | Sean E. O'Connor | +============================================================================*/ class PerspectiveMatrix extends TransformMatrix { // // Default projection in which d = 1 unit. // public PerspectiveMatrix() { // Construct zero transform matrix. super() ; // 1's along diagonal. for (int row = 0 ; row < TRANSFORM_MATRIX_SIZE ; ++row) { matrix_[ row ][ row ] = 1.0 ; } // Distance from eye to projection plane = 1. matrix_[ 3 ][ 2 ] = 1.0 ; } // // Use all information about the perspective. // public PerspectiveMatrix( double eyeToProjectionPlaneDistance, double picturePlaneWidth, double picturePlaneHeight, double FarPlaneDistance, double NearPlaneDistance ) { // Construct zero transform matrix. super() ; // 1's along diagonal. for (int row = 0 ; row < TRANSFORM_MATRIX_SIZE ; ++row) { matrix_[ row ][ row ] = 1.0 ; } // TODO handle divide by zero. matrix_[ 3 ][ 2 ] = 1.0 / eyeToProjectionPlaneDistance ; } } /*============================================================================= | | NAME | | NameOfClass | | DESCRIPTION | | WhatItDoes | | PUBLIC MEMBER FUNCTIONS | | DefaultConstructor WhatItDoesBriefly | ConstructorWithArguments WhatItDoesBriefly | MemberFunction1 WhatItDoesBriefly | MemberFunction2 WhatItDoesBriefly | MemberFunction3 WhatItDoesBriefly | | EXCEPTIONS | | Description | | NOTES | | Algorithm_References_Tricks | | BUGS | | Bugs_Enhancements | | AUTHOR | | Sean E. O'Connor | +============================================================================*/ class RotationMatrix extends TransformMatrix { public RotationMatrix() { super() ; } public RotationMatrix( double angle ) { super() ; } } /*============================================================================= | | NAME | | TransformVector | | DESCRIPTION | | WhatItDoes | | PUBLIC MEMBER FUNCTIONS | | TransformVector | setElement | print | | EXCEPTIONS | | Description | | NOTES | | Algorithm_References_Tricks | | BUGS | | Bugs_Enhancements | | AUTHOR | | Sean E. O'Connor | +============================================================================*/ class TransformVector implements PrenticeConstants { // 4 vector holding the homogeneous transform coordinates. // Let it be accessed by subclasses for convenience. protected double[] vector_ ; public TransformVector() { // Allocate the whole matrix. vector_ = new double[ TRANSFORM_MATRIX_SIZE ] ; // Zero it out. for (int col = 0 ; col < TRANSFORM_MATRIX_SIZE ; ++col) { vector_[ col ] = 0 ; } } // Set ith element of the matrix. public void setElement( int col, double element ) { vector_[ col ] = element ; } // Return a Java array. public double[] get() { return vector_ ; } // Convert from 4D homogeneous coordinates to 3D coordinates. public double[] to3D() { double[] vec3D = new double[ TRANSFORM_MATRIX_SIZE - 1 ] ; vec3D[ 0 ] = vector_[ 0 ] / vector_[ 3 ] ; vec3D[ 1 ] = vector_[ 1 ] / vector_[ 3 ] ; vec3D[ 2 ] = vector_[ 2 ] / vector_[ 3 ] ; return vec3D ; } // Print out the vector. public void print() { DebugLog.print( "( " ) ; for (int col = 0 ; col < TRANSFORM_MATRIX_SIZE ; ++col) { DecimalFormat floatFormat = new DecimalFormat( " 0.000E000;-0.000E000" ) ; String num = floatFormat.format( vector_[ col ] ) ; DebugLog.print( num + " " ) ; } } } /*============================================================================= | | NAME | | ClipView | | DESCRIPTION | | WhatItDoes | | PUBLIC MEMBER FUNCTIONS | | DefaultConstructor WhatItDoesBriefly | ConstructorWithArguments WhatItDoesBriefly | MemberFunction1 WhatItDoesBriefly | MemberFunction2 WhatItDoesBriefly | MemberFunction3 WhatItDoesBriefly | | EXCEPTIONS | | Description | | NOTES | | Algorithm_References_Tricks | | BUGS | | Bugs_Enhancements | | AUTHOR | | Sean E. O'Connor | +============================================================================*/ class ClipView implements PrenticeConstants { private double picturePlaneWidth_ ; private double picturePlaneHeight_ ; private double eyeToProjectionPlaneDistance_ ; private double farPlaneDistance_ ; private double nearPlaneDistance_ ; // Create the clipping object. public ClipView( double eyeToProjectionPlaneDistance, double picturePlaneWidth, double picturePlaneHeight, double farPlaneDistance, double nearPlaneDistance ) { picturePlaneWidth_ = picturePlaneWidth ; picturePlaneHeight_ = picturePlaneHeight ; eyeToProjectionPlaneDistance_ = eyeToProjectionPlaneDistance ; farPlaneDistance_ = farPlaneDistance ; nearPlaneDistance_ = nearPlaneDistance ; } // Clip in homogeneous coordinates. boolean clip( TransformVector tv ) { double[] v = tv.get() ; double x = tv.get()[ 0 ] ; double y = tv.get()[ 1 ] ; double z = tv.get()[ 2 ] ; double w = tv.get()[ 3 ] ; double t1 = w * eyeToProjectionPlaneDistance_ * picturePlaneHeight_ ; double t2 = 2 * eyeToProjectionPlaneDistance_ * y ; double t3 = picturePlaneHeight_ * z ; double s1 = w * eyeToProjectionPlaneDistance_ * picturePlaneWidth_ ; double s2 = 2 * eyeToProjectionPlaneDistance_ * x ; double s3 = picturePlaneWidth_ * z ; DebugLog.println( "-t1 + t2 - t3 = " + (-t1 + t2 - t3) ) ; DebugLog.println( "-t1 - t2 - t3 = " + (-t1 - t2 - t3) ) ; // Initially within the viewing pyramid. boolean clipping = false ; // Clip when higher than the top plane of the viewing pyramid. if (-t1 + t2 - t3 > 0.0) { DebugLog.println( "Clipping against top plane" ) ; clipping = true ; } // Clip when lower than the bottom plane of the viewing pyramid. else if (-t1 -t2 -t3 > 0.0) { DebugLog.println( "Clipping against bottom plane" ) ; clipping = true ; } // Clip when right of the right plane of the viewing pyramid. else if (-s1 -s2 -s3 > 0.0) { DebugLog.println( "Clipping against right plane" ) ; clipping = true ; } // Clip when left of the left plane of the viewing pyramid. else if (-s1 +s2 -s3 > 0.0) { DebugLog.println( "Clipping against left plane" ) ; clipping = true ; } // Clip when beyond far plane. else if (z > w * farPlaneDistance_) { DebugLog.println( "Clipping against far plane" ) ; clipping = true ; } // Clip when nearer than near plane. else if (z < w * nearPlaneDistance_) { DebugLog.println( "Clipping against near plane" ) ; clipping = true ; } // Change the sense for w < 0 if (w > 0) return clipping ; else { DebugLog.println( "w < 0: Reversing clip sense." ) ; if (clipping == true) clipping = false ; else if (clipping == true) clipping = true ; } return clipping ; } } // Map a point on the picture plane to the screen, keeping aspect ratio constant. class PicturePlaneToScreenTransform { private int screenWidth_ ; private int screenHeight_ ; private double picturePlaneWidth_ ; private double picturePlaneHeight_ ; public PicturePlaneToScreenTransform( int screenWidth, int screenHeight, double picturePlaneWidth, double picturePlaneHeight ) { // Save constructor arguments into object. screenWidth_ = screenWidth ; screenHeight_ = screenHeight ; picturePlaneWidth_ = picturePlaneWidth ; picturePlaneHeight_ = picturePlaneHeight ; } /** * * @param picturePlanePoint * @return */ int [] map( double [] picturePlanePoint ) { int [] screenPoint = new int[ 2 ] ; // Compute aspect ratios for picture plane and screen. double picturePlaneAspectRatio = picturePlaneWidth_ / picturePlaneHeight_ ; double screenAspectRatio = (double) screenWidth_ / (double) screenHeight_ ; // Picture plane is taller and narrower than the display screen. // Keep the picture plane aspect ratio invariant such that // picturePlaneAspectRatio = / . double scaledScreenWidth = 0.0 ; double scaledScreenHeight = 0.0 ; if (picturePlaneAspectRatio < screenAspectRatio) { scaledScreenWidth = (int)(0.5 + screenHeight_ * picturePlaneAspectRatio) ; scaledScreenHeight = screenHeight_ ; } // picturePlaneAspectRatio = / . else { scaledScreenWidth = screenWidth_ ; scaledScreenHeight = (int)(0.5 + screenWidth_ / picturePlaneAspectRatio) ; } // Picture plane x axis is lefthanded, so invert first. // Rescale picture plane width to screen width. // Picture plane origin is at screen center, so translate. double normalizedPicturePlaneX = (-picturePlanePoint[ 0 ] / picturePlaneWidth_) * (double) scaledScreenWidth ; screenPoint[ 0 ] = (int)(0.5 + normalizedPicturePlaneX + (double) scaledScreenWidth / 2.0) ; // Same idea, but screen y goes downwards. double normalizedPicturePlaneY = (picturePlanePoint[ 1 ] / picturePlaneHeight_) * (double) scaledScreenHeight ; screenPoint[ 1 ] = (int)(0.5 - normalizedPicturePlaneY + (double) scaledScreenHeight / 2.0) ; return screenPoint ; } }