A transformation is an alteration of the geometry of a shape. Transformations affect the location of the coordinates of a shape and we distinguish three major ones: translation (or move), rotation, and scale.
Movement (translation) is very simple: all we need to do is add an offset to the x and y coordinate. Specifically:
//********** Move
public void move(double xoff, double yoff){
x += xoff; // or x
= x + xoff;
y += yoff;
}
Scaling is also simple. All we need is to multiply the coordinate by a scaling factor xs and ys. Specifically:
//********** Scale
public void scale(double xs, double ys){
x *= xs;
y *= ys;
}
This will expand or shrink the position of the shape relatively to the 0,0 coordinate system (for example (2,2) coordinate will become (6,6) if multiplied by 3. That will move (2,2) four units on each direction away for (0,0)). In order to scale with respect to a reference point ref we need to:
move the coordinates to (0,0) (by subtracting ref),
do the scaling,
and them move back to the original position (by adding ref).
Here is the corresponding code:
//********** Scale with respect to a reference point
public void scale(double
xs, double ys, MyPoint ref){
x = (x-ref.x)*xs + ref.x;
y = (y-ref.y)*ys + ref.y;
}
Rotation is bit more complicated. Luckily, we already saw how to arrange points along a circle. Here we do something similar: we multiply the x and y coordinates by the both the sine and cosine of the rotation angle according to the following formula:
//********** Rotate
public
void rotate (double angle) {
angle = Math.PI/180*angle;
//make it to degrees
double newx = x *
Math.cos(angle) + y * Math.sin(angle);
double newy = y *
Math.cos(angle) - x * Math.sin(angle);
x =
newx;
y =
newy;
}
The above code will translate an object around the (0,0) origin but (as in scaling) if we want to rotate around a reference point we do the following:
//********** Rotate around point ref
public
void rotate (double angle, MyPoint ref) {
double cosa, sina;
cosa
= Math.cos(Math.PI/180*angle);
sina
= Math.sin(Math.PI/180*angle);
double newx = (x-ref.x) * cosa - (y-ref.y) * sina + ref.x;
double newy = (y-ref.y) * cosa + (x-ref.x) * sina + ref.y;
x = newx;
y = newy;
}
All the above transformations can be generalized into schemes (called matrices) that can let you see the “big picture” and to predict or explain all possible transformations. We will not get into the details of matrices yet.
18.1.
Implementing Transformations
After we create the basic transformation methods (move, rotate and scale) we put them into the MyPoint class.
public class MyPoint {
double x, y; // members
of class
//********** Constructor
public MyPoint(double xin, double yin){
x = xin;
y = yin;
}
//********** Move
public void move(double xoff, double yoff){
x += xoff;
y += yoff;
}
//********** Rotate
public void rotate (double angle, MyPoint ref) {
double cosa, sina;
cosa = Math.cos(Math.PI/180*angle);
sina = Math.sin(Math.PI/180*angle);
double newx =
(x-ref.x) * cosa - (y-ref.y) * sina + ref.x;
double newy =
(y-ref.y) * cosa + (x-ref.x) * sina + ref.y;
x = newx;
y = newy;
}
//********** Scale
public void scale(double xs, double ys, MyPoint ref){
x = (x-ref.x)*xs + ref.x;
y = (y-ref.y)*ys + ref.y;
}
}
Then we need to adjust the MySegment and MyShape classes to propagate the transformation methods all the way to the MyPoint methods. This done as follows:
import java.awt.*;
public class MySegment {
MyPoint start; //
members of class
MyPoint end;
//********** Constructor
public MySegment(MyPoint p1, MyPoint p2){
start = p1;
end = p2;
}
//********** Move
public void move(double xoff, double yoff){
start.move(xoff, yoff);
end.move(xoff, yoff);
}
//********** Rotate
public void rotate (double angle, MyPoint ref) {
start.rotate(angle, ref);
end.rotate(angle, ref);
}
//********** Scale
public void scale(double xs, double ys, MyPoint ref){
start.scale(xs, ys, ref);
end.scale(xs, ys, ref);
}
//*********** draw
public void
draw(Graphics g){
g.drawLine((int)start.x, (int)start.y, (int)end.x,
(int)end.y);
}
}
and
import java.awt.*;
public class MyShape {
MySegment[] segs; //
members of class
int numSegments;
//********** Constructor
public MyShape(int numInputSegments, MySegment[]
inputSegments){
numSegments = numInputSegments;
segs = new MySegment[numSegments];
for(int i=0; i<numSegments; i++)
segs[i] = inputSegments[i];
}
//********** Move
public void move(double xoff, double yoff){
for(int i=0; i<numSegments; i++)
segs[i].move(xoff, yoff);
}
//********** Rotate
public void rotate (double angle, MyPoint ref) {
for(int i=0; i<numSegments; i++)
segs[i].rotate(angle, ref);
}
//********** Scale
public void scale(double xs, double ys, MyPoint ref){
for(int i=0; i<numSegments; i++)
segs[i].scale(xs, ys, ref);
}
//*********** draw
public void draw(Graphics g){
for(int i=0; i<numSegments; i++)
segs[i].draw(g);
}
}
Now in the main code (simple) all we need to do is create shapes and then transform them. For example:
import java.applet.*;
import java.awt.*;
public class simple extends Applet{
MyPoint p1, p2, p3, p4;
MySegment[] s = new MySegment[2];
MyShape shape;
//*****************************************
public
void init(){
p1 =
new MyPoint(100., 100.);
p2 = new MyPoint(200., 200.);
p3 =
new MyPoint(200., 200.);
p4 =
new MyPoint(300., 100.);
s[0]
= new MySegment(p1, p2);
s[1]
= new MySegment(p3, p4);
shape = new MyShape(2, s);
}
//*****************************************
public
void paint(Graphics g){
shape.move(10., 20.);
MyPoint ref = new MyPoint(200., 200.);
shape.rotate(30.,ref);
shape.draw(g);
}
}
18.2.
Mouse feedback
If want to do transformations using feedback from the mouse we need to modify the code slightly to add the mouseDrag and mouseDown methods:
import java.applet.*;
import java.awt.*;
public class simple
extends Applet{
MyPoint p1, p2, p3, p4;
MySegment[] s = new MySegment[2];
MyShape shape;
int xfirst = 0; //
the first point clicked
int yfirst = 0;
//*****************************************
public void init(){
p1 = new MyPoint(100., 100.);
p2 = new MyPoint(200., 200.);
p3 = new MyPoint(200., 200.);
p4 = new MyPoint(300., 100.);
s[0] = new MySegment(p1, p2);
s[1] = new MySegment(p3, p4);
shape = new MyShape(2, s);
}
//*****************************************
public void paint(Graphics g){
shape.draw(g);
}
//*****************************************
public boolean mouseDown(Event evt, int x, int y){
xfirst = x; //
remember this point
yfirst = y;
return true;
}
//*****************************************
public boolean mouseDrag(Event evt, int x, int y){
int xoff = x - xfirst;
// get the offset
int yoff = y - yfirst;
MyPoint ref = new MyPoint(200., 200.);
shape.rotate(xoff, ref);
xfirst = x;
//remember this point
yfirst = y;
repaint();
return true;
}
}
The rotate method takes an incremental angle of rotation. In other words if we are 40 degrees and we want to go to 45 we must pass 5 degrees to the rotate because it adds to the previous angle (and not the absolute value 45). So we always need to pass the increments of the mouse (and not its absolute position). So in the mouseDrag method we get an x and y coordinate of the mouse and we subtract the first position of the mouse (when it was clicked down in mouseDown). Then we get the offset in both x and y that we pass to the rotate method. Before we leave mouseDrag we save the x and y mouse position in xfirst and yfirst so that next time we call mouseDrag we can get the offset increment.