1

I am having trouble figuring out the correct order of transformations to apply on elements in my app. The app has elements which have parent/child relations and each element has a transformation (each element is drawn in local space and then transformed).

What i want to archive is that if i transform the parent all its child should also get transformed (so if you rotate the parent, the child should rotate around the parent).

The code below does just that but the problem is when i rotate the parent and then want to move the child. It moves in the wrong direction (due to its parent transformation ). I've tried changing the sequence of the transformations but no luck (i know i should first translate and then transform but then the child is rotated around its own axis - instead of parents).

The code:

 Element e;
 AffineTransform t = new AffineTransform();
 AffineTransform t3 = new AffineTransform();

 for (i = 0; i < el.size(); i++)
 {
    e = el.get(i);
    t3.setToIdentity();
    t.setToIdentity();
    tmp = e.getParent();
    while(tmp != null)
    {
         t.translate(tmp.getX(), tmp.getY());
         t3.rotate(Math.toRadians(tmp.getAngle()),tmp.getAnchorX(), tmp.getAnchorY());
         tmp = tmp.getParent();
    }

    t.concatenate(t3);
    t.translate(e.getX(), e.getY());

    t.rotate(Math.toRadians(e.getAngle()),e.getAnchorX(), e.getAnchorY());

    e.draw(g2d,t);
}

The problem: - given two elements (one child of other) - parent is rotate (by 40 degrees) - child is then move by 10px How can i concatenate transformations so that when i move the child it wont move in the rotated direction?

EDIT: The code that Torious posted (not working yet):

public AffineTransform getTransformTo(Element ancestor) {
    AffineTransform t = (AffineTransform)getAffineTransform().clone();
    Element parent = getParent();
    while (parent != null && parent != ancestor) {
        t.preConcatenate(parent.getAffineTransform());
        parent = parent.getParent();
    }
    return t;
}


public void translateInAncestorSpace(Element ancestor, Point translation) {
    AffineTransform fromAncestor = getTransformTo(ancestor); // to ancestor space
    try
    {
        fromAncestor.invert();
    } catch(Exception e)
    {
       e.printStackTrace();   
    }
    translation = (Point)fromAncestor.transform(translation, new Point());

    Transform t1 = new Transform();
    t1.translate(translation.x,translation.y);
    transform(t1);
}

Output of the code:

 Moving by [x=1,y=1] old position [x=22,y=40]
 Moved by [x=-21,y=-39] new position [x=1,y=1]
 Moving by [x=2,y=2] old position [x=1,y=1]
 Moved by [x=1,y=1] new position [x=2,y=2]
 Moving by [x=4,y=3] old position [x=2,y=2]
 Moved by [x=2,y=1] new position [x=4,y=3]
blejzz
  • 3,349
  • 4
  • 39
  • 60

2 Answers2

3

Something like this:

// get composite transform for 'e'
t.setToIdentity();
t.translate(e.getX(), e.getY());
t.rotate(...);

tmp = e.getParent();
while (tmp != null) {

    // get composite transform for parent
    t3.setToIdentity();
    t3.translate(tmp.getX(), tmp.getY());
    t3.rotate(...);
    tmp = tmp.getParent();

    t.preConcatenate(t3); // t3 is applied after t
}

e.draw(g2d, t);

Edit: For moving an element in 'global space' while it's actually a child of another element, I'd suggest transforming the desired move to element space so it can simply be added to its translation:

(untested code)

// Element.getTransformTo: get transform to ancestor or root (null)
public AffineTransform getTransformTo(Element ancestor) {
    AffineTransform t = getCompositeTransform();
    Element parent = getParent();
    while (parent != null && parent != ancestor) {
        t.preConcatenate(parent.getCompositeTransform());
        parent = parent.getParent();
    }
}

// Element.translateInAncestorSpace
// ancestor may be null for root/global space
public void translateInAncestorSpace(Element ancestor, Point translation) {
    AffineTransform fromAncestor = getTransformTo(ancestor); // to ancestor space
    fromAncestor.invert(); // from ancestor space

    // [NEW] re-apply element rotation since translation occurs after rotation
    fromAncestor.rotate(Math.toRadians(angle), anchor.x, anchor.y);

    translation = fromAncestor.deltaTransform(translation, new Point());

    // translation is now in element space; add to existing translation
    element.setX(element.getX() + translation.x);
    element.setY(element.getY() + translation.y);
}

// example usage:
// (this should move the element 10px to the right, regardless
// of parent transforms)
element.translateInAncestorSpace(null, new Point(10, 0));

Also consider implementing an AffineTransform Element.getCompositeTransform() method, which will make your life easier in the end.

Also consider rendering your 'element tree' from the root node to the leaf nodes, so you'd get something like this:

public void render(Element node, AffineTransform transform) {

    AffineTransform nodeTransform = node.getCompositeTransform();
    nodeTransform.preConcatenate(transform);

    node.draw(g2d, nodeTransform);

    for (Element child : node.children())
        render(child, nodeTransform);
}
Torious
  • 3,364
  • 17
  • 24
  • thank you very much. You can remove the "untested" as it works. The problem with the "element tree" is that i cant render the elements as a tree because elements have to be rendered ordered by z index. – blejzz Apr 22 '12 at 14:59
  • spoke to soon without properly testing it.. The problem is that when the parent is rotated by 40 degrees and you want to move the child by 10px it will move in the rotated direction. – blejzz Apr 22 '12 at 15:05
  • That's supposed to happen with the code I posted, since the child is inside the parent's coordinate system. Can I ask why you want to translate independently but rotate dependently? – Torious Apr 22 '12 at 15:11
  • elements are moved on mouse events and if you move a child it will move in a undesired way. The rotation must be dependent because elements support keyframe animations (so only movement has to be independent). – blejzz Apr 22 '12 at 15:16
  • 1
    I edited my answer. I think this is the 'cleanest' approach to solving the problem. Let me know if it works/fails. – Torious Apr 22 '12 at 15:36
  • i have implemented the code but something is off. Now even one element cant be moved (its stuck to global position 0,0). Here is a output of translateInAncestorSpace: Moving by java.awt.Point[x=1,y=1] old position java.awt.Point[x=22,y=40] Moved by java.awt.Point[x=-21,y=-39] new position java.awt.Point[x=1,y=1] Moving by java.awt.Point[x=2,y=2] old position java.awt.Point[x=1,y=1] Moved by java.awt.Point[x=1,y=1] new position java.awt.Point[x=2,y=2] – blejzz Apr 22 '12 at 17:00
  • Try `translation = fromAncestor.deltaTransform(...)` instead of `translation = fromAncestor.transform(...)` – Torious Apr 22 '12 at 17:14
  • the moving is now ok now but if the element is rotated the move is also rotated (it should be parallel to x an y axis). – blejzz Apr 22 '12 at 17:49
1

Ok, I think I understand what you want now. The code below should (untested) concatenate translations, but not rotations. Each element is affected only by its own rotation.

I've posted this as a new answer since the code's intention is different from the code in my previous answer.

With this code, just set an element's x and y to translate it.

// set e's translation
t.setToIdentity();
t.translate(e.getX(), e.getY());

// add ancestor translations (order is irrelevant)
Element parent = e.getParent();
while (parent != null) {
    t.translate(parent.getX(), parent.getY());
    parent = parent.getParent();
}

// append e's rotation (applied first)
t.rotate(...);

e.draw(g2d, t);

Is this what you mean?

Torious
  • 3,364
  • 17
  • 24
  • each element is affected by its own and its parents rotation. Your previous answer was on track. The only problem is when element is being moved because it doesnt move in the x,y direction. For example one element at 10,10 and angle 30, and other element is a child of the first one at 10,10 and angle 30 (his global values are 20,20 and 60). And i want to move the child to the right by 10 px. But if i move the element by 10px to the right it moves 10px rotated by 60 degrees. – blejzz Apr 22 '12 at 18:33
  • What does your `transform()` method do? Maybe you should just set x/y like I did in `translateInAncestorSpace()` instead of calling `transform()`. – Torious Apr 22 '12 at 19:02
  • it just adds dx and dy to the current position. Same as your code. – blejzz Apr 22 '12 at 19:04
  • the only solution i currently have is to drop the transformation and store only global transformations per element (but then i have to update every child when a parent changes). – blejzz Apr 22 '12 at 19:23
  • I've added a line marked `[NEW]` in a comment in my original answer. Let me know if it works. – Torious Apr 22 '12 at 19:26
  • jup it solved it. Thank you very much you have been really kind and helpful! – blejzz Apr 22 '12 at 19:31