Is player inside Lozenge Boundary (Math)

Started by Knox, Thu 19/04/2012 17:52:16

Previous topic - Next topic

Knox

I put this is advanced but Im not sure how easy/hard this really is.

Ive got a script that checks if  the player is inside a certain boundary, but its rectangular in shape:
Code: ags

function playerInsideBounds(int iMinX, int iMaxX, int iMinY, int iMaxY)
{
  //Display("**function playerInsideBounds...");
  return ((player.x >= iMinX && player.x <= iMaxX) && (player.y >= iMinY && player.y <= iMaxY));
}


Since Id like to check if a player is inside a diamond-shape boundary (isometric) without using hotspots or regions, what is the math I need to make sure the extra "triangles" get cut off of the rectangle so that the area is for a diamond (blue lozenge below) (Do I have to multiply the y coordinates by sin(30°), ~ 0.454 ?)

**EDIT**
(And would that same formula work for non-diamond rhombuses too, like the green zones in the above picture?)

--All that is necessary for evil to triumph is for good men to do nothing.

cat

I'd suggest you break down the calculation on those triangles to make it most versatile for other shapes. Then you can register an object for each triangle and look it up.

Khris

#2
(Like I mentioned in the other thread, I'd do all the calculations in a top down view, then turn the final coordinates into isometric ones. You'll get 90% less headaches that way.)

Since the calculation is slightly different for the green areas, I'll explain how to do it in a general way.

To find out whether a point is above or below a line, express the line by a function, insert the point's x coordinate, then compare the result against the point's y coordinate.

1) First, let's calculate the center: Xc = (Xmin + Xmax)/2;  Yc = (Ymin + Ymax)/2;

2) Let's look at the top-left line of the rhombus. Two points on the line are (Xmin, Yc) and (Xc, Ymax).
Thus, if the formula for the line is Y = m * X + t, m equals (Ymax - Yc)/(Xc - Xmin). In this case here though, we don't need to calculate this, m is simply sin(30°).
(The bottom-right line has the same m, and the other two lines' is -m.)

3) To get t, solve for t with a known point.
Yc = m * Xmin + t  =>  t = Yc - sin(30°) * Xmin

4) With t and m, let's put the point's X into the equation: Y = m * Xp + t.
If Yp > Y, the point is above the line.
If Yp = Y, it's on the line.
If Yp < Y, it's below the line.

5) Calculate the four t's, then check whether the point is above the lower lines and below the upper ones.


1a) Use a top-down view, do a rectangular check using your function, convert coords into iso.

Knox

Nice!

Ok, Im gonna try  that right now!

Just to be sure sure sure I understand your full post, method "1a" is for a perfect diamond-rhombus, the rest (steps 1 through 5) are for irregular rhombuses such as the green ones...right?

Thanks btw! :)
--All that is necessary for evil to triumph is for good men to do nothing.

Khris

No, 1a is just to show how much easier things get if you do all your calculations in a top-down space, then convert all coordinates to iso to display them.

1-5 applies to both blue and green.

Sorry for the confusion.

Knox

#5
Ok I  think I got it to work...here is what I did (I placed it in a BOOL function). Do you see any problems with this code? Also, (if you feel like it), what would be a nicer, more efficient and more elegant way of writing what I did?

Also, I placed this in my rep_exec...meaning it will constantly check the player's position in relation to where the "virtual rhombus region" is...and Im surely going to be making a crap load of them, so could  this be potentially too heavy to
run constantly? One of the main reasons I was thinking I could use this is to have pretty much an infinite amount of regions/hotspots/walkable areas. (It should work, right?)
Code: ags

bool InsideBounds_ISO(this Character*, int iMinX, int iMaxX, int iMinY, int iMaxY)
{
  int iXc; //Center X of rhombus area
  int iYc; //Center Y of rhombus area 
  int iXp = player.x;
  int iYp = player.y;

  int iY_TL; //Y on the TOP-LEFT tangeant
  int iY_TR; //Y on the TOP-RIGHT tangeant
  int iY_BL; //Y on the BOTTOM-LEFT tangeant
  int iY_BR; //Y on the BOTTOM-RIGHT tangeant

  int iX; //X on the tangeant
  float fSin30 = Maths.Sin(Maths.DegreesToRadians(30.0));
  int iSin30 = FloatToInt(fSin30, eRoundNearest);
  int iTL;
  int iTR;
  int iBL;
  int iBR;

  iXc = (iMinX + iMaxX)/2;  
  iYc = (iMinY + iMaxY)/2;

  iTL = iYc - iSin30 * iMinX;         //TOP LEFT LINE
  iTR = iYc - (-1*iSin30) * iMaxX;  //TOP RIGHT LINE
  iBL = iYc - (-1*iSin30) * iMinX;  //BOTTOM LEFT LINE
  iBR = iYc - iSin30 * iMaxX;         //BOTTOM RIGHT LINE

  iY_TL = iSin30 * iXp + iTL;
  iY_TR = (-1*iSin30) * iXp + iTR;
  iY_BL = (-1*iSin30) * iXp + iBL;
  iY_BR = iSin30 * iXp + iBR;
  if ((iYp <= iY_TL) && (iYp <= iY_TR) && (iYp >= iY_BL) && (iYp >= iY_BR))  return true;
  else return false;
}
--All that is necessary for evil to triumph is for good men to do nothing.

Khris

#6
Yours isn't going to work because you need the actual float value of the sinus; turning it into an int will yield 0 or 1, which corresponds to horizontal or vertical.

Regarding speed: AGS is reasonably fast when it comes to float calculations; even doing lots of them each loop shouldn't noticeably slow down AGS.
This is tested and working:
Code: ags
float m = -0.5;  // going from left corner towards top corner, 1 across corresponds to -0.5 down

bool InsideBounds_ISO(this Character*, int iX1, int iY1, int iX2, int iY2)
{
  // d is vector from X1;Y1 to X2;Y2
  float dx = IntToFloat(iX2 - iX1), dy = IntToFloat(iY2 - iY1);
  // a is vector from X1;Y1 to top corner
  // b is vector from X1;Y1 to bottom corner  (a + b = d)
  float bx = (m*dx - dy)/(2.0*m);
  float ax = dx - bx;
  float ay = m * ax, by = -m * bx;
  // express character coords using a & b
  // p is vector from X1,Y1 to character
  float px = IntToFloat(this.x - iX1), py = IntToFloat(this.y - iY1);
  float det = ay*bx - ax*by;
  // p = k*a + l*b
  float k = (bx*py - by*px)/det;
  float l = (ay*px - ax*py)/det;
  // => if both k & l are inside [0;1], character is inside parallelogram
  return (k >= 0.0 && k <= 1.0 && l >= 0.0 && l <= 1.0);
}

Note that this function doesn't use min and max values though but the coordinates of two corners (because stating only the four min & max values isn't sufficient to exactly define the shape of the parallelogram).

Here's the diagram I used to test it:


In case you're interested, here's how it works:

First, the function calculates the two vectors a & b based on the corners and the angle of the lines / m.
Then, the character's position p in relation to X1;Y1 is expressed using those two vectors. In the example, p would amount to about 2/3 of a + 1/2 of b.
In other words, starting from X1;Y1, in order to reach p, I have to go 2/3 of the length of a in a's direction and 1/2 of the length of b in b's direction.
And if both values are inside [0;1], I'm still inside (or on) the boundaries.

Sephiroth

#7
Hello,

I'm not sure if that would do in your case but, say you have that picture with losanges and all (the same size as your background), now let's say you assign each one of them a different color, you would only have to load the picture to a drawing surface and then check : if(GetPixelAt(cEgo.X, cEgo.Y) == zone1_color) { //cEgo is in zone 1 }

This would allow you to have unlimited amount of zones, and more importantly, any kind of random shapes.

*Edit, It can be achieved with this:

Sample Code:
Code: ags

//global outside functions, preferably top of globalscript
  DrawingSurface *CurrentMap;

//room load
  DynamicSprite  *MapSprite = DynamicSprite.CreateFromExistingSprite(10);   //the sprite number corresponding to the colored zones image
  CurrentMap = MapSprite.GetDrawingSurface();

//room leave
  CurrentMap.Release();
  MapSprite.Delete();

//check pixel color at hero coords
  CurrentMap.GetPixel(cEgo.X, cEgo.Y);


You can play with it to suit your needs of course, this is just an example.

Knox

#8
Hey guys!

Sorry I havent replied in a while, I havent had time to work at all on my game since the last time :P

@Khris: WOw, this is great! I tried it, everything works with a "normal" player, however when I use it with my car player, Ive got this line in rep_exec (taken from the Taxi Demo game):

Code: ags

    player.x = oCar.X + half_sprite_size;
    player.y = oCar.Y - half_sprite_size;


How do I integrate that to your script so that the car's front/back part of the sprite is detected exactly when it touches the region? Right now since Ihave those 2 lines, the car can "enter" the region almost halfway before triggering my debug line "player is inside region":

Code: ags

bool InsideBounds_ISO(this Character*, int iX1, int iY1, int iX2, int iY2) //iMinX's vector (x,y) and iMax's vector(x,y)
{
  int iXp = this.x;
  int iYp = this.y;
  // d is vector from X1;Y1 to X2;Y2
  float dx = IntToFloat(iX2 - iX1), dy = IntToFloat(iY2 - iY1);
  // a is vector from X1;Y1 to top corner
  // b is vector from X1;Y1 to bottom corner  (a + b = d)
  float bx = (m*dx - dy)/(2.0*m);
  float ax = dx - bx;
  float ay = m * ax, by = -m * bx;
  // express character coords using a & b
  // p is vector from X1,Y1 to character
  float px = IntToFloat(iXp - iX1), py = IntToFloat(iYp - iY1);
  float det = ay*bx - ax*by;
  // p = k*a + l*b
  float k = (bx*py - by*px)/det;
  float l = (ay*px - ax*py)/det;
  // => if both k & l are inside [0;1], character is inside parallelogram
  return (k >= 0.0 && k <= 1.0 && l >= 0.0 && l <= 1.0);
}

// in rep_exec:

    if (player.InsideBounds_ISO(2323, 1593, 2473, 1623)) //REGION A
    {
      Display("player is inside REGION A!"); //debug line
    }




@Sephiroth: Wow thanks for that script :) Im going to try that tonight and see how it goes: I can use it in conjunction with Khris's and see what I come up with. Ill post the results so others can use it too! :)

**
Code: ags
 
doesnt seem to work right now (?)[/s]
--All that is necessary for evil to triumph is for good men to do nothing.

Khris

Quote from: General_Knox on Sun 13/05/2012 20:45:52How do I integrate that to your script so that the car's front/back part of the sprite is detected exactly when it touches the region? Right now since Ihave those 2 lines, the car can "enter" the region almost halfway before triggering my debug line "player is inside region":
You have to move the detection point depending on the direction of the car's movement.

Knox

#10
Ah ok, in this case I created a script that detects the direction, then depending on the direction I moved the detection point by 10 pixels (the car is usually 10 pixels away from the edge of the sprite), like so:

Code: ags

void getCarDirection()
{
    // leaving out the in-between angles for now, do later
    
    if (iCarSprite == 4452) sCarDirection = "N";       // 270º
    else if (iCarSprite == 4541) sCarDirection = "E";  // 360º
    else if (iCarSprite == 4272) sCarDirection = "S";  //  90º
    else if (iCarSprite == 4362) sCarDirection = "W";  // 180º
    else if (iCarSprite == 4500) sCarDirection = "NE"; // 330º
    else if (iCarSprite == 4402) sCarDirection = "NW"; // 210º
    else if (iCarSprite == 4222) sCarDirection = "SE"; //  30º
    else if (iCarSprite == 4320) sCarDirection = "SW"; // 150º
    else sCarDirection = "Null";
}

//at top of the bounds script
  int iXs;
  int iYs;
  int iXp;
  int iYp;
  
  if (sCarDirection != "Null")
  {
    if (sCarDirection == "S" || sCarDirection == "SE" ||sCarDirection == "SW")
    {
      iXs = -10;
      iYs = 10;
    }
    else if (sCarDirection == "N" || sCarDirection == "NE" ||sCarDirection == "NW")
    {
      iXs = 10;
      iYs = -10;
    }
    
    iXp = this.x + iXs; //if using 42x42 sprite car
    iYp = this.y + iYs; //if using 42x42 sprite car
  }
--All that is necessary for evil to triumph is for good men to do nothing.

Knox

#11
Ok I tested the adjustment for 10 pixels...however its not working perfectly...I think Ive placed it in the wrong place, is that possible? If the car enters the boundary at the top of the lane, I found out that the adjustment should be 13 pixels, but if the car enters the boundary at the bottom of the lane, the value should be 10.

Did I make a mistake as to where to apply the adjustments "iXs" and "iYs" ?
Code: ags

bool InsideBounds_ISO(this Character*, int iX1, int iY1, int iX2, int iY2) //iMinX's vector (x,y) and iMax's vector(x,y)
{
  int iXs;
  int iYs;
  int iXp;
  int iYp;
  
  if (sCarDirection != "Null")
  {
    if (sCarDirection == "NW" || sCarDirection == "SE") //calibrate
    {
      iXs = -10;
      iYs = 10;
    }
    else if (sCarDirection == "SW")// || sCarDirection == "NE")
    {
      //Display("SW");
      iXs = -10;     //should be -13 at bottom of lane, -10 at top of  lane: investigate
      iYs = 10;
    }
    else // N,S,E,W  //calibrate
    {
      iXs = 10;
      iYs = -10;      
    }
    
    iXp = this.x + iXs; //if using 42x42 sprite car
    iYp = this.y + iYs; //if using 42x42 sprite car    
  }
  
  //String debug = String.Format("x: %d y: %d", iXp, iYp);
  //lblHotspot.Text = debug;  
  
  // d is vector from X1;Y1 to X2;Y2
  float dx = IntToFloat(iX2 - iX1), dy = IntToFloat(iY2 - iY1);
  // a is vector from X1;Y1 to top corner
  // b is vector from X1;Y1 to bottom corner  (a + b = d)
  float bx = (m*dx - dy)/(2.0*m);
  float ax = dx - bx;
  float ay = m * ax, by = -m * bx;
  // express character coords using a & b
  // p is vector from X1,Y1 to character
  float px = IntToFloat(iXp - iX1), py = IntToFloat(iYp - iY1);
  float det = ay*bx - ax*by;
  // p = k*a + l*b
  float k = (bx*py - by*px)/det;
  float l = (ay*px - ax*py)/det;
  // => if both k & l are inside [0;1], character is inside parallelogram
  return (k >= 0.0 && k <= 1.0 && l >= 0.0 && l <= 1.0);
}

--All that is necessary for evil to triumph is for good men to do nothing.

SMF spam blocked by CleanTalk