1

I am implementing a java swing application which has a JPanel that serves as a drawing surface. The surface renders (active) different elements. Each element has its own shape and an affine transform which is used for rendering and collision detection (each element is drawn with local coordinates and than transformed with the affine transform - like opengl).

The elements can be moved and rotated on the surface (this is done through transforms). Every time a transform is applied a Area object is created with the shape and the transformation (for accurate collision detection).

The problem is when I rotate the element (for 45 degrees) and then move it by 10 px. When I move it the element moves in the rotated direction which I don't want.

Is there any simple way I can overcome this?
(If my description isnt enough I'll post some example code).

EDIT:

 class Element
 {
     private AffineTransform transform = new AffineTransform();
     private Shape shape = new Rectangle(0,0,100,100);       
     private Area boundingBox;       

     public void onMouseDrag(MouseEvent e)
     {
        translate(dx,dy); // dx,dy are calculated from event
     }

     public void onMouseMove(MouseEvent e)
     {
        rotate(Math.atan2(dx/dy); // dx,dy are calculated from event
     }

     public void translate(int dx,int dy)
     {
        transform.translate(dx,dy);
        boundingBox = new Area(shape);
        boundingBox.transform(transform);
     }

     public void rotate(double angle)
     {
         transform.rotate(Math.toRadians(angle));
         boundingBox = new Area(shape);
         boundingBox.transform(transform);
     }

     public void draw(Graphics2D g2d)
     {
         g2d.setTransform(transform); 

         g2d.draw(shape); 
        ...
      }

 } 
blejzz
  • 3,349
  • 4
  • 39
  • 60
  • 2
    With this code, you could use `rotate(theta, x, y)` instead of `rotate(theta)` to rotate around the current position. Anyway, incremental transforms like this are discouraged, and you should consider giving `Element` a `position` and `rotation` property, which is used to re-calculate an `AffineTransform` when they change, or once per frame/update. – Torious Apr 18 '12 at 22:56
  • From http://web.eecs.utk.edu/~bvz/gui/notes/transforms/transform.html: "In most graphical toolkits, including Java, transformations are applied in a LIFO order. That is, the last transformation listed in your code is the first transformation that actually gets applied, and the first transformation listed in your code is the last transformation that actually gets applied." – CodeSlinger Apr 18 '12 at 22:59
  • @Torious if you would post an answer with a example ill accept it – blejzz Apr 18 '12 at 23:10

2 Answers2

2

Reverse the order you are applying the transformations. Or unrotate the object, move it, then re-rotate the object. We would really need to see your code (or even pseudocode) to give you a better answer than this.

CodeSlinger
  • 429
  • 3
  • 8
  • updated my question with the relevant code. The problem is i dont know the order or of the transformations. – blejzz Apr 18 '12 at 22:53
  • +1 This related [Q&A](http://stackoverflow.com/q/3843105/230513) examines the (non-commutative) order in which transforms are concatenated. – trashgod Apr 19 '12 at 03:12
2

I've modified your code by giving Element position and orientation fields, which you might want to expose through getters/setters (probably defensively copying the Point instance before returning/setting it). updateTransform() simply re-builds the AffineTransform based on the Element's current position and orientation.

public class Element {

    private Point position = new Point();
    private float orientation;

    private AffineTransform transform = new AffineTransform();
    private Shape shape = new Rectangle(0, 0, 100, 100);
    private Area boundingBox;

    public void onMouseDrag(MouseEvent e) {
        translate(dx, dy);
    }

    public void onMouseMove(MouseEvent e) {
        rotate(Math.atan2(dx / dy));
    }

    public void translate(int dx, int dy) {
        position.translate(dx, dy);
        updateTransform();
    }

    public void rotate(double angle) {
        orientation += Math.toRadians(angle);
        updateTransform();
    }

    private void updateTransform() {
        transform.setToIdentity();
        transform.translate(position.x, position.y);
        transform.rotate(orientation);
        // ... update bounding box here ...
    }

    public void draw(Graphics2D g2d) {
        g2d.setTransform(transform);
        g2d.draw(shape);
    }

}

Also, consider the following approach:

  • Change draw(Graphics2D) to draw(Graphics2D, AffineTransform transform)
  • Create AffineTransform Element.getCompositeTransform() which builds the current AffineTransform and returns it (like updateTransform()).
  • Remove the transform field from Element.
  • In your render method, do something like:

.

for (Element element : elements) {
    AffineTransform transform = element.getCompositeTransform();
    element.draw(graphics, transform);
}

Why? This gives you the flexibility to control Element's drawing transform externally. This is useful if you'll build a node graph (like when an Element can have Element children) and you'll recursively render the graph, concatenating transforms. Btw, this approach is pretty much standard in 3D programming.

M A
  • 71,713
  • 13
  • 134
  • 174
Torious
  • 3,364
  • 17
  • 24
  • thanks. I've child support but the collection is z ordered instead of tree based. But the transformation merging is similar. – blejzz Apr 19 '12 at 00:33