Vhelium
Vhelium

Reputation: 23

Java: Coordinate Transformation - Rotation & Scale

Given is a rotated rectangle which is inscribed into another rectangle.
Both rectangles have their own coordinate systems.
In the inscribed & rotated rectangle there is a point P with the coordinates relative to this rectangle (red).
Wanted are the coordinates of this points relative to the outer rectangle (green):
EDIT: Given are also the width and height of both rectangles + the angle of the rotation

Image of an overview: http://i.imgur.com/QH96n7x.png

My attempts with a transform matrix (setRotate() & setScale()) failed and with Trigonometry I neither got it working.

How can I calculate the position of point P relative to the outer rectangle?

Thanks in advance!

Upvotes: 1

Views: 1670

Answers (2)

Vhelium
Vhelium

Reputation: 23

EDIT2: A friend of mine pointed out a much more elegant solution (Thanks!). Here it is:

@Override
public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint)
{
    shadowSize.set((int) width + 1, (int) height + 1);

    float x = offsetX - w / 2, y = offsetY - h / 2;
    shadowTouchPoint.x = Math.round(x * c - y * s * sFac + w / 2 + (width - w) / 2);
    shadowTouchPoint.y = Math.round(x * s * sFac + y * c + h / 2 + (height - h) / 2);
}

sFac is defined as:

float sFac = (float) Math.signum(rotationRad);

Alright, I managed to solve it with Trigonometry.
For anyone interested, here is the source code for my custom DragShadowBuilder which is used in Android to drag and drop rotated and scaled View objects:

public class TargetDragShadowBuilder extends View.DragShadowBuilder
{
    ImageView view;
    float offsetX, offsetY;

    double rotationRad;
    float w;
    float h;
    double s;
    double c;
    float width;
    float height;

    public TargetDragShadowBuilder(final ImageView view, float offsetX, float offsetY)
    {
        super(view);
        this.view = view;
        this.offsetX = offsetX * view.getScaleX();
        this.offsetY = (int) (offsetY * view.getScaleY());

        rotationRad = Math.toRadians(view.getRotation());
        w = view.getWidth() * view.getScaleX();
        h = (int) (view.getHeight() * view.getScaleY());
        s = Math.abs(Math.sin(rotationRad));
        c = Math.abs(Math.cos(rotationRad));
        width = (int) (w * c + h * s);
        height = (int) (w * s + h * c);
    }

    @Override
    public void onDrawShadow(Canvas canvas)
    {
        canvas.scale(view.getScaleX(), view.getScaleY(), width / 2, height / 2);
        canvas.rotate(view.getRotation(), width / 2, height / 2);
        canvas.translate((width - view.getWidth()) / 2, (height - view.getHeight()) / 2);

        super.onDrawShadow(canvas);
    }

    @Override
    public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint)
    {
        shadowSize.set((int) width + 1, (int) height + 1);

        double x = offsetX, y = offsetY;
        if(rotationRad < 0)
        {
            final double xC = offsetX / c;
            x = xC + s * (offsetY - xC * s);
            final double yC = offsetY / c;
            y = yC + s * (w - offsetX - yC * s);
        }
        else if(rotationRad > 0)
        {
            final double xC = offsetX / c;
            x = xC + s * (h - offsetY - xC * s);
            final double yC = offsetY / c;
            y = yC + s * (offsetX - yC * s);
        }

        shadowTouchPoint.x = (int) Math.round(x);
        shadowTouchPoint.y = (int) Math.round(y);
    }
}

It is valid for rotations from -90° to +90°.
If anyone has a cleaner or easier solution I am still interested in it.

EDIT: And here is the code for how I handle the drop of the View object.

private class TargetDragListener implements OnDragListener
{
    @Override
    public boolean onDrag(View v, DragEvent e)
    {
        switch(e.getAction())
        {
            case DragEvent.ACTION_DRAG_STARTED:
                break;
            case DragEvent.ACTION_DRAG_ENTERED:
                break;
            case DragEvent.ACTION_DRAG_EXITED:
                break;
            case DragEvent.ACTION_DROP:
                if(e.getLocalState() instanceof TargetItem)
                {
                    TargetItem target = (TargetItem) e.getLocalState();
                    dropTarget(target, e.getX(), e.getY());
                }
                break;
            case DragEvent.ACTION_DRAG_ENDED:
                ((DragableItem) e.getLocalState()).setVisibility(View.VISIBLE);
            default:
                break;
        }
        return true;
    }
}

private void dropTarget(TargetItem target, float x, float y)
{
    target.setDragged(false);
    target.setVisibility(View.VISIBLE);
    target.bringToFront();

    final float scaleX = target.getScaleX(), scaleY = target.getScaleY();
    double rotationRad = Math.toRadians(target.getRotation());
    final float w = target.getWidth() * scaleX;
    final float h = target.getHeight() * scaleY;
    float s = (float) Math.abs(Math.sin(rotationRad));
    float c = (float) Math.abs(Math.cos(rotationRad));
    float sFac = (float) -Math.signum(rotationRad);

    target.offsetX *= scaleX;
    target.offsetY *= scaleY;

    x += -target.offsetX * c - target.offsetY * s * sFac;
    y += target.offsetX * s * sFac - target.offsetY * c;
    float[] pts = { x, y };
    float centerX = x + c * w / 2f + sFac * s * h / 2f;
    float centerY = y - sFac * s * w / 2f + c * h / 2f;

    transform.setRotate(-target.getRotation(), centerX, centerY);
    transform.mapPoints(pts);

    target.setX(pts[0] + (w - target.getWidth()) / 2);
    target.setY(pts[1] + (h - target.getHeight()) / 2);
}

Upvotes: 0

SergeyB
SergeyB

Reputation: 9858

Might be a bit of an overkill, but JTS (Java Topology Suite) provides tons of useful functions for dealing with 2D coordinates.

Upvotes: 0

Related Questions