Convert Canvas Space point to local space of a rotated rect?

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:

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!

// 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>

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