Update Rotation Transform Matrix After Dragging Component


#1

I have been experimenting rotating components using a slider control and dragging them afterwards.

The components are rotated around their centres as expected and everything is well, the problem is, after the object is dragged to a different position rotating them using a slider will throw them off wildly. I understand it is happening because after the object has been dragged the rotation centre of its matrix is still bound to the previous location.

I tried resetting the transform matrix using this bit of code:

void reinitRotation(float radians) {
     float top = getY();
     float left = getX();
     float w = getWidth()*.5;
     float h = getHeight()*.5;
     setTransform(AffineTransform::rotation(radians, left + w, top + h));
     repaint();
}

However it does not work as expected because the values the getPosition() returns also appear to be transformed, so the new matrix is no better. The object continues to rotate around its centre, the problem is, it changes its position in a strange manner. Using getBoundsInParent() instead of getPosition() for some strange reason does not seem to work either.

The question is, how do I reset the transform matrix to be centred at object's new location?


#2

I spent some time trying to figure it out and ended up even more confused.

I tried a different method to track the center of the position of the component by using the position change of the mouse cursor in the parent component during dragging like this:

private:
    Point<float> mousePos;
    Point<float> mouseStart;
    Point<float> mouseDelta;

Here mousePos position defaults to the location of the centre of the component that is being rotated relative to its parent. mouseStart records the position of the mouse in the parent component when the dragging of the component in question starts and mouseDelta is the change in the location of the mouse in the parent component. This delta is applied to mousePos to update the location of the centre of the rotational transform. All works like this:

void mouseDown(const MouseEvent& e)
{
    dragger.startDraggingComponent(this, e);
    mouseStart = e.getEventRelativeTo(getParentComponent()).position;
}

void mouseUp(const MouseEvent& e)
{
    Point<float> newPos = e.getEventRelativeTo(getParentComponent()).position;
    mouseDelta = newPos - mouseStart;
    mousePos += mouseDelta;
}

void reinitRotation(float radians) {
    float top = mousePos.getY();
    float left = mousePos.getX();
    setTransform(AffineTransform::rotation(radians, left, top));
    repaint();
}

The result, however, is not much different as shown in the initial post. Breakpoints show that the mouse positions update as expected, however subsequent rotation of the object is still messed up. It looks like the values supplied to construct a transform get messed up somehow along the way but there is nothing to suggest that in the Juce code.

Has anyone had to deal with similar issues?


#3

I don't know if there is a Juce specific problem in it as well, but working with other scene graphs (OpenSG), I would try first

1.) getTransform

2.) translate so that the center is at position zero

3.) do your rotation

4.) re-apply the translation

IIRC you find the translation in mat01 / mat02, or simply translate the coordinate 0/0 via AffineTransform::transformPoint( dx, dy )

Maybe something like this helps:

float dx=0;
float dy=0;
AffineTransform transform = getTransform();
transform.transformPoint (dx, dy);
// add rotation center coordinate to dx/dy, if necessary
transform = transform.translated (-dx, -dy);
// do your rotation here
transform = transform.rotated (angle);
setTransform (transform.translated (dx, dy));

Good luck


#4

Matrices make my head hurt too.

It gets really messy if you start changing both the component's position and its transform, because obviously the position itself is transformed. Maybe just leave the component at 0, 0 and do both your translation and rotation by changing the matrix?


#5

HI daniel,

I'll try your suggestion. I will let you know how it worked.


#6

Hi jules,

I would be quite happy just ot use transforms for both rotation and translation, unfortunately the ability to manipulate the controls is supposed to be the key element of the interface for the app. 

Basically, what I need is to be able to move, resize and rotate simple shapes on the screen using a mouse. Those do not have to be components really, although using them is probably a bit more convenient. In its simplest form that could even be shapes drawn in the Graphics context, however I'd imagine selecting and dragging them would be tricky.

Would there be other tools in Juce's arsenal to achieve such functionality?

 


#7

Not sure why you think using a single transform for your component's entire position + rotation would make that harder. I think it'd probably make it easier. Certainly juce isn't lacking any tools that you might need for this, but like I said, working with transforms can be very confusing and hard to visualise.


#8

I understand you are suggesting not to use the default dragging functionality of the component but rather implement a custom one that uses translate transforms in order to achive a similar mouse drag positioning effect.


#9

Reading your post again, I wonder, why you involve the Component's position into your rotation?

maybe simply change the line

// instead of
setTransform(AffineTransform::rotation(radians, left + w, top + h));
// do
setTransform(AffineTransform::rotation(radians, w, h));

But I can't answer the question, what impact this has for the bounding box. This note in the docs made me nervous:

http://learn.juce.com/doc/classComponent.php#a882733448ca364ba8f493c6301800572

Note that if you've used setTransform() to apply a transform, then the component's bounds will no longer be a direct reflection of the position at which it appears within its parent, as the transform will be applied to its bounding box.

So the transform will be applied to the boundingBox or not? I guess it's a typo, either the transform is applied to the bounding box, then it appears within the bounding box. Or if it's not applied, the Component is painted outside the bounding box.

If you do the translation in the transform or with the Component's position is a matter of taste IMHO (as long as the hit tests and drawing optimizations are correctly implemented, which I would expect). But I would take jules' warning serious:

It gets really messy if you start changing both the component's position and its transform, because obviously the position itself is transformed.

So I would try not to mix the Component's position AND a translation inside the matrix, I think that's the biggest pitfall.

Btw. I wrote nonsense, the translation in the matrix is 02/12


#10

// instead of 
setTransform(AffineTransform::rotation(radians, left + w, top + h)); // do setTransform(AffineTransform::rotation(radians, w, h));

This is exactly how I expected the component transform to behave however the rotational transform is not local, unfortunately. It is applied in the coordinate system of the parent, so if you want to rotate the object around its center, you have to find its center in the parent's scale. This means you need to find the coordinates of its top-left corner and then add half of the width and half of the height so that you get the center of the object. Around it you can then rotate, in theory at least.

I am not sure whether this is a feature, or a nuisance but I strongly suspect that using the coordinate system of the parent is what causes the problem in the first place. If the transform  of a component was applied in its local coordinates (where I strongly believe it belongs) the location of the component within its parent would not matter and would always be treated as the orgin.


#11

Ok, that make's sense to me. The X and Y coordinates of the Component are only stored within the transform.

I thought the X and Y position are an extra translation, but as I understand you, it's not.

So to rotate around it's local centre you need to preserve the previous matrix like I wrote before. Did you try that? Translate the current transform, so that the object's center is at zero, apply your rotation and move back to the previous position and you'll be fine. It's only two matrix multiplications, and if your're lucky it's even done on the hardware, so don't worry about that.

Another thing you will have to define for yourself is, if you intend to do relative operations or absolute. So you use only setTransform, but you expect the position to be kept. That can't work. 

Good luck...


#12

I think I understand what you are talking about. Before setting a new transform I would need to apply the old one to the delta between the two positions to figure out where the transform thinks the new center is. I gonna try it tomorrow, it is a bit late for that now, I spent too much time on this weekend.


#13

Don't know if you already saw them, but the Component::getLocalPoint and getLocalArea would probably be helpful for this kind of thing.


#14

I tried it and this approach almost worked:

(by the way, the forum does not appear to accept gif images as uploads even that the size is smaller than 1mb)

Now the component can be dragged around its parent and rotated at any position around its centre, however the rotation seems to break down at around 90 degrees regardless of the location.

The code used was this:

    void reinitRotation(float radians) {
        float midV = getHeight() * .5;
        float midH = getWidth() * .5;
        Point<float> center = Point<float>(midH, midV);

        Component* parent = getParentComponent();
        center = parent->getLocalPoint(this, center);
        setTransform(AffineTransform::rotation(radians, center.getX(), center.getY()));
    }

I am yet to try daniel's method but will do that later this week, I am a bit taken over by other things at the moment.