1.  Standard Transformations (move, rotate, scale)

 

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.