/*=============================================================================
|
| NAME
|
| PrenticeModel
|
| DESCRIPTION
|
| Generates the model of the reality.
|
| LEGAL
|
| Prentice Version 1.1 - An Artist's Software Apprentice.
| Copyright (C) 2003-2009 by Sean Erik O'Connor. All Rights Reserved.
|
| Primpoly Version 10.4 - A Program for Computing Primitive Polynomials.
| Copyright (C) 1999-2009 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, either version 3 of the License, or
| (at your option) any later version.
|
| 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, see .
|
| The author's address is artificer!AT!seanerikoconnor!DOT!freeservers!DOT!com
| with !DOT! replaced by . and the !AT! replaced by @
|
+============================================================================*/
/**
* 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