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
\$\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\$
  • \$\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 Aug 13 '18 at 12:10
  • \$\begingroup\$ Are the local coordinates of your rect actually scaled? try sx=sy=1 \$\endgroup\$ – Kyy13 Aug 13 '18 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 Aug 13 '18 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 Aug 13 '18 at 18:48
  • \$\begingroup\$ make sure that the theta you use in the equation is clockwise rotation \$\endgroup\$ – Kyy13 Aug 13 '18 at 18:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.