GMLscripts.com

Discuss and collaborate on GML scripts
Invert

You are not logged in.

#1 2009-06-28 15:12:36

GMLdisaster
Member
Registered: 2009-06-28
Posts: 2

Bullets/rays vs rectangles and circles (need ok from xot and EyeGuys)

Firstly, I have used the difference between angles script written by xot and EyeGuy within the rayCircle script (and lineCircle), so please can I upload it xot and EyeGuy? Or send you a PM or something?

...
else if abs(((((dir-ldir)mod 360)+540)mod 360)-180)>90  { //angle difference script by EyeGuy and xot [[http://www.gmlscripts.com/script/angle_difference]]
    //is facing wrong way, wont be a collision
    return false;
}
...

Hope you see me! huh

For 2-d shooters and the like, that have very fast, very small moving projectiles. It's not practical to iterate down the whole line doing collision checks, and the built in functions don't return exactly where the collision took place.

I have 3-4 scripts to upload:
-rayCircle gets the first (nearest to line origin) collision of a line with a circle
-rayRect is same thing for a rectangle
-bullet is a way of iterating through all of one type of object (or group of objects by using a parent) that all have circle or rectangles for collisions.
-the iffy 4th one is lineCircle which is the same as rayCircle with a few extra parameters to make it more flexible, but not as fast probably

They take x and y for the start of the line and circle centre/ top left and bottom right, a direction in degrees of the line and a couple of other tidbits.

It could probably do with some optimization and there are probably better ways to do it using areas of math I don't know... but it might be useful for someone, and I don't write anything useful very often. tongue

Also, I'm new here - so hi all, great site! biggrin

Offline

#2 2009-06-30 20:06:16

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

Re: Bullets/rays vs rectangles and circles (need ok from xot and EyeGuys)

Welcome to the forum, glad you like the site. All scripts on the GMLscripts.com website are free to use for any purpose. Scripts posted in the forum belong to their authors unless stated otherwise. Your scripts sound interesting, by all means post them.


Abusing forum power since 1986.

Offline

#3 2009-06-30 20:49:36

GMLdisaster
Member
Registered: 2009-06-28
Posts: 2

Re: Bullets/rays vs rectangles and circles (need ok from xot and EyeGuys)

Awesome biggrin

In that case I'll put them up. I'm pretty sure I got them all in the right formatting and whatnot.

Rays against rectangles... the simplest way I could think to do it, checks the nearest 2 sides of the rectangle in the right direction. A bit typing heavy but as the typing is done now! tongue

Expand/*
**  script_rayRect(x0,y0,dir,x1,y1,x2,y2,get)
**
**  script returns if a collision is made between a ray and a rectangle
**  can also return collision point coordinates to variables resultX and resultY
**
**  x0 and y0 are line start point coordinates, dir is line angle between 0 and 360 degrees
**  x1,y1,x2,y2 define the rectangle, script only works if x2>x1 and y2>y1
**  get determines whether the collision coordinates are returned or not
**
**  requires resultX and resultY if returning collision point coordinates
**
**  GMLscripts.com
*/

{
    //arguments named
    var x0,y0,dir,x1,y1,x2,y2,get;
    x0=argument0;
    y0=argument1;
    dir=argument2;
    x1=argument3;
    y1=argument4;
    x2=argument5;
    y2=argument6;
    get=argument7;
    //
    //horizontal checks
    var ans;
    if x0<x1    { //left check
        if dir<90 or dir>270    {
            ans=floor(y0+tan(degtorad(dir))*(x0-x1)); //corresponding y coordinate
            if ans>=y1 and ans<=y2  {//the y coordinate is in range
                if get=true {resultX=x1;resultY=ans} 
                return true; //collision made left
            }
        }
    }
    else if x0>x2   { //right check
        if dir>90 and dir<270   {
            ans=floor(y0+tan(degtorad(dir))*(x0-x2));
            if ans>=y1 and ans<=y2  {
                if get=true {resultX=x2;resultY=ans}
                return true; //collision made right
            }
        }
    }
    else if y0>=y1 and y0<=y2   { //inside (relevant line dirs are other way inside)
        //horizontal checks
        if dir<270 and dir>90   { //left check
            ans=floor(y0-tan(degtorad(dir))*(x1-x0));
            if ans>=y1 and ans<=y2  {
                if get=true {resultX=x1;resultY=ans}
                return true; //collision made left (inside)
            }
        }
        else if dir<90 or dir>270   { //right check
            ans=floor(y0-tan(degtorad(dir))*(x2-x0));
            if ans>=y1 and ans<=y2  {
                if get=true {resultX=x2;resultY=ans}
                return true; //collision made right (inside)
            }
        }
        //vertical checks
        if dir>0 and dir<180    { //top check
            ans=floor(x0+tan(degtorad(dir+90))*(y1-y0));
            if ans>=x1 and ans<=x2  {
                if get=true {resultX=ans;resultY=y1}
                return true; //collision made top (inside)
            }
        }
        else if dir>180 and dir<360 { //bottom check
            ans=floor(x0+tan(degtorad(dir-90))*(y2-y0));
            if ans>=x1 and ans<=x2  {
                if get=true {resultX=ans;resultY=y2}
                return true; //collision made bottom (inside)
            }
        }
    }

    //vertical checks
    if y0<y1    { //top check
        if dir>180 and dir<360  {
            ans=floor(x0-tan(degtorad(dir+90))*(y0-y1));
            if ans>=x1 and ans<=x2  {
                if get=true {resultX=ans;resultY=y1}
                return true; //collision made top
            }
        }
        else    {
            return false; //no collision
        }
    }
    else if y0>y2   { //bottom check
        if dir<180 and dir>0    {
            ans=floor(x0-tan(degtorad(dir-90))*(y0-y2));
            if ans>=x1 and ans<=x2  {
                if get=true {resultX=ans;resultY=y2}
                return true; //collision made bottom
            }
        }
        else    {
            return false; //no collision
        }
    }
    return false;
}

Here is the ray vs circle, a teensy bit shorter thankfully! A lot of commenting, otherwise I would forget what I was doing. Not too crazy though unsure

Expand/*
**  script_rayCircle(x0,y0,dir,xc,yc,radius,get)
**  
**  returns true if collision is made between ray and circle
**  can return coordinates to resultX, resultY
**  
**  x0,y0 start point of line, dir is line direction in degrees
**  xc,yc are center of circle, radius is obvious
**  if get is true coordinates returned to resultX and resultY
**  
**  requires variables resultX and resultY if coordinates are to be returned
*/

{
    //arguments named
    var x0,y0,dir,xc,yc,radius,get;
    x0=argument0;
    y0=argument1;
    dir=argument2;
    xc=argument3;
    yc=argument4;
    radius=argument5;
    get=argument6;

    //temporary variables
    var perpangle,ldir,lang,lhyp,ldis,xi,yi,psq,rsq,swap,offset;
    
    //angle perpendicular to line, sounds cool, radius squared, useful throughout
    perpangle=dir+90;
    rsq=sqr(radius);
    //find intersection point between line and perpendicular, this is in middle of results along line
    //uses trig on a triangle circle center C, line start S, line intersect I with a right angle at CIS
    //inside angle at ISC
    ldir=point_direction(x0,y0,xc,yc);
    lang=ldir-dir;
    lhyp=point_distance(xc,yc,x0,y0); //hypotenuse length
    
    //if ray is pointing away from circle, or inside circle, need to swap answers round
    //if inside circle, then will definately have a collision, if ray might stop here
    if sqr(lhyp)<=rsq   { //inside
        swap=-1;
        if get=false    { //if only want number of collisions can stop here
            return true;   
        }
    }
    else if abs(((((dir-ldir)mod 360)+540)mod 360)-180)>90  { //angle difference script by EyeGuy and xot [[http://www.gmlscripts.com/script/angle_difference]]
        //is facing wrong way, wont be a collision
        return false;
    }
    else    {
        swap=1;
    }
    
    //continue to find the intersect point
    ldis=cos(degtorad(lang))*lhyp; //adjacent length (line start to point)
    xi=x0+lengthdir_x(ldis,dir);
    yi=y0+lengthdir_y(ldis,dir);

    //needed to get number of solutions
    psq=sqr(xi-xc)+sqr(yi-yc); //sqr distance of point to circle center

    //if point is outside circles radius, can stop here
    if psq>rsq  {
        return false;
    }
    else if get=false   {
        return true;
    }
        
    //work out number of solutions and return collision point
    //answers are the line intersect -/+ offset along the line
    //offset is opposite in triangle CSI, where S is solution
    if psq=rsq  {
        //one solution
        resultX=xi;
        resultY=yi;
        return true;
    }
    else if psq<rsq {
        //two solutions (but only return first one)
        offset=sqrt(sqr(radius)-psq);
        resultX=xi-lengthdir_x(offset,dir)*swap;
        resultY=yi-lengthdir_y(offset,dir)*swap;
        return true;
    }
}

And this one basically just iterates through the specified group of objects to find the nearest hit...

Expand/*
**  script_bullet(x0,y0,dir,length,object,w,h,method)
**
**  returns if and where a ray first touches an instance of object object, and which instance this is
**  good for fast moving point projectiles against circles and axis aligned rectangles, but beware iterations
**  instead of calling this script many times for different object types, give all obstacles the same parent object
**
**  x0 and y0 are line start point coordinates, dir is line angle between 0 and 360 degrees
**  length is the line length, use -1 for an infinite ray
**  object is the object type to be checked
**  w and h are width and height for rectangles (objects x,y used as top left)
**  for circles w is the radius (objects x,y used as center)
**  method decides how to check the shape, 0 for rectangle, 1 for circle or 2 for bounding box
**  if method uses bounding box, the object being checked must have a sprite or mask
**
**  returned value is if a collision is made
**  requires resultX and resultY variables to return collision points coordinates
**  requires resultInst to return which instance was hit
**  requires script_rayCircle and script_rayRect to perform checks
**
**  GMLscripts.com
*/

{
    //arguments named
    var x0,y0,dir,length,object,x1,y1,x2,y2,method;
    x0=argument0;
    y0=argument1;
    dir=argument2;
    length=argument3;
    object=argument4;
    w=argument5;
    h=argument6;
    method=argument7;
    //temporary variables
    var n,dist,collision,i,inst,hit,newDist,poorUnfortunate,X,Y;
    
    n=instance_number(object); //only have to count once
    dist=length; //duplicate variable because this changes between iterations and length must not
    collision=false; //no collisions yet
    for(i=0;i<n;i+=1)   {
        inst=instance_find(object,i);
        //checks if there is a hit for this inst using appropriate check
        hit=false;
        if method=0
            hit=script_rayRect(x0,y0,dir,inst.x,inst.y,inst.x+w,inst.y+h,true);
        else if method=1
            hit=(script_rayCircle(x0,y0,dir,inst.x,inst.y,w,1,true)>0);
        else
            hit=script_rayRect(x0,y0,dir,inst.bbox_left,inst.bbox_top,inst.bbox_right,inst.bbox_bottom,true);
        //if there is a hit, is it closest?
        if hit=true {
            newDist=sqr(resultX-argument0)+sqr(resultY-argument1);
            if dist<0 or newDist<dist   { //keep track of closest hit
                dist=newDist;
                X=resultX;
                Y=resultY;
                poorUnfortunate=inst;
            }
            if collision=false  { //there has been a collision now
                collision=true;
            }
        }
    }
    //return coordiantes of first collision
    if collision=true   {
        resultX=X;
        resultY=Y;
        resultInst=poorUnfortunate;
    }
    return collision;
}

I'll put up line vs circle too if anyone thinks it'd be more useful than ray circle (ray circle is the same script with some clutter, and a little functionality, taken out biggrin)
      script_lineCircle(x0,y0,dir,xc,yc,radius,points,isray)
You can specify to use a ray instead of a line, and it also returns no, closest, furthest or all (closest first, then furthest) intersections with the circle being tested.

This post is plenty long as it is for now though, and I'll want to check these are OK first. rolleyes

edit: If these are OK I will upload them for the main site? Any suggestions first though?

Last edited by GMLdisaster (2009-07-03 12:49:37)

Offline

Board footer

Powered by FluxBB