GMLscripts.com

Discuss and collaborate on GML scripts
Invert

You are not logged in.

#1 2009-08-02 18:01:18

RaiSoleil
Member
Registered: 2009-08-02
Posts: 16

An alternative, flexible range_finder() script

After some discussion with icuurd12b42, I think I've cut this script down as much as I can. It preforms much the same function as range_finder except it finds the x, y, distance, and instance of collision as well as being faster. The biggest jump comes from that point_distance() idea of icuurd's, but to solve his inaccuracy problem I've made the script dependent on the variable sprite_radius, which holds the maximum distance from the origin to the four bbox points. Problem is, it either has to be pre-calculated in the instance or checked as below in the script, which wastes time. Furthermore, it's unlikely this radius check will work for all the possible sprite/mask transformations. An alternative, less local-variable-dependent solution would be preferable. So far all my ideas have either had loophole situations or slowed things down.

Speed-wise, the script is great; its execution time is dependent on the distance to the first object found by collision_line() instead of the total length. In comparison to range_finder, the script is up to twice as fast with nearby objects and a large (10000+) range; with a smaller range, the speed is still a noticeable percentage better. Testing 65536 beams of 65536 range in a room full of concave-shaped objects got about 0.062 to 0.065 ms per call on my computer. A resolution of 4 (~2px from perfect) got about 0.055 ms. A possible addition here is that once within a certain range (adjustable?), the script should only check its current target instance in collision_line() instead of all the objects of the given type.

Suggestions always appreciated. Including, perhaps, that I'm a little psyco over a few thousandths of a millisecond.

In other news, I did some testing of "x>>1" vs "x*0.5" vs "x/2". Apparently . . . division and multiplication are essentially the same speed. And since "x>>1" actually forces you to use "x = x>>1", bitshift ends up the slowest. Compared to "x = x/2", bitshift is almost as slow. Annoying.

Expand/*
**  Usage:
**      scr_collision_beam(x,y,range,dir,object,prec,notme,res)
**
**  Given:
**      x,y     position in room
**      range   farthest distance to check
**      dir     direction of beam in degrees
**      object  which instance or object to look for, or keyword all
**      prec    if true, use precise collision checking
**      notme   if true, ignore the calling instance
**      res     maximum deviation from perfect
**
**  Returns:
**      Instance id or -4 if none
**
**  Sets:
**      cx,cy   point of collision 
**      cd      distance to point of collision
**
**  Notes:
**      Computes answer in log2(first_found_distance)-log2(res) collision checks
**      Answer is within res/2 pixels of perfect
**      Creates local variable "sprite_radius" for the first collision_line() instance if it does not exist already
**
**  GMLScripts.com
*/

{
    var x0, y0, x1, y1, lx, ly, d, t, t2, td, res;
    x0 = argument0;
    y0 = argument1;
    x1 = x0;
    y1 = y0;
    d = argument2;
    lx = lengthdir_x(1,argument3);
    ly = lengthdir_y(1,argument3);
    t2 = -4;
    td = d;
    res = argument7;
    if (res <= 0) res = 1; 
    
    t = collision_line(x0,y0,x0+lx*d,y0+ly*d,argument4,argument5,argument6);
    
    if t {
        td = 0;
        with t { // Needs an alternative
            if !variable_local_exists(sprite_radius) {
                                sprite_radius = sqrt(sqr(max(bbox_right-sprite_xoffset-x,x+sprite_xoffset-bbox_left)) +
                                sqr(max(bbox_bottom-sprite_yoffset-y,y+sprite_yoffset-bbox_top)))*0.5;
            }
        }
        
        d = min(d,point_distance(x0,y0,t.x,t.y)+t.__sprite_radius);
        
        while (d>res) {
            // at what point should it switch to only checking one instance?
            // When it hasn't found a new instance in 3 checks? Or the last 3 (res?) checks of the loop?
            d *= 0.5;
            x1 += lx*d;
            y1 += ly*d;
            
            t = collision_line(x0,y0,x1,y1,argument4,argument5,argument6);
            
            if t {
                x1 = x0;
                y1 = y0;
                t2 = t;
            }
            else {
                x0 = x1;
                y0 = y1;
                td += d;
            }
        }
        
        cx = x1+lx*res*0.5;
        cy = y1+ly*res*0.5;
        cd = td;
    }
    else {
        cx = argument0 + lx*td;
        cy = argument1 + ly*td;
        cd = td;
    }
    
    return t2;
}

Offline

#2 2009-08-03 02:45:44

xot
Administrator
Registered: 2007-08-18
Posts: 1,240

Re: An alternative, flexible range_finder() script

I'm a little confused. This script is riddled with obvious bugs, it won't even run. How did you possibly test it?

I patched the bugs and changed it to return distance, but I didn't find it to be faster than the existing script by any perceptible amount, at least in the tests I did with the only projects I have that make heavy use of that script. In extreme scenarios like you mention, it might be miles faster, but are those realistic scenarios?

With a little work, this could be a drop-in replacement for the range finder script. That would be ideal, but it would have to run at least as fast at the same level of precision. Otherwise, we should make a separate function. One thing I did notice is that at maximum precision it seems to provide a far more smooth and stable result.

Expand        with t { // Needs an alternative
            if !variable_local_exists(sprite_radius) {
                sprite_radius = sqrt(sqr(max(bbox_right-sprite_xoffset-x,x+sprite_xoffset-bbox_left)) +
                sqr(max(bbox_bottom-sprite_yoffset-y,y+sprite_yoffset-bbox_top)))*0.5;
            }
        }

This is madness. You can't create instance variables like that.

Is there a reason you can't use:

Expandvar sprite_radius;
sprite_radius = point_distance(t.bbox_left, t.bbox_top, t.bbox_right, t.bbox_bottom) / 2;

It doesn't provide the same answer, but I don't understand what the extra bits in your code are doing there.


Abusing forum power since 1986.

Offline

#3 2009-08-03 23:59:35

RaiSoleil
Member
Registered: 2009-08-02
Posts: 16

Re: An alternative, flexible range_finder() script

Oi, sorry. I'd attempted to reformat the code to more closely match the standards; apparently I also borked some things in the process.

The whole with() section is an even worse drag on the script (30% to 40% slower) than I thought it would be, having edited it without retesting.  It either needs to be forced in the creation code for colliding objects or replaced with something better. The code you posted isn't very accurate if the origin isn't centered. However, it gave me the idea you see below - a *fraction* slower now closer to your code, and slightly faster now back to "slightly slower" (because I fail at variable referencing) than the point_distance(x0,y0,t.x,t.y)+t.sprite_radius by itself (surprisingly), but it doesn't require an instance variable and is much, much faster than the with() section.

And some things that don't seem to help speed things up at all, for reasons I can only attribute to GM's own idiosyncratic execution speeds:
- Setting precise collision checking to 0 for both collision_line()s
- Restricting the line to only collide with the last found target after a certain time - the if/else check countered any increase.

Expand{
    var x0, y0, lx, ly, d, t, t2, res;
    x0 = argument0;
    y0 = argument1;
    d = argument2;
    lx = lengthdir_x(1,argument3);
    ly = lengthdir_y(1,argument3);
    t2 = -4;

    res = argument7;
    if (res <= 0) res = 1;

    t = collision_line(x0,y0,x0+lx*d,y0+ly*d,argument4,argument5,argument6);

    if t {
        var x1, y1, td;
        d =  point_distance(x0,y0,mean(t.bbox_left,t.bbox_right),mean(t.bbox_top,t.bbox_bottom))+t.sprite_width+t.sprite_height;
        x1 = x0;
        y1 = y0;
        td = 0;
        
        while (d>res) {
            d *= 0.5;
            x1 += lx*d;
            y1 += ly*d;

            t = collision_line(x0,y0,x1,y1,argument4,argument5,argument6);

            if t {
                x1 = x0;
                y1 = y0;
                t2 = t;
            }
            else {
                x0 = x1;
                y0 = y1;
                td += d;
            } 
        }

        cx = x1+lx*res*0.5;
        cy = y1+ly*res*0.5;
        cd = td;
    }
    else {
        cx = argument0 + lx*d;
        cy = argument1 + ly*d;
        cd = d;
    }

    return t2;
}

[Edit:] switched out some code (faster now), split variable assignments

Last edited by RaiSoleil (2009-08-04 10:06:42)

Offline

Board footer

Powered by FluxBB