Point in rotated rectangle?

MeBoingus

Member
Hi all!

I'm busy working on a simple editing program that allows the user to move and rotate sprites. Long story short, I'm drawing a box around these sprites using the following code:

GML:
var _left = -sprite_xoffset;
var _top = -sprite_yoffset;
var _right = sprite_width + _left;
var _bottom = sprite_height + _top;
var _c = dcos(image_angle);
var _s = dsin(image_angle);
var _x0 = x + _left * _c +  _top * _s;
var _y0 = y + _left * -_s + _top * _c;
var _x1 = x + _right * _c +  _top * _s;
var _y1 = y + _right * -_s + _top * _c;
var _x2 = x + _right * _c +  _bottom * _s;
var _y2 = y + _right * -_s + _bottom * _c;
var _x3 = x + _left * _c +  _bottom * _s;
var _y3 = y + _left * -_s + _bottom * _c;

draw_line_color(_x0, _y0, _x1, _y1, c_lime, c_lime);
draw_line_color(_x1, _y1, _x2, _y2, c_lime, c_lime);
draw_line_color(_x2, _y2, _x3, _y3, c_lime, c_lime);
draw_line_color(_x3, _y3, _x0, _y0, c_lime, c_lime);

And it's working a treat. My problem is that I now need to detect when this new, rotated rectangle is clicked.

Due to the way everything's being drawn, any of the 'position_meeting' or 'place_meeting'-style functions are out of the question, as the object that's drawing the sprite (it's actually drawing ALL of the sprites that can be altered, but I've simplified this code down to basic, built-in variables) doesn't have any form of collision mask, and simply cannot.

Any idea how I could detect if the mouse is within this specific area?
 

Joe Ellis

Member
I'd use the "distance_from_line" method: (derived from the "nearest_point_on_line" method:

GML:
function point_on_line(px, py, x1, y1, x2, y2) {

    var
    line_dx = x1 - x2,
    line_dy = y1 - y2,
    point_dx = px - x2,
    point_dy = py - y2;

    var
    ratio = clamp(dot_product(point_dx, point_dy, line_dx, line_dy) / dot_product(line_dx, line_dy, line_dx, line_dy), 0, 1)

    return [lerp(x2, x1, ratio), lerp(y2, y1, ratio)]
}
GML:
function distance_from_line(px, py, x1, y1, x2, y2) {

    var
    line_dx = x1 - x2,
    line_dy = y1 - y2,
    point_dx = px - x2,
    point_dy = py - y2;

    var
    ratio = clamp(dot_product(point_dx, point_dy, line_dx, line_dy) / dot_product(line_dx, line_dy, line_dx, line_dy), 0, 1)

    return point_distance(px, py, lerp(x2, x1, ratio), lerp(y2, y1, ratio))
}

And then something like this: (I use this in my program for getting the edge of the triangle the mouse is over that the mouse is nearest to, while also being within the mouse radius(25 pixels))

GML:
function mouse_get_edge() {

    var
    p1 = vertex_get_screen_pos(f[_face.vertex1]),
    p2 = vertex_get_screen_pos(f[_face.vertex2]),
    p3 = vertex_get_screen_pos(f[_face.vertex3]);

    var
    d1 = distance_from_line(mouse_x, mouse_y, p1[0], p1[1], p2[0], p2[1]),
    d2 = distance_from_line(mouse_x, mouse_y, p2[0], p2[1], p3[0], p3[1]),
    d3 = distance_from_line(mouse_x, mouse_y, p3[0], p3[1], p1[0], p1[1]);

    var d_min = min(d1, d2, d3);

    if d_min < global.mouse_radius
    {

    if d_min = d1
    {mouse_edge = [f[_face.vertex1], f[_face.vertex2]]}

    if d_min = d2
    {mouse_edge= [f[_face.vertex2], f[_face.vertex3]]}

    if d_min = d3
    {mouse_edge = [f[_face.vertex3], f[_face.vertex1]]}
    }
    else
    {mouse_edge = 0}

}
If you did this for the 4 edges of the rotated box it'd work. However for detecting which rectangle the mouse is over, I'd use something like this: (Which is more complicated, it involves setting up a surface of 1x1 pixels, and using id mapping. I'll explain further about this if you need me to, but for now I'll just post the code and you can decide whether it's worth it or not. You could do it in a simpler way by just looping through all the rectangles and asking if the mouse is inside it's bounding box, then if it is do the point on line checks. Yeah actually that should work fine, you probably don't need to bother with the method I'm about to post, I only use it cus it's works so well with 3d stuff)

GML:
function mouse_step_model() {

    if mouse_x != old_mouse_x
    || mouse_y != old_mouse_y
    {
    old_mouse_x = mouse_x
    old_mouse_y = mouse_y

    surface_set_target(global.s_m3d)
    draw_clear(c_black)
    mouse_get_normal(mouse_x, mouse_y)

    matrix_set(matrix_view, matrix_build_lookat(global.view_x, global.view_y, global.view_z,
    global.view_x + mouse_nx, global.view_y + mouse_ny, global.view_z + mouse_nz,
    global.xup, global.yup, global.zup))
    matrix_set(matrix_projection, mouse_projection)

    shader_set(shd_m3d_id_mdl)

    var l = global.current_file.meshes, mesh, i = 0, fl,
    uid = shader_get_uniform(shd_m3d_id_mdl, "u_id");
    repeat l[0]
    {
    mesh = l[++i]
    shader_set_uniform_f(uid, i)
    vertex_submit(mesh.idvbuffer, pr_trianglelist, 0)
    }
    shader_reset()
    surface_reset_target()
  
    buffer_seek(global.b_m3d, buffer_seek_start, 0)
    buffer_get_surface(global.b_m3d, global.s_m3d, buffer_surface_copy, 0, 0)
  
    mesh = buffer_peek(global.b_m3d, 2, buffer_u8)

    if mesh != 0
    {
    mesh = l[mesh]
    fl = mesh.faces;
    mouse_face = fl[buffer_peek(global.b_m3d, 0, buffer_u8) + (255 * buffer_peek(global.b_m3d, 1, buffer_u8))]
    }
    else
    {mouse_face = 0}

    }
}
 

Joe Ellis

Member
Oh my god, why is head so far up my\ why am I always thinking too complicatedly?
It's like if I had to pull a carrot out of the ground, I'd invent a trowel that can dig directly downwards and protract a metal sieve that safely brings it up to the surface...
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
From a post I wrote a few years ago:
Code:
function pointInRotatedRectangle(pointX, pointY,
    rectX, rectY, rectOffsetX, rectOffsetY, rectWidth, rectHeight, rectAngle
) {
    var relX = pointX - rectX;
    var relY = pointY - rectY;
    var angle = -rectAngle;
    var angleCos = dcos(angle);
    var angleSin = -dsin(angle);
    var localX = angleCos * relX - angleSin * relY;
    var localY = angleSin * relX + angleCos * relY;
    return localX >= -rectOffsetX && localX <= rectWidth - rectOffsetX &&
           localY >= -rectOffsetY && localY <= rectHeight - rectOffsetY;
}
 
Top