package org.vorthmann.zome.export.java2d;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.vecmath.Vector3f;
import org.vorthmann.ui.DefaultController;
/**
* Renders out to PDF, Postscript, or SVG.
*
* TODO extract model from controller, to support headless export.
*
* @author vorth
*/
public class Java2dSnapshot extends DefaultController
{
public Java2dSnapshot( Java2dExporter exporter )
{
this .mLineDrawing = false;
this .monochrome = true;
this .doLighting = true;
this .doOutlines = true;
this.setExporter( exporter );
}
private final static String[] DRAW_STYLES = new String[]{ "outlined shapes", "shaded shapes", "shaded, outlined shapes", "colored lines", "black lines" };
@Override
public String[] getCommandList( String listName )
{
if ( "draw.styles" .equals( listName ) )
{
return DRAW_STYLES;
}
else
return super .getCommandList( listName );
}
@Override
public String getProperty( String propName )
{
if ( "showBackground" .equals( propName ) )
return Boolean .toString( mShowBackground );
if ( "lineDrawing" .equals( propName ) )
return Boolean .toString( mLineDrawing );
if ( "monochrome" .equals( propName ) )
return Boolean .toString( monochrome );
if ( "drawStyle" .equals( propName ) )
{
if ( this .mLineDrawing )
if ( this .monochrome )
return "black lines";
else
return "colored lines";
else if ( this .doLighting )
if ( this .doOutlines )
return "shaded, outlined shapes";
else
return "shaded shapes";
else
return "outlined shapes";
}
return super.getProperty( propName );
}
@Override
public void actionPerformed( ActionEvent e )
{
String action = e .getActionCommand();
if ( action .equals( "refresh" ) )
{
current = false;
}
else if ( action .equals( "toggleBackground" ) )
{
current = false;
mShowBackground = !mShowBackground;
}
else if ( action .equals( "toggleLineDrawing" ) )
{
current = false;
mLineDrawing = !mLineDrawing;
}
else if ( action .equals( "toggleMonochrome" ) )
{
monochrome = !monochrome;
if ( mLineDrawing )
current = false;
}
else if ( action .startsWith( "setDrawStyle." ) )
{
String drawStyle = action .substring( "setDrawStyle." .length() );
if ( "black lines" .equals( drawStyle ) ) {
this .monochrome = true;
this .mLineDrawing = true;
}
else if ( "colored lines" .equals( drawStyle ) ) {
this .monochrome = false;
this .mLineDrawing = true;
}
else if ( "outlined shapes" .equals( drawStyle ) ) {
this .mLineDrawing = false;
this .doLighting = false;
this .doOutlines = true;
}
else if ( "shaded shapes" .equals( drawStyle ) ) {
this .mLineDrawing = false;
this .doLighting = true;
this .doOutlines = false;
}
else if ( "shaded, outlined shapes" .equals( drawStyle ) ) {
this .mLineDrawing = false;
this .doLighting = true;
this .doOutlines = true;
}
current = false;
}
else
super.actionPerformed( e );
}
// private void refresh()
// {
//
// mSnapshot .setBackgroundColor( new java.awt.Color( colors .getColor( Colors.BACKGROUND ) .getRGB() ) );
// }
@Override
public void doFileAction( String command, File file )
{
try {
SnapshotExporter snapshotExporter = null;
switch (command) {
case "export.2d.pdf":
snapshotExporter = new PDFExporter();
break;
case "export.2d.ps":
snapshotExporter = new PostScriptExporter();
break;
case "export.2d.svg":
snapshotExporter = new SVGExporter();
break;
default:
break;
}
if ( snapshotExporter == null )
return;
// A try-with-resources block closes the resource even if an exception occurs
try (Writer out = new FileWriter( file )) {
snapshotExporter .export( this, out );
}
openApplication( file );
} catch ( Exception e ) {
mErrors .reportError( UNKNOWN_ERROR_CODE, new Object[]{ e } );
}
}
@Override
public void repaintGraphics( String panelName, Graphics graphics, Dimension size )
{
if ( ! current )
try {
mPolygons .clear();
mLines .clear();
this .exporter .doExport( new File(""), null, size.height, size.width );
current = true;
} catch ( Exception e1 ) {
mErrors .reportError( UNKNOWN_ERROR_CODE, new Object[]{ e1 } );
}
Graphics2D g2d = (Graphics2D) graphics;
g2d .setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2d .clearRect( 0, 0, (int) size .getWidth(), (int) size .getHeight() );
if ( mShowBackground ) {
g2d .setPaint( this .getBackgroundColor() );
Rectangle2D rect = new Rectangle2D.Float();
rect .setFrame( new Point2D.Float(), size );
g2d .fill( rect );
}
if ( mLineDrawing ) {
g2d .setStroke( new BasicStroke( 3*mStrokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) );
g2d .setPaint( java.awt.Color.BLACK );
for (LineSegment line : mLines) {
if ( monochrome )
if ( mShowBackground )
g2d .setPaint( java.awt.Color.WHITE );
else
g2d .setPaint( java.awt.Color.BLACK );
else
g2d .setPaint( line .getColor() );
g2d .draw( line .getPath() );
}
}
else {
g2d .setStroke( new BasicStroke( mStrokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) );
for ( Java2dSnapshot.Polygon poly : mPolygons ){
g2d .setPaint( poly .getColor() );
g2d .fill( poly .getPath() );
if ( this .doOutlines ) {
g2d .setPaint( java.awt.Color.BLACK );
g2d .draw( poly .getPath() );
}
}
}
g2d.dispose(); //clean up
}
private Java2dExporter exporter;
private boolean mShowBackground, mLineDrawing, monochrome, doLighting, doOutlines;
private boolean current;
private List<Polygon> mPolygons = new ArrayList<>();
private List<LineSegment> mLines = new ArrayList<>();
private Rectangle2D mRect;
private float mStrokeWidth;
public boolean isLineDrawing()
{
return mLineDrawing;
}
public boolean isMonochrome()
{
return monochrome;
}
public boolean hasLighting()
{
return doLighting;
}
public void addPolygon( Polygon polygon )
{
mPolygons .add( polygon );
}
public void addLineSegment( Color color, Vector3f start, Vector3f end )
{
mLines .add( new LineSegment( color, start, end ) );
}
public void depthSort()
{
if ( mLineDrawing )
Collections .sort( mLines );
// TODO eliminate duplicates
else
Collections .sort( mPolygons );
}
public void setRect( Rectangle2D rect )
{
mRect = rect;
}
public void setStrokeWidth( float strokeWidth )
{
mStrokeWidth = strokeWidth;
}
public Rectangle2D getRect()
{
return mRect;
}
public float getStrokeWidth()
{
if ( this .mLineDrawing || this .doOutlines )
return mStrokeWidth;
else
return -1f;
}
public Dimension getDimension()
{
return new Dimension( (int) mRect .getWidth(), (int) mRect .getHeight() ) ;
}
public static class LineSegment implements Comparable<LineSegment>
{
private final GeneralPath mPath;
private float mDepth;
private final Color mPolyColor;
public GeneralPath getPath()
{
return mPath;
}
public LineSegment( Color color, Vector3f start, Vector3f end )
{
mPolyColor = color;
mPath = new GeneralPath();
mPath .moveTo( start.x, start.y );
mPath .lineTo( end.x, end.y );
mDepth = ( start.z + end.z ) / 2.0f;
}
public Color getColor()
{
return mPolyColor;
}
@Override
public int compareTo( LineSegment other )
{
double otherZ = other .mDepth;
if ( mDepth > otherZ )
return 1;
if ( mDepth < otherZ )
return -1;
return 0;
}
}
public static class Polygon implements Comparable<Polygon>
{
private final GeneralPath mPath;
private float mDepth;
private int mSize = 0;
private Color mPolyColor;
public GeneralPath getPath()
{
return mPath;
}
public int size()
{
return mSize;
}
public void addVertex( Vector3f vertex )
{
++ mSize;
if ( mSize == 1 ) {
mPath .moveTo( vertex.x, vertex.y );
mDepth = vertex.z;
}
else {
mPath .lineTo( vertex.x, vertex.y );
mDepth += vertex.z;
}
}
public void close()
{
mDepth /= mSize;
mPath .closePath();
}
public Polygon( Color color )
{
mPolyColor = color;
mPath = new GeneralPath();
}
public Color getColor()
{
return mPolyColor;
}
@Override
public int compareTo( Polygon other )
{
double otherZ = other .mDepth;
if ( mDepth > otherZ )
return 1;
if ( mDepth < otherZ )
return -1;
return 0;
}
public void applyLighting( Vector3f normal, Vector3f[] lightDirs, Color[] lightColors, Color ambient )
{
float redIntensity = ambient .getRed() / 255f;
float greenIntensity = ambient .getGreen() / 255f;
float blueIntensity = ambient .getBlue() / 255f;
for ( int i = 0; i < lightColors.length; i++ ) {
float intensity = Math .max( normal .dot( lightDirs[ i ] ), 0f );
redIntensity += intensity * ( lightColors[ i ].getRed() / 255f );
greenIntensity += intensity * ( lightColors[ i ].getGreen() / 255f );
blueIntensity += intensity * ( lightColors[ i ].getBlue() / 255f );
}
int red = (int) ( mPolyColor.getRed() * Math.min( redIntensity, 1f ) );
int green = (int) ( mPolyColor.getGreen() * Math.min( greenIntensity, 1f ) );
int blue = (int) ( mPolyColor.getBlue() * Math.min( blueIntensity, 1f ) );
mPolyColor = new Color( red, green, blue );
}
}
public Iterator<LineSegment> getLines()
{
return mLines .iterator();
}
public Iterator<Polygon> getPolygons()
{
return mPolygons .iterator();
}
public void setExporter( Java2dExporter exporter )
{
current = false;
this.exporter = exporter;
exporter .setSnapshot( this );
}
public Color getBackgroundColor()
{
if ( this .mShowBackground )
return this .exporter .getBackgroundColor();
else
return null;
}
}