34
1.00 Lecture 18 Geometric Transformations in the 2D API

1.00 Lecture 18 Geometric Transformations in the 2D API

Embed Size (px)

Citation preview

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 );}