1
\$\begingroup\$

I got a quite tricky problem:

I have my mouse position and my rectangle position in canvas space(screen space but with adjusted scaling). Now I want my mouse position converted to local space of the rectangle. Problem is, the rectangle can be rotated. Without rotation its quite simple and works nicely.

Heres a graphic for better demonstration:

enter image description here

Edit3: Got it finally working. I messed up with a minus in the calculation instead of using a +. Also the hierarchy of the image in unity was incorrect. Lastly I had to change the rotation from CCW to CW(Thanks unity for using CCW in your UI...). This is my final solution:

Code Unity C#:

    var mapRect = MapContentTransform.rect;

    // Canvas Space Map Center
    var mapPositionCenter = CanvasRectTransform.InverseTransformPoint(MapContentTransform.position);
    mapPositionCenter.x += CanvasRectTransform.rect.width / 2.0f;
    mapPositionCenter.y += CanvasRectTransform.rect.height / 2.0f;
    // Mouse Position in Canvas Space
    Vector2 mousePositionCanvasSpace = CalculateScreenspacePositionCanvasSpace(new Vector2(Input.mousePosition.x, Input.mousePosition.y));

    // Rotation angle of the Map in Radians
    float theta = -(MapContentTransform.eulerAngles.z - 360.0f) * Mathf.Deg2Rad;

    var rotatedX = ((mousePositionCanvasSpace.x - mapPositionCenter.x) * Mathf.Cos(theta) -
                   (mousePositionCanvasSpace.y - mapPositionCenter.y) * Mathf.Sin(theta)) + mapRect.width / 2.0f;


    var rotatedY = ((mousePositionCanvasSpace.y - mapPositionCenter.y) * Mathf.Cos(theta) +
                   (mousePositionCanvasSpace.x - mapPositionCenter.x) * Mathf.Sin(theta)) + mapRect.height / 2.0f;

Thank you Kyy13!

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$
// let <x ,y > be the position of the mouse in canvas space coordinates
// let <x',y'> be the position of the mouse in local space coordinates
// let <x0,y0> be the origin of local space in canvas space coordinates
// let sx be the ratio of canvas space to local space coordiantes in the x direction
// let sy be the ratio of canvas space to local space coordinates in the y direction
// let theta be the CW rotation of local space with respect to canvas space

x' = (x-x0)*sx*cos(theta) - (y-y0)*sy*sin(theta)
y' = (y-y0)*sy*cos(theta) + (x-x0)*sx*sin(theta)

// to specify the center of the local space bounding rectangle instead of the origin <x0,y0>,
// we need to express <x0,y0> in terms of <xc,yc>

// let <xc,yc> be the center of the local space bounding rectangle in canvas space coordinates
// let halfw be half the width of the local space bounding rectangle
// let halfh be half the height of the local space bounding rectangle

x0 = xc - halfw*cos(theta) - halfh*sin(theta)
y0 = yc - halfh*cos(theta) + halfw*sin(theta)

// if you want positive theta to represent CCW rotation,
// then we need to replace theta with -theta,
// and we can use the following properties to reduce the equations
// cos(-x) = cos(x)
// sin(-x) = -sin(x)
// the new equations are:

x0 = xc - halfw*cos(theta) + halfh*sin(theta)
y0 = yc - halfh*cos(theta) - halfw*sin(theta)

x' = (x-x0)*sx*cos(theta) + (y-y0)*sy*sin(theta)
y' = (y-y0)*sy*cos(theta) - (x-x0)*sx*sin(theta)

HTML5 example

<!DOCTYPE html>
<html>
<body>

<!-- main window -->
<canvas id = "canvas" width = "300" height = "300"></canvas>

<!-- local rectangle space -->
<canvas id = "rect" width = "100" height = "50"></canvas>

<script>
    // get object handles
    var canvas = document.getElementById("canvas");
    var canvas_rect = document.getElementById("rect");
    var ctx = canvas.getContext('2d');
    var ctx_rect = canvas_rect.getContext('2d');

    // set origin to lower left
    ctx.translate(0, canvas.height);
    ctx.scale(1, -1);
    ctx_rect.translate(0, canvas_rect.height);
    ctx_rect.scale(1, -1);

    // inner rect param
    var x0 = 120;
    var y0 = 120;
    var theta = Math.PI/5;
    var sx = 1;
    var sy = 1;

    /* functions */

    // update images
    function render(coord)
    {
        // fill canvas with color
        ctx.fillStyle = "#CCCCCC";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx_rect.fillStyle = "#666666";
        ctx_rect.fillRect(0, 0, canvas_rect.width, canvas_rect.height);
        ctx.fillStyle = "#000000";
        ctx_rect.fillStyle = "#000000";

        // draw local rect on canvas
        ctx.beginPath();
        ctx.moveTo(x0,y0);
        ctx.lineTo(x0 + canvas_rect.width * Math.cos(theta), y0 - canvas_rect.width * Math.sin(theta));
        ctx.lineTo(x0 + canvas_rect.width * Math.cos(theta), y0 - canvas_rect.width * Math.sin(theta));
        ctx.lineTo(x0 + canvas_rect.width * Math.cos(theta) + canvas_rect.height * Math.sin(theta), y0 - canvas_rect.width * Math.sin(theta) + canvas_rect.height * Math.cos(theta));
        ctx.lineTo(x0 + canvas_rect.height * Math.sin(theta), y0 + canvas_rect.height * Math.cos(theta));
        ctx.lineTo(x0,y0);
        ctx.stroke();

        // draw coordinate on canvas
        drawCoord(coord, ctx);

        // coordinate in local rectangle space
        var c_rect = canvas2rect(coord[0], coord[1]);

        // draw coordinate in local space
        drawCoord(c_rect, ctx_rect);
    }

    // get coordinate when mouse is moved
    canvas.onmousemove = function(e)
    {
        var rect = e.target.getBoundingClientRect();
        render([e.clientX - rect.left, canvas.height - e.clientY + rect.top]);
    }

    // draw coordinate
    function drawCoord(coord, context)
    {
        context.fillRect(coord[0],coord[1],4,4);
    }

    // canvas coordinates to rect coordinates
    function canvas2rect(x, y)
    {
        var xPrime = (x - x0) * sx * Math.cos(theta) - (y - y0) * sy * Math.sin(theta);
        var yPrime = (y - y0) * sy * Math.cos(theta) + (x - x0) * sx * Math.sin(theta);
        return [xPrime, yPrime];
    }

</script>
</body>
</html>
\$\endgroup\$
7
  • \$\begingroup\$ I get most of the formula but the sx and sy put me off. I tried to test the solution but it doesn't work, the scaling seems weird. See my main post for an image and code. \$\endgroup\$
    – TriNityGER
    Commented Aug 13, 2018 at 12:10
  • \$\begingroup\$ Are the local coordinates of your rect actually scaled? try sx=sy=1 \$\endgroup\$
    – Kyy13
    Commented Aug 13, 2018 at 17:40
  • \$\begingroup\$ X, X0, Y, Y0 are all in the same frame of reference. Therefore I assume there is no further scaling needed. Just as a note: I did test it using a value of 1. Got even weirder results \$\endgroup\$
    – TriNityGER
    Commented Aug 13, 2018 at 17:49
  • \$\begingroup\$ I will udpate my answer with some code I wrote in HTML that will help debug your problem. Move your mouse over the local rectangle on the canvas to see how the coordinate is projected into local space. \$\endgroup\$
    – Kyy13
    Commented Aug 13, 2018 at 18:48
  • \$\begingroup\$ make sure that the theta you use in the equation is clockwise rotation \$\endgroup\$
    – Kyy13
    Commented Aug 13, 2018 at 18:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .