Legacy GM Getting Nearest Point of another object [Solved!]

AnalogF6

Member
EDIT: SOLVED! Great thanks to FlyingSaucerInvasion, you're one First Contact scenario I wouldn't mind!

Link to comment with a portable solution usable as a script:

https://forum.yoyogames.com/index.p...t-of-another-object-solved.64338/#post-385711




I am attempting to draw a line between a circle's center and the edge of another object.

Unfortunately, the code below will draw lines around the circle calling the action, but doesn't seem to detect the collision along the line. If I don't //comment out the "if collision_line(x,y,cx,cy,obj_box,false,false) = true", it just draws nothing.

An overview of the code below: the for loop checks 360 degrees at the distance between the circle and box. Collision_line_point gets the point of contact (in both directions), which is used to get the points between which to draw the lines. This is color-coded to show 360 red lines when no collision is detected, and to do the same with green when a collision is detected (and to stop testing, hence i=360).


Code:
var circle_self = self
if instance_exists(obj_box)
{
    with obj_box
    {
        var dist = distance_to_object(obj_circle)+16
        draw_set_color(c_black)
        draw_text(x,y,string(dist))
        for (i=0; i<360; i+=1)
        {
            var cx = (circle_self).x+lengthdir_x(dist,i)
            var cy = (circle_self).y+lengthdir_y(dist,i)
            draw_set_color(c_red)
            with (obj_circle)
            if collision_line(x,y,cx,cy,obj_box,false,false) = true
            {
                i = 360
                draw_set_color(c_green)
                draw_text(mouse_x+5,mouse_y+5,"Collision True")
                var box_edge = collision_line_point(x,y,cx,cy,obj_box,true,true)
                var box_edge_x = box_edge[1]
                var box_edge_y = box_edge[2]
                var circle_edge = collision_line_point(cx,cy,x,y,obj_circle,true,true)
                var ciredge_x = circle_edge[1]
                var ciredge_y = circle_edge[2]
                draw_line(ciredge_x,ciredge_y,box_edge_x,box_edge_y)
            }
            else
                {
                    draw_line(x,y,cx,cy)
                    draw_text(mouse_x+5,mouse_y+5,"Collision False")
                }
        }
       
    }
}
draw_self()

Collision_line_point is below:

Code:
/// collision_line_point(x1, y1, x2, y2, obj, prec, notme)
/*returns an array with following points
r[0] = instance collided with
r[1] = x of point of collision
r[2] = y of point of collision

*/

var x1 = argument0;
var y1 = argument1;
var x2 = argument2;
var y2 = argument3;
var qi = argument4;
var qp = argument5;
var qn = argument6;
var rr, rx, ry;
rr = collision_line(x1, y1, x2, y2, qi, qp, qn);
rx = x2;
ry = y2;
if (rr != noone) {
    var p0 = 0;
    var p1 = 1;
    repeat (ceil(log2(point_distance(x1, y1, x2, y2))) + 1) {
        var np = p0 + (p1 - p0) * 0.5;
        var nx = x1 + (x2 - x1) * np;
        var ny = y1 + (y2 - y1) * np;
        var px = x1 + (x2 - x1) * p0;
        var py = y1 + (y2 - y1) * p0;
        var nr = collision_line(px, py, nx, ny, qi, qp, qn);
        if (nr != noone) {
            rr = nr;
            rx = nx;
            ry = ny;
            p1 = np;
        } else p0 = np;
    }
}
var r;
r[0] = rr;
r[1] = rx;
r[2] = ry;
return r;
Any ideas?
 
Last edited:
Can you clarify the problem for me? You are dealing with collisions between a circle and obj_box? Are there more than one instance of obj_box? Is the box a rectangle aligned with the world axes? You are drawing 360 lines, is that because you need 360 lines to be visible, or is that just for testing? Are you trying to get the point on the box that is nearest to the circle's center?
 

AnalogF6

Member
I am doing all of this for testing right now. Trying to solve a problem for explosions in another project.

This is located on the draw event of the circle object. There is one of both object, but eventually the code will need to support an arbitrary number of both, amd the rectangle will need to be at arbitrary angles.

Ultimately, yes, just need the closest point between any given instances.
 
Okay. Let me know if I've got this straight. You have a point, the center of a circle. And you want to know the which point on a rectangle (not axis-aligned) is nearest to that point. Do you need to know which rectangle instance has a point nearest to the circle center, or do you need to know the nearest point on every rectangle instance? Does a point have to be within the radius of the circle to be a valid nearest point?
 

AnalogF6

Member
You got it. Eventually this will be used for physics calculations for vaguely rectangular objects affected by circular explosions.

And this needs to be applicable to all rectangles in an area (a radius). Hopefully just once point per rectangle.
 
If your project will work well with the box2d physics system, it is something you might want to look into using.

Anyway, finding the nearest point on your rectanges. I've made a few assumptions here. The main assumption is that the size and proportion and orientation of your rectangles can be described according to their sprite and image properties, sprite_xoffset, sprite_yoffset, sprite_width, sprite_height, and image_angle.

So what's going on here is that for each obj_box, the position of the circle's center is transformed into the rectangle's space. Then to get the nearest point on the rectangle in the rectangle's space, the circle's (transformed) position is clamped between the sides of the rectangle. Once that is done, the nearest point is transformed back into world space. (x3,_y3) is that final point. I don't know if you need to check whether the point is within the radius of the circle, but I included that in the example just in case. A couple of optimization suggestions are included in the comments.

Code:
var _x = circle_x,  //should be circle center x
var _y = circle_y;  //should be circle center y
var _r = radius;
var _x1, _y1, _x2, _y2, _x3, _y3, _d;
with (obj_box) {
    //_c and _s can be precomputed if rectangle has a fixed image_angle
    //or optimized by recomputing it just once when image_angle changes.
    var _c = dcos(image_angle);
    var _s = dsin(image_angle);
    //Also, parts in parentheses on next two lines
    //can be precomputed if rectangle has a fixed image_angle
    //or optimized by recomputing it just once when image_angle changes.
    //circle center (_x,_y), into rectangle's space
    _x1 = _x * _c + _y * -_s + ( - x * _c - y * -_s );
    _y1 = _x * _s + _y *  _c + ( - x * _s - y *  _c );
    //nearest point on rectangle to circle center, in rectangle's space
    _x2 = clamp( _x1, -sprite_xoffset, sprite_width-sprite_xoffset);
    _y2 = clamp( _y1, -sprite_yoffset, sprite_height-sprite_yoffset);
    //does point need to be within a radius?
    _d = point_distance(_x1,_y1,_x2,_y2);
    if (_d < _r) {
        //if you need the closest point (_x2,_y2), back in world space:
        _x3 = x + _x2 *  _c + _y2 * _s;
        _y3 = y + _x2 * -_s + _y2 * _c;
    }
}
EDIT: Never mind the spr_cross that was being drawn at _x3,_y3. That was just for testing.

EDIT2: I wasn't totally sure whether all the lines you were computing had actual function, or if you were just trying to solve the problem that this solution addresses. If I've misunderstood your problem, appologies, and do let me know about it.
 
Last edited:

AnalogF6

Member
I actually started this with the box2d system, but couldn't reliably return a single point on a collision with a circle at the time it is created. If i create an explosion at a given position, where its radius is less than the distance to another object, it appears further away from that object so as to not appear in the room already overlapping, meaning the explosion occurs usually almost a full radius away from where it is supposed to. Only solution i can think of there is to start with the fixture as a circle with a radius of 0 (or 1) and destroy/recreate it with progressively larger radii. That seems computationally expensive and silly.

I will have to review your solution to try and get a better grasp of it. In the end I will be using the solution to create a force at the point, directed away from the circle (explosion). Because boxes will have different lengths, I can't just direct based on center of mass, othewise explosions wont create rotation.
 
I didn't mean to suggest the physics system to detect the nearest point, just that it will make the rest of physics stuff easier.

And I kind of suspect applying force just to the nearest point is not realistic, but I haven't spent a lot of time thinking about it.
 

AnalogF6

Member
Ah, gotcha.

But I think it is more realistic than Center of Mass. Imagine a rectangle 30 units long with a low density. The explosion occurs in a 2 unit radius, aligned with the very last unit-length of the rectangle, at a distance of 1 unit. It should push against the end of the rectangle, inparting ritation and very little sliding.
 
Actually, linear acceleration should be independent of the angular acceleration. You'd think not, but it's true. However, yes, it makes sense that it would cause it to rotate in this case. Applying force just to the nearest point might be good enough at close range, but I wonder if it won't cause strange things to happen when the explosion is farther away.
 

AnalogF6

Member
I GREATLY appreciate your help.

I reviewed your code, brushing up on my Trigonometry in the process, and once I got a firm grasp on how it worked, I made it into a function I could easily reuse.

My end result:

Circle Draw Event:
Code:
draw_self()

draw_set_color(c_red)

var xx = x
var yy = y

if instance_exists(obj_box)

with (obj_box)
{
    var r = instance_point_nearest(xx,yy,400)
    if r[0] = true
    {
        draw_line(xx,yy,r[1],r[2])
    }
}
instance_point_nearest()
Code:
///instance_point_nearest(x,y,[radius])

var _x = argument0;
var _y = argument1;
var calculate;
var r;
var _r
var _x1, _y1, _x2, _y2, _x3, _y3, _d;


if(argument_count > 2)
{
    _r = argument2;
}
else
    _r = false;

var _c = dcos(image_angle);
var _s = dsin(image_angle);

_x1 = _x * _c + _y * -_s + ( - x * _c - y * -_s );
_y1 = _x * _s + _y *  _c + ( - x * _s - y *  _c );

_x2 = clamp( _x1, -sprite_xoffset, sprite_width-sprite_xoffset);
_y2 = clamp( _y1, -sprite_yoffset, sprite_height-sprite_yoffset);

_d = point_distance(_x1,_y1,_x2,_y2);

if _r = false
{
    calculate = true;
}
else
{
    if (_d < _r)
    {
        calculate = true;
    }
    else
    {
        calculate = false;
        r[0] = false;
        r[1] = false;
        r[2] = false;
        return r;
    }
}
if calculate = true
{
    _x3 = x + _x2 *  _c + _y2 * _s;
    _y3 = y + _x2 * -_s + _y2 * _c;
    r[0] = true;
    r[1] = _x3;
    r[2] = _y3;
    return r;
}
else
{
    r[0] = false;
    r[1] = false;
    r[2] = false;
    return r;
}
Here's how it looks in action:




In the end, I think I'll end up dividing force applied by an explosion between that point and the center of mass, maybe adjusted depending on the difference in angle between the two points. That way it will apply force more evenly across the face of an object nearby and not impart spin when it is close enough to it to not be expected. For example, the rectangle directly to the right of the circle on the screenshot would probably spin, but if it had force applied to the center of mass relatively close to the amount of force applied to the corner, it probably wouldn't.
 

AnalogF6

Member
For the record, that worked flawllessly. Applying the force of an explosion to the center of mass makes objects slide away without any change in orientation. Applying force to just the nearest point frequently makes stuff spin instead of being pushed at all. But applying force to both results in a satisfyingly realistic result.
 
Top