# rotating individual java2D shapes

I am trying to create a simple drawing program with java2D and am running into a problem with rotation. I want to allow the user to rotate individual shapes on a canvas using a mouse click but all that I seem to be able to achieve is a slight jitter when I click and drag a shape.

Here is the code I use to calculate the AffineTransform:

public boolean startMotion(double startX, double startY){ // starts a motion event if over a shape

startDragX = startX;

startDragY = startY;

for(int i = 0; i < shapeArray.size(); i++){

if(this.shapeArray.get(i) instanceof Line2D){

if(((Line2D)this.shapeArray.get(i)).ptSegDist(startDragX, startDragY) < 4){

itemToDrag = i;

Line2D rotatedLine = ((Line2D)this.shapeArray.get(itemToDrag));

double centerX = rotatedLine.getX1() +(rotatedLine.getX2() - rotatedLine.getX1())/2;

double centerY = rotatedLine.getY1() +(rotatedLine.getY2() - rotatedLine.getY1())/2;

centerPoint = new Point((int)centerX, (int)centerY);

return true;

}

}else if(this.shapeArray.get(i) instanceof Rectangle2D){

if(((Rectangle2D)this.shapeArray.get(i)).contains(new Point2D.Double(startDragX, startDragY))){

itemToDrag = i;

Rectangle2D rotatedRect = ((Rectangle2D)this.shapeArray.get(itemToDrag));

centerPoint = new Point((int)rotatedRect.getCenterX(), (int)rotatedRect.getCenterY());

return true;

}

}

}

return false;

}

public void continueRotate(Point dragPoint){ // continues a drag event

double startTheta = Math.atan2(startDragY - centerPoint.y, startDragX - centerPoint.x);

double dragTheta = Math.atan2(dragPoint.y - centerPoint.y, dragPoint.x - centerPoint.x);

double deltaTheta = dragTheta - startTheta;

isRotation = true;

Point2D[] startPoints = new Point2D[2], endPoints = new Point2D[2];

Shape rotatedShape = null;

System.out.println(deltaTheta);

rotateTransform = AffineTransform.getRotateInstance(deltaTheta, centerPoint.x, centerPoint.y);

rotateTransform.transform(startPoints, 0, endPoints, 0, 2);

startDragX = dragPoint.x;

startDragY = dragPoint.y;

updateCanvas();

}

This is my overload of paintComponent function:

protected void paintComponent(Graphics g){

super.paintComponent(g);

Graphics2D g2 = (Graphics2D) g;

AffineTransform tempTrans = g2.getTransform();

g.setColor(Color.red);

g2.setStroke(new BasicStroke(10));

if(!shapeArray.isEmpty()){

for(int i = 0; i < shapeArray.size(); i++){

if(isRotation && (i == itemToDrag)){

g2.setTransform(rotateTransform);

}

g2.draw(this.shapeArray.get(i));

if(isRotation && (i == itemToDrag)){

g2.setTransform(tempTrans);

}

}

}

}

There's 2 ways to do this: I consider one absolution rotation and the other relative rotation.

Absolute rotation would be the easiest.

Imagine an arrow pointing from the center of your shape to the right, this is your 0 degree reference (vector <1, 0>).

You can create a vector from the shape's center to the mouse pos and use the AffineTransform to rotate the shape so that reference line points at the mouse cursor.

[code]

public boolean startMotion (double startX, double startY) {

AffineTransform transform = new AffineTransform();

Shape shape;

Point2D.Double center;

Rectangle2D bounds;

//... find the nearest Shape object and assign it to the shape variable...

bounds = shape.getBounds2D();

center = new Point2D.Double( bounds.getCenterX(), bounds.getCenterY() );

transform.rotate( startX - center.x, startY - center.y );

shape = transform.createTransformedShape( shape ); // shape is now a transformed copy. leave the

// original shape alone--to ensure that the

// same corner/edge of the shape follows the

// mouse--and save the copy somewhere for the paint() method.

return true; // ?

}

[/code]

It gets the job done, but it's not as sophisticated as relative rotation.

With relative rotation, the shape rotates by the same arc distance the mouse cursor travels around the shape.

This method looks nicer, but is more difficult.

[code]

public class SomeClass { // wrapper class for a single Shape

private double startX, startY; // starting x and y

private double currX, currY; // current x and y

private boolean isDirty = true; // let's avoid excessive computation

private Shape original; // add your own getter/setter methods for this variable

private Shape cache; // rotated shape - replaces original after mouseReleased

public void startMotion (double startX, double startY) { // called from a mousePressed method

this.startX = startX;

this.startY = startY;

this.currX = startX;

this.currY = startY;

}

public void updateMotion (double newX, double newY) { // called from a mouseMotion method

this.currX = newX;

this.currY = newY;

this.isDirty = true;

}

public Shape getRotatedShape () { // called from paint(), paintComponent(), etc

if (! isDirty)

return cache;

Point2D.Double vecStart, vecCurr, vecAB = new Point2D.Double();

// time for vector math \o/

// create 2 vectors from the shape's center to each mouse location (start and current).

{

Rectangle2D bounds = Shape.getBounds2D();

vecStart = new Point2D.Double( startX - bounds.getCenterX(), startY - bounds.getCenterY() );

vecCurr = new Point2D.Double( currX - bounds.getCenterX(), currY - bounds.getCenterY() );

}

// the starting vector needs to be unit length

{

double distance = Math.sqrt( vecStart.x * vecStart.x + vecStart.y * vecStart.y );

if (distance < 0.000001) { // if the mouse is currently over the shape's center, then division by zero

isDirty = false; // don't try to recompute until mouse position changes

return cache; // do nothing and reuse the cached shape from the previous mouse location

}

vecStart.x /= distance;

vecStart.y /= distance;

}

// here's the vector math, i wish i could draw you a picture. If you want to draw it, here's how:

// 1. draw your shape (box, circle, etc) on a paper.

// 2. draw that zero degree reference (arrow to the right) along the x-axis from the shape's center

// 3. draw a line from shape's center along 45deg, stop at imaginary point...

// - this is the mouse's startX, startY point - label Line S

// 4. draw a line from shape's center along, say, 60deg, stop at different point...

// - this is the mouse's current x,y - label the line as Line M

// 5. now draw a perpendicular line from Line S to the current x,y point...

// - label this line B, label the intersection Point C.

// 6. now draw over Line S from shape's center to Point C - label this line A

// 7. if you rotate the page so that Line S points to the right, you'll see that A and B look

// like x and y components from Line S to Line M.

// 8. we'll find A and B and use them with the zero reference (vector <1,0>) to create the same

// amount of rotation Line S and Line M gave us.

// the A component is easy, it's the dot product between vecStart (unit length) and vecCurr

{

vecAB.x = vecStart.x * vecCurr.x + vecStart.y * vecCurr.y;

}

// for the B component, we'll need a line perpendicular to vecStart and unit length (also called a normal)

{

Point2D.Double vecN = new Point2D.Double( -vecStart.y, vecStart.x ); // normals in 2D are much easier than

// 3D, and we inherited unit length

// from vecStart.

vecAB.y = vecN.x * vecCurr.x + vecN.y * vecCurr.y;

}

// use vecAB to rotate our shape with respect to the zero degree reference <1,0>

// note: the angle between vecAB and <1,0> is the same angle between vecStart and vecCurr (or Line S and Line M)

{

AffineTransform transform = new AffineTransform();

transform.rotate( vecAB.x, vecAB.y ); // I used vector math so I could reuse this rotate() function.

cache = transform.createTransformedShape( original );

}

isDirty = false;

return cache;

}

}

[/code]

If my comments seem absurdly condescending, my intent was to be thorough for _any_ viewers who may not understand the math and desire the excessive documentation.

I can verify that this code is entirely sound and it's my fifth time rewriting the same material (browser crashing, excessive backspacing dragging me through my browser history, etc) so I may have overlooked something.

I'm currently working on a project that plots a spaceship's position as it accelerates in different directions, and it's using the absolute rotation described to point the spaceship at the mouse, so I know that code works :)

You'll just have to modify this version so you can redraw the screen as you move the mouse.

Hope this helps.

(edit) - clear isDirty flag when mouse is over shape's center

Message was edited by: charetjc