1.00 Lecture 18 Geometric Transformations in the 2D API Transformed Coordinates

advertisement
1.00 Lecture 18
Geometric Transformations
in the 2D API
Transformed Coordinates
in NgonApp
( −1.5, −1 . 5 )
(0.0,0.0)
(1.0,0.0)
 cos  2 π ,sin  2 π 
    
  5   5 
2
1
Pixel Coordinates
in NgonApp
(0,0)
static final float SCALE=200.0F;
static final float tx = 1.5F;
static final float ty = 1.5F;
float transX( float x ) {
(300,300)
(500,300)
return ( x + tx ) * SCALE;
}
float transY( float y ) {
return ( y + ty ) * SCALE;
(362,490)
}
3
Transformations in the
Cardioid Grapher
The circle with
the marked rim
drawing the
cardioid is
continuously
transformed in
the animation
The 4.0x3.0
Cartesian
coordinate
space is
transformed to
arbitrary device
coordinates
4
2
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.
5
Scaling
 sx
0

 0
0
sy
0
0  x   s x ∗ x 
0  y  =  s y ∗ y 
  

1   1   1 
6
3
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 xaxis, scale by sx=1, sy=-1.
7
Reflection as Scaling
1 0 0   x 
 0 −1 0   y  =

 
 0 0 1   1 
x 
− y 
 
 1 
8
4
Translation
1 0 t x   x   x + t x 
0 1 t   y  =  y + t 
y 
y


 0 0 1   1   1 
ty
tx
9
Rotation
 cos ( α )
 sin ( α )

 0
− sin ( α )
cos ( α )
0
  x   x cos ( α ) − y sin ( α ) 
0  y  =  x sin ( α ) + y cos ( α ) 
  

1  1  
1

0
α
10
5
Composing Transformations
• Suppose we want to scale point (x, y) by 2 and
then rotate by 90 degrees.
 0 −1 0   2 0 0   x  
 1 0 0   0 2 0   y  


  

 0 0 1   0 0 1  1  
rotate
scale
11
Composing Transformations, 2
Because matrix multiplication is associative, we can
rewrite this as
  0 −1 0  2 0 0    x 

 0 2 0    y 
1
0
0


 
  0 0 1  0 0 1   1 

 

 0 −2 0   x 
= 2 0 0   y 

 
 0 0 1  1 
12
6
Composing Transformations, 3
•
Because matrix multiplication does not regularly commute,
the order of transformations matters. This squares with our
geometric intuition.
2. translate
1. scale
1. translate
•
2. scale
If we invert the matrix, we reverse the transformation.
13
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.
14
7
Transformations and the Origin, 2
1. translate to origin
2. rotate
3. translate back
15
Transformations in the 2D API
•
•
Transformations are represented by instances
of the AffineTransform class in the
java.awt.geom package.
Build a compound transform by
1.
2.
Creating a new instance of AffineTransform
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)
16
8
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.
First to be applied
translate
scale
baseXf
17
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 );
}
18
9
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:
-1.5
Y
1.5
3.0
X
4.0
19
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 );
. . .
20
10
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();
}
21
GraphPanel, paintComponent()
public void paintComponent( Graphics g ) {
super.paintComponent( g );
Graphics2D g2 = (Graphics2D) g;
if ( ! getSize().equals( curDim ) )
doResize();
drawAxes( g2 );
drawGrid( g2 );
}
22
11
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();
}
23
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));
}
24
12
Initializing CardioidGraph
uRect.x
uRect.y
hub
wheel
uRect.height
uRect.width
25
Initializing CardioidGraph,2
public 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 );
}
26
13
Timers
•
Swing provides a utility class called Timer that makes it
simpler to build animations.
• Timers tick at an interval you can set, and the ticks are
reported as ActionEvents.
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*/ }
27
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()
28
14
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();
}
29
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();
}
30
15
CardioidGraph,
paintComponent()
public void paintComponent( Graphics g ) {
super.paintComponent( g );
Graphics2D g2 = (Graphics2D) g;
drawHub( g2 );
drawCurve( g2 );
drawWheel( g2 );
}
31
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 );
}
32
16
Geometry of the Cardioid
θ
diam
θ
diam/2
diam
33
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 );
}
34
Java® is a trademark or registered trademark of Sun Microsystems, Inc. in the United
States and other countries.
17
Download