/*============================================================================= | | NAME | | PrenticeModel | | DESCRIPTION | | Generates the model of the reality. | | 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. | +============================================================================*/ /** * Model of reality. */ package Model ; // Package name for this project. import java.awt.* ; // AWT basic window handling. import java.awt.geom.* ; // 2D graphics. import java.awt.image.* ; // Buffered images and operations. import java.io.* ; // File I/O. import java.util.* ; // Observer. import javax.swing.* ; // Swing main GUI package. import javax.swing.event.* ; // Swing GUI event handling. import javax.imageio.* ; // ImageIO. import Prentice.* ; // Prentice stuff. /** * The model has the properties of a Swing worker thread, so as to keep the * GUI snappy. */ public class PrenticeModel extends SwingWorker { /** * Link back to the top level application object. */ Prentice app ; /* * Kludge: won't recognize PerspectiveMatrix class at compilation unless * parent class is declared. */ TransformMatrix dummy ; int imageWidth ; int imageHeight ; double picturePlaneWidth = inchesToMeters( 24.0 ) ; double picturePlaneHeight = inchesToMeters( 36.0 ) ; double eyeToPicturePlaneDistance = inchesToMeters( 10.0 ) ; double farPlaneDistance = 1.0e10 ; // Effectively infinite. double nearPlaneDistance = inchesToMeters( -10.0 ) ; PerspectiveMatrix perspectiveTransform ; PicturePlaneToScreenTransform picturePlaneToScreenTransform ; ClipView clippingTransform ; GeneralPath wireFrame ; public void eyeToPicturePlane( double eyeToPP ) { eyeToPicturePlaneDistance = inchesToMeters( eyeToPP ) ; } /** * Update the model. Starts a worker thread going which calls execute() * which runs the code in doInBackground() */ public void update() { DebugLog.println( "Model update..."); execute() ; } /** * All model computations happen here in a background thread. * Initiated by model.execute(). */ public GeneralPath doInBackground() { // Generate the wire frame model. cameraView() ; generateWireFrameModel() ; return getWireFrame() ; } /** * Thread calls this when doInBackground() completes. */ public void done() { try { app.getCanvas().update() ; // Notify the canvas that we're done. } catch (Exception ignore) { DebugLog.println( "Model thread had an exception."); } } /** * * @return */ public GeneralPath getWireFrame() { return this.wireFrame ; } /** * Helper function to convert inches to meters. * @param inches * @return */ private double inchesToMeters( double inches ) { return inches * 2.54 / 100.0 ; } public PrenticeModel() { // Does nothing. } /** * * We use a left handed picture plane coordinate system. * y * ^ * | * | z * | / * | / * |/ * x <--------+ * * Kludge: won't recognize PerspectiveMatrix class at compilation unless * parent class is declared. */ public void cameraView() { DebugLog.println( "\n" + "pp width = " + picturePlaneWidth + "\n" + "pp height = " + picturePlaneWidth + "\n" + "eye to pp = " + eyeToPicturePlaneDistance + "\n" + "far plane = " + farPlaneDistance + "\n" + "near plane = " + nearPlaneDistance + "\n" ) ; // P = perspective matrix for the view. perspectiveTransform = new PerspectiveMatrix( eyeToPicturePlaneDistance, picturePlaneWidth, picturePlaneHeight, farPlaneDistance, nearPlaneDistance ) ; DebugLog.println( "Perspective transform for the view = " ) ; perspectiveTransform.print() ; picturePlaneToScreenTransform = new PicturePlaneToScreenTransform( imageWidth, imageHeight, picturePlaneWidth, picturePlaneHeight ) ; // Clipping view. ClipView clipView = new ClipView( eyeToPicturePlaneDistance, picturePlaneWidth, picturePlaneHeight, farPlaneDistance, nearPlaneDistance ) ; this.perspectiveTransform = perspectiveTransform ; this.picturePlaneToScreenTransform = picturePlaneToScreenTransform ; this.clippingTransform = clipView ; } /** * * Initialize the model. */ public PrenticeModel( Prentice app, int imageWidth, int imageHeight ) { this.app = app ; this.imageWidth = imageWidth ; this.imageHeight = imageHeight ; cameraView() ; // Create a polyline object with initial capacity. this.wireFrame = new GeneralPath( GeneralPath.WIND_EVEN_ODD, 1000 // Arbitrary. ) ; } /** * Render a wire frame model. */ public void generateWireFrameModel() { DebugLog.println( "\n" + "PrenticeModel render." ) ; double x = 0.0 ; double y = 0.0 ; double z = 0.0 ; int [] screenPoint = null ; double normalizedPicturePlaneX ; double normalizedPicturePlaneY ; double [] proj = null ; boolean firstPoint = true ; // Orbital base and walls wireframe graphics. for (int circleNum = 1 ; circleNum <= 7 ; // ++circleNum) { // Translate the circle center up by the radius so that the circle's // lowest point touches the origin of the world coordinate system. // Then translate the center down by 20km, so we are above its lowest // point by that much. double radius = 1.50e6 * 1000.0 ; // 1.5 mega km radius. double offsetX = 0.0 ; double offsetY = radius -20.0 * 1000.0 ; double offsetZ = 0.0 ; if (circleNum == 1) offsetX = 0.0 ; // Circle in the middle. else if (circleNum == 2) offsetX = 500.0 * 1000.0 ; // Orbital ringwall base 500 km left of eye. else if (circleNum == 3) offsetX = -500.0 * 1000.0 ; // Orbital ringwall base 500 km right of eye. else if (circleNum == 4) offsetX = 100.0 * 1000.0 ; // Orbital ringwall base 100 km left of eye. else if (circleNum == 5) offsetX = -100.0 * 1000.0 ; // Orbital ringwall base 100 km left of eye. else if (circleNum == 6) { radius = (1.50e6 - 500.0) * 1000.0 ; // Orbital top wall 500 km up offsetX = 500.0 * 1000.0 ; // and 500km left. } else if (circleNum == 7) { radius = (1.50e6 - 500.0) * 1000.0 ; // Orbital top wall 500 km up offsetX = -500.0 * 1000.0 ; // and 500km right. } // Generate the circle's points. double startingAngle = -90.0 ; // Start generating circle at bottom of view. double endingAngle = 30.0 ; // End at the top. double angleInc = 0.1 ; // (fine enough?) firstPoint = true ; for (double angle = startingAngle ; angle < endingAngle ; angle += angleInc) { // Increase graphics sampling near bottom of orbital. if (-90.0 <= angle && angle < -89.9) angleInc = 0.0001 ; else if (-89.9 <= angle && angle < -89.0) angleInc = 0.001 ; else angleInc = 0.1 ; // Convert angle from degrees to radians. double angle2 = angle * Math.PI / 180.0 ; // Generate a circle parallel to yz plane. x = offsetX ; y = radius * Math.sin( angle2 ) + offsetY ; z = radius * Math.cos( angle2 ) + offsetZ ; DebugLog.println( "\n\nNext point in orbital circle " + circleNum ) ; DebugLog.println( "angle (degrees) = " + angle ) ; DebugLog.println( "World Coord (x y z) = ( " + x + " " + y + " " + z + " )" ) ; // Load a vector. TransformVector v = new TransformVector() ; v.setElement( 0, x ) ; v.setElement( 1, y ) ; v.setElement( 2, z ) ; v.setElement( 3, 1.0 ) ; // w-axis, w = 1. DebugLog.print( "Homogeneous world coordinates of object point (x y z w) = " ) ; v.print() ; DebugLog.println() ; // Clip against viewing pyramid. boolean clip = clippingTransform.clip( v ) ; //clip = false ; if (!clip) { // Perpective projection. v = perspectiveTransform.multiply( v ) ; DebugLog.print( "Homogeneous perspective projection (x y z w) = " ) ; v.print() ; DebugLog.println() ; // Map to picture plane: convert to 3D and drop the z coordinate. proj = v.to3D() ; DebugLog.println( "Perspective projection (x y z) = ( " + proj[ 0 ] + " " + proj[ 1 ] + " " + proj[ 2 ] + " )" ) ; screenPoint = picturePlaneToScreenTransform.map( proj ) ; DebugLog.println( "Screen ( x y ) = ( " + screenPoint[0] + " " + screenPoint[1] + " )" ) ; // First point is a move. if (firstPoint) { this.wireFrame.moveTo( screenPoint[0], screenPoint[1] ) ; firstPoint = false ; } else { this.wireFrame.lineTo( screenPoint[0], screenPoint[1]) ; } } // end clip } // end for angle } // end circleNum // ----------------------< Graphics for Forest >------------------------- firstPoint = true ; // Orbital endless forest graphics. for (int circleNum = 1 ; circleNum <= 2 ; ++circleNum) { // Translate the circle center up by the radius so that the circle's // lowest point touches the origin of the world coordinate system. // Then translate the center down by 20km, so we are above its lowest // point by that much. double baseRadius = 1.50e6 * 1000.0 ; // 1.5 mega km radius. double offsetX = 0.0 ; double offsetY = baseRadius -20.0 * 1000.0 ; double offsetZ = 0.0 ; if (circleNum == 1) offsetX = 500.0 * 1000.0 ; // 500 km left of eye. else if (circleNum == 2) offsetX = -500.0 * 1000.0 ; // 500 km left of eye. // Generate the points. double startingAngle = -90.0 * (Math.PI / 180.0) ; // Start generating circle at bottom of view. double endingAngle = -85.0 * (Math.PI / 180.0) ; // // Angle increment in radians. // Tree spacing is 500 km. double angleInc = 500.0 * 1000.0 / baseRadius ; for (double angle = startingAngle ; angle < endingAngle ; angle += angleInc) { for (int tree = 1 ; tree <= 2 ; // ++tree) { double radius = 0.0 ; if (tree == 1) { radius = baseRadius ; } // Trees are 100 km high. else if (tree == 2) { radius = baseRadius - 100.0 * 1000.0 ; } // Generate a circle parallel to yz plane. x = offsetX ; y = radius * Math.sin( angle ) + offsetY ; z = radius * Math.cos( angle ) + offsetZ ; // Load a vector. TransformVector v = new TransformVector() ; v.setElement( 0, x ) ; v.setElement( 1, y ) ; v.setElement( 2, z ) ; v.setElement( 3, 1.0 ) ; // w-axis, w = 1. // Clip against viewing pyramid. boolean clip = clippingTransform.clip( v ) ; //clip = false ; if (!clip) { // Perpective projection. v = perspectiveTransform.multiply( v ) ; // Map to picture plane: convert to 3D and drop the z coordinate. proj = v.to3D() ; screenPoint = picturePlaneToScreenTransform.map( proj ) ; // First point is a move. if (tree == 1) { this.wireFrame.moveTo( screenPoint[0], screenPoint[1] ) ; firstPoint = false ; } else if (tree == 2) { this.wireFrame.lineTo( screenPoint[0], screenPoint[1]) ; } } // end clip } // end tree } // end for angle } // end circleNum //this.polyline.closePath() ; } // end render } // end PrenticeModel