1.00 lecture 18 geometric transformations in the 2d api

34
1.00 Lecture 18 Geometric Transformations in the 2D API

Upload: ursula-shelton

Post on 29-Dec-2015

218 views

Category:

Documents


1 download

TRANSCRIPT

1.00 Lecture 18

Geometric Transformations

in the 2D API

Transformed Coordinatesin NgonApp

Pixel Coordinatesin NgonApp

static final float SCALE=200.0F;

static final float tx = 1.5F;

static final float ty = 1.5F;

float transX( float x ) {

return ( x + tx ) * SCALE;

}

float transY( float y ) {

return ( y + ty ) * SCALE;

}

Transformations in theCardioid Grapher

Affine Transformations

• The 2D API provides strong support for affine transformations.

• An affine transformation maps 2D coordinates so that the straightness and parallelism of lines are preserved.

• All affine transformations can be represented by a 3x3 floating point matrix.

• There are a number of “primitive” affine transformations that can be combined.

Scaling

Sx 0 0 x Sx *x 0 Sy 0 y = Sy *y 0 0 1 1 1

Scaling Notes

• Basic scaling operations take place with respect to the origin. If the shape is at the origin, it grows. If it is anywhere else, it grows and moves.

• sx, scaling along the X dimension, does not have to equal sy , scaling along the y.

• For instance, to flip a figure vertically about the x-axi

s, scale by sx=1, sy=-1

Reflection as Scaling

1 0 0 x x

0 -1 0 y = -y

0 0 1 1 1

Translation

100

10

01

ty

tx

1

y

x

1

tyy

txx

=

Rotation

100

0)cos()sin(

0)sin()cos(

1

y

x

=

1

)cos()sin(

)sin()cos(

yx

yx

Composing Transformations

• Suppose we want to scale point (x, y) by 2 and then rotate by 90 degrees.

rotate scale

1100

020

002

100

001

010

y

x

Composing Transformations, 2

• Because matrix multiplication is associative, we can rewrite this as

1100

002

020

1100

020

002

100

001

010

y

x

y

x

Composing Transformations, 3

• Because matrix multiplication does not regularly commute, the order of transformations matters. This squares with our geometric intuition.

• If we invert the matrix, we reverse the transformation.

Transformations and the Origin

• When we transform a shape, we transform each of the defining points of the shape, and then redraw it.

• If we scale or rotate a shape that is not anchored at the origin, it will translate as well.

• If we just want to scale or rotate, then we should translate back to the origin, scale or rotate, and then translate back.

Transformations and the Origin, 2

Transformations in the 2D API

• Transformations are represented by instances of the AffineTransform class in the java.awt.geom package.

• Build a compound transform by1. Creating a new instance of AffineTransform

2. Calling methods to build a stack of basic transforms:

last in, first applied:– translate(double tx, double ty)

– scale(double sx, double sy)

– rotate(double theta)

– rotate(double theta, double x, double y) rotates about (x,y)

Transformation Example

baseXf = new AffineTransform();

baseXf.scale( scale, -scale );

baseXf.translate( -uRect.x, -uRect.y );

If we now apply baseXF it will translate first, then scale. Remember in Java® that transforms are built up like a stack, last in, first applied.

Back to Cardioid

Let’s build our coordinate system:

public class Cardioid extends JFrame { private CardioidGraph graph; private Rectangle2D.Float uSpace;

public static void main( String [] args ) { Rectangle2D.Float uS =

new Rectangle2D.Float(-1.5F,1.5F,4.0F,3.0F);Cardioid card = new Cardioid( uS );card.setSize( 640, 480 );card.setVisible( true );

}

Cardioid Cartesian Space

Rectangle2D.Float(-1.5F,1.5F,4.0F,3.0F) represents the area of Cartesian coordinates in which we will draw our graph:

CardioidGraph

public Cardioid( Rectangle2D.Float uS ) { uSpace = uS; graph = new CardioidGraph( uSpace ); . . .

public class CardioidGraph extends GraphPanel implements ActionListener {

public CardioidGraph( Rectangle2D.Float uR ) { super( uR ); . . .

GraphPanel

public class GraphPanel extends JPanel {

protected Rectangle2D.Float uRect;

protected AffineTransform baseXf = null;

protected Dimension curDim = null;

protected double scale;

private GeneralPath axes = null;

public GraphPanel( Rectangle2D.Float uR ) {

uRect = (Rectangle2D.Float) uR.clone();

}

GraphPanel, paintComponent()

public void paintComponent( Graphics g ) {

super.paintComponent( g );

Graphics2D g2 = (Graphics2D) g;

if ( ! getSize().equals( curDim ) )

doResize();

drawAxes( g2 );

drawGrid( g2 );

}

GraphPanel, doResize()

private void doResize() { curDim = getSize(); hScale = curDim.width / uRect.width; vScale = curDim.height / uRect.height; scale = Math.min( hScale, vScale ); baseXf = new AffineTransform(); baseXf.scale( scale, -scale ); baseXf.translate( -uRect.x, -uRect.y );

axes = createAxes(); grid = createGrid();}

Creating and Drawing the Axes

private GeneralPath createAxes() { GeneralPath path = new GeneralPath(); path.moveTo( uRect.x, 0F ); path.lineTo( uRect.x + uRect.width, 0F ); path.moveTo( 0F, uRect.y ); path.lineTo( 0F, uRect.y - uRect.height ); return path;}private void drawAxes( Graphics2D g2 ) { g2.setPaint( Color.green ); g2.setStroke( new BasicStroke(3 ) ); g2.draw(baseXf.createTransformedShape(axes));}

Initializing CardioidGraph

Initializing CardioidGraph,2public CardioidGraph( Rectangle2D.Float uR ) { super( uR ); diam = uRect.width / 4; hub = new Ellipse2D.Float(0F, -diam/2, diam, diam); tick = uRect.width / 40; wheel = new GeneralPath(); Shape s = new Ellipse2D.Double(diam, -diam/2,diam, diam);

wheel.append( s, false ); wheel.moveTo( 2*diam, 0F ); wheel.lineTo( 2*diam + tick, 0F );}

Timers• Swing provides a utility class called Timer that makes it simpler to buil

d animations.• Timers tick at an interval you can set, and the ticks are reported as Act

ionEvents.

public class TimerUser implements ActionListener { private Timer timer = null; private int tIval = 100; // interval in milliseconds

public TimerUser(){ timer = new Timer( tIval, this ); }

public void start(){ timer.start(); }

public void actionPerformed( ActionEvent e ){ /*do repeated action on every tick*/ }

Timer Methods

Timer( int tickMillis, ActionListener l )

void start()

void stop()

boolean isRunning()

void setRepeats(boolean repeats)

boolean isRepeats()

void setCoalesce(boolean coalesce)

boolean isCoalesce()

CardioidGraph, start()

public void start() { if ( timer != null ) { timer.stop(); } timer = new Timer( tIval, this ); curve = new GeneralPath(); curve.moveTo( 2F, 0F ); tCount = 0; repaint(); timer.start();}

CardioidGraph,actionPerformed()

public void actionPerformed( ActionEvent e ) { tCount++; double theta = tCount * Math.PI / 180; double sint = Math.sin( theta ); double cost = Math.cos( theta ); double cx = cost + cost*cost; double cy = sint + sint*cost; curve.lineTo( (float)cx, (float) cy ); if ( tCount >= 360 ) {

timer.stop();timer = null;

} repaint();}

CardioidGraph,paintComponent()

public void paintComponent( Graphics g ) { super.paintComponent( g ); Graphics2D g2 = (Graphics2D) g; drawHub( g2 ); drawCurve( g2 ); drawWheel( g2 );}

CardioidGraph, drawCurve()private void drawCurve( Graphics2D g2 ) { if ( curve == null ) return; // make curve translucent Composite c = g2.getComposite(); Composite hc = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, .5F ); g2.setComposite( hc );

g2.setPaint( Color.red ); g2.setStroke( new BasicStroke( 2 ) ); g2.draw( baseXf.createTransformedShape( curve ) ); g2.setComposite( c );}

Geometry of the Cardioid

CardioidGraph, drawWheel()private void drawWheel( Graphics2D g2 ) { Composite c = g2.getComposite(); Composite hc = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, .5F ); g2.setComposite( hc ); g2.setPaint( Color.orange ); g2.setStroke( new BasicStroke( 2 ) ); double theta = tCount * Math.PI / 180; AffineTransform whlXf = new AffineTransform(baseXf); whlXf.rotate( theta, diam/2, 0.0 ); whlXf.rotate( theta, 3*diam/2, 0.0 ); g2.draw( whlXf.createTransformedShape( wheel ) ); g2.setComposite( c );}