The line class

Started by habi, Mar 30, 2020, 04:41 AM

Previous topic - Next topic

habi

Ok.
Q. How do you check if the player is in. eh. Downtown. ?

Well, habi presents you his new nut file.  *cheers*  line.nut.
How about something like Downtown.contains( player.Pos ) ..?

line.nut
[spoiler][noae][noae][noae][noae][noae][noae]const PI = 3.1415926;

enum Orientation
{
right = 0x01,
left = 0x02,
same = 0x03,
error = 0x04
}

class Vector2D
{
x = null;
y = null;

constructor( x, y )
{
this.x = x;
this.y = y;
}
static function CreateFromVector3D( v )
{
return Vector2D( v.x, v.y );
}
static function Midpoint( a , b )
{
return Vector2D( ( a.x + b.x )/2.0, ( a.y + b.y )/2.0 );
}
static function createfrom( point, theta, length )
{
return Vector2D( point.x + length * cos( theta ), point.y + length * sin( theta ) );
}
static function DistanceBetween( a, b )
{
return sqrt ( pow( a.x - b.x ,2 )+ pow( a.y - b.y , 2 ) );
}

}
origin <- Vector2D( 0, 0 );
//-------------------------------------------------------------------------------------------------
//--------------------------------Area Class-------------------------------------------------------
//-------------------------------------------------------------------------------------------------
class Area
{
n = null;
lines = null;
center = null;
constructor( center, lines )
{
this.n = lines.len();
this.lines = lines;
this.center = center;
}

static function Rectangle( a, c ) // corners
{
local e = Line( a, c ); // diagonal of rectangle

local center = Vector2D.Midpoint( a , c ); //midpoint of diagonal

local t = e.theta;

local halfDiagonal = e.length() / 2;
local s = Line.turnAngle( t, Orientation.right, PI/2 );
local b =  Vector2D.createfrom( center, s, halfDiagonal );

s = Line.turnAngle( t, Orientation.left, PI/2 );
local d = Vector2D.createfrom( center, s, halfDiagonal );
return Polygon( center , a, b, c, d );

}

static function Polygon( center, one, two, three, ... )
{
/* _______
   /       \
              /         \
              1     *    1          polygon with a center( any inside point )
  1          1
   \________/


*/
local lines=[];
local first = Line( one, two )
lines.push( first );
local second = Line( two, three )
lines.push( second );
local s = GetOrientation( first.theta, second.theta );
local line;
local new;
if( vargv.len() == 0 )
{
line = Line( three, one );
if( line.IntersectAny( lines, 1, 0 ) )return null;// not the second line which is at index 1
new = GetOrientation( second.theta, line.theta );
if( new != Orientation.same && new != s )
{
return null; // cannot be convex polygon
}
lines.push( line );
}
else
{
line = Line( three, vargv[0] );
if( line.IntersectAny( lines, 1 ) )return null;
new = GetOrientation( second.theta, line.theta );
if( new != Orientation.same && new != s )
{
return null; // cannot be convex polygon
}
lines.push( line );
foreach( i,val in vargv )
{
if( i+1 != vargv.len() )
{
line = Line( val, vargv[ i+1 ] ) ;
if( line.IntersectAny( lines, lines.len()-1 ) )return null;
}
else
{
line = Line( val, one );
if( line.IntersectAny( lines, lines.len()-1, 0 ) )return null;
}
local last_line = lines[lines.len()-1];
new = GetOrientation( last_line.theta, line.theta );
if( new != Orientation.same && new != s )
{
return null; // cannot be convex polygon
}
lines.push( line );
}
}
return Area( center, lines );
}
function contains( p ) // if the point is inside the area.
{
if( this.center != null )
{
local line = Line( p, this.center );
if( line.IntersectAny( this.lines ) )return false;
return true;
}
return null;
}
function DistanceFrom( p ) // distance from a point. point must be outside in the sense of center
{
/* _______
   /       \
  /         \
*------------1     *    1         
   distance   1          1
   \________/


*/
if( this.center != null )
{
if( this.contains( p ) )return 0;
local min_dis, d;
foreach( i, line in this.lines )
{
d = line.DistanceFrom( p );
if( i == 0 )min_dis = d;
else if( d < min_dis )min_dis = d;
}
return min_dis;
}
}
static function GetOrientation( t1, t2 ) // used in the code
{
if( t1 <= PI && t1 > 0 )return _Upper( t1, t2 );
if( t1 == 0 )return _Middle( t1, t2 );
if( -PI < t1 && t1 < 0 )return _Lower( t1, t2 );
}
static function _Upper( t1, t2 ) // PI <= t1 < 0;
{
if( t1 == t2 )return Orientation.same;
if( t1 - PI == t2 )return Orientation.error;
if( t2 < t1 )
{
if( t2 < t1 - PI)
return Orientation.left;
else return Orientation.right;
}else return Orientation.left;
}
static function _Middle ( t1, t2 ) // t1 == 0
{
if( t1 == 0 )
{
if( t2 == PI ) return Orientation.error;
if( t2 < 0 )return Orientation.right;
else return Orientation.left;
}
}
static function _Lower ( t1, t2 ) // -PI < t1 < 0
{
if( t1 == t2 )return Orientation.same;
if( t1 + PI == t2 )return Orientation.error;
if( t2 < t1 ) return Orientation.right;
else
{
if( t2 > t1 + PI )return Orientation.right;
else return Orientation.left;
}

}
}
//------------------------------------------------------------------------------------------------
//------------------------------------Line class--------------------------------------------------
//------------------------------------------------------------------------------------------------
class Line
{
/*
$
/
   / $ is end
  /
/
/
   / * is start
  *

*/

start = null;
end = null;
m = null; //eqn of line y = mx + c
c = null;
a = null; // x = a, the case of vertical line
theta = null; // as a complex number, the same theta or angle
/* Our Angles are in the range -PI to PI with PI included and -PI excluded */

constructor( start, end ) // parameters as vector 2d or 3d
{
this.start = start;
this.end = end;
if( start.x == end.x )
{
this.m = false;
this.c = false;
this.a = start.x;
if( end.y > start.y )theta = PI/2;
else this.theta = -PI/2;
}else
{
this.m = (start.y - end.y)/(start.x-end.x);
this.c = (start.y - this.m * start.x );
if( this.m > 0 )
{
this.theta = end.y > start.y ? atan( this.m ): -PI + atan( this.m );
}else if( this.m < 0 )
{
this.theta = end.y > start.y ? PI + atan( this.m ): atan( this.m );
}else this.theta = end.x > start.x ? 0: PI;
this.a = false;
}
}
function length( ) // returns the length of the line segment.
{
return ::Vector2D.DistanceBetween( this.start, this.end );
}
static function turnAngle( t, orienation, T ) // don't worry
{
if( T < PI && T > 0 )
{
switch( orienation )
{
case Orientation.right:
{
if( t - T > -PI )return t - T;
else return t - T + 2 * PI;
}break;
case Orientation.left:
{
if( t + T <= PI )return t + T;
else return t + T - 2 * PI;
}break;
default:
break;
}
}
}
function DirectionOfPoint( p ) // ie. the point is on the right or left
{
local t1 = this.theta ;
local line = Line( this.end, p );
local t2 = line.theta;
return Area.GetOrientation( t1, t2 );
}
function IntersectAny( lines, ...) // '...' is exception indices
{
foreach( i, line in lines )
{
if( vargv.find( i ) !=  null )continue;
if( this.Intersect( line ) )return true;
}
return false;
}
function GetIntersectionPoint( line )
{
if( !this.Intersect( line ) )return null;
if( this.m == false && line.m == false ) // parallel line
return null;
if( this.m == false )
{
local y = line.m * this.a + line.c ;
return Vector2D( this.a, y );
}else if( line.m == false )
{
local y = this.m * line.a + this.c ;
return Vector2D( line.a, y );
}else if (this.m == line.m )return null; // parallel line
local x = ( line.m - this.m ) / ( this.c - line.c );
local y = this.m * x + this.c;
return Vector2D( x, y );

}
function Intersect( line )
{
if( this.m  == false ) //this is vertical
{
if( line.m == false ) // line is vertical
{
if( this.a != line.a )return false; //different lines.
return true; // same line
}else
{
return HalfApple( this, line );
}
}else
{
if( line.m == false ) //line is vertical
{
return HalfApple( line, this );
}else
{
if( this.m == line.m )
{
if( this.c == line.c )return true; //same line
else return false; //parallel lines
}else
{
local x = ( line.c - this.c )/( this.m - line.m );
local y = this.m * x + this.c;
return this.contains( Vector2D( x, y ) );
}
}
}
}
static function HalfApple( vertical, ordinary ) // where the paradise i go for names?
{
local y = ordinary.m * vertical.a + ordinary.c;
return vertical.contains( Vector2D( vertical.a, y ) );
}
function contains( p )
{
if( this.m != null )
{
if( this. m == false ) //vertical line
{
if( p.x == this.a ) //p lies on it. but do it lie on our line segment?
{
if( p.y == this.start.y || p.y == this.end.y )return true;
if( p.y < this.start.y && p.y > this.end.y )return true;
if( p.y > this.start.y && p.y < this.end.y )return true;
return false;
}else return false;
}else
{
if( p.y == this.m * p.x + this.c ) // p lies on the line. but same problem.
{
if( p.x == this.start.x && p.y == this.start.y)return true;
if( p.x == this.end.x && p.y == this.end.y )return true;
if( p.x > this.start.x && p.x < this.end.x )return true;
if( p.x < this.start.x && p.x > this.end.x )return true;
return false;
}else return false;
}
}
}
function DistanceFrom( p )//vector2d ( parameter can be vector3d, z co-ordinate not evaluated.)
{
if( this.m != null )
{
if( this.m != false )
{
local q;
if( this.m != 0 )
{
local b = ( this.m * p.x + this.c + p.y ) / 2;
local a = ( b - this.c ) / this.m;
q = Vector2D( a, b );
}else q = Vector2D( p.x, 0 );
if( this.contains( q ) )
{
return Vector2D.DistanceBetween( p, q );
}
}else
{
if( p.y == this.start.y || p.y == this.end.y )return fabs( p.x - this.a );
if( p.y < this.start.y && p.y > this.end.y )return fabs( p.x - this.a );
if( p.y > this.start.y && p.y < this.end.y )return fabs( p.x - this.a );
}
local d1 = Vector2D.DistanceBetween( p, this.start );
local d2 = Vector2D.DistanceBetween( p, this.end );
return min( d1, d2 );
}
}
function min( x, y )
{
if( x < y) return x;
else return y;
}
}
[/noae][/noae][/noae][/noae][/noae][/noae][/spoiler]
Sample code
[spoiler]
Let us check if the player is on the bridge near Police station in Washington Beach. To start with, you need four corners of the bridge.
[noae][noae][noae][noae][noae][noae][noae][noae]local a =  Vector2D(325.024, -346.899);

local b =  Vector2D(347.484, -325.347);

local c =  Vector2D(444.129, -347.649);

local d =  Vector2D(438.035, -373.434);

local i =  Vector2D(382.222, -346.697);

local bridge = Polygon( i, a, b, c, d );


if( bridge.contains( player.Pos ) )
{
if( player.Pos.z > 10.0 && player.Pos < 20.0 )
MessagePlayer(" You are on washington bridge ", player );
}
[/noae][/noae][/noae][/noae][/noae][/noae][/noae][/noae]So what are a, b, c, d? Simple, the four corners of the bridge, taken in an order
Ah. what is i? it is any point on the bridge, other than boundaries.
[/spoiler]
Member functions
[spoiler]
SYNTAX: [returned-type] FunctionName( [argument-type] argument_name, ... );
class Area
Area Rectangle( Vector2D a, Vector2D c )
Area Polygon( Vector2D center, Vector2D one, Vector2D two, Vector2D three, ... )
bool contains( Vector2D(/Vector) p )
float DistanceFrom( Vector2D p ) // distance from a point.

class Line
integer DirectionOfPoint( Vector2D p)
bool IntersectAny( array lines, integer exception_indice_1, integer exception_indice_2, ...)
Vector2D GetIntersectionPoint( Line line )
bool Intersect( Line line )
bool contains( Vector2D( /Vector) p )
float DistanceFrom( Vector2D( /Vector) p )
float length( )

Vector2D
Vector2D CreateFromVector3D( Vector v )
Vector2D Midpoint( Vector2D a , Vector2D b )
Vector2D createfrom( Vector2D point, float theta, float length )
float DistanceBetween( Vector2D a, Vector2D b )
[/spoiler]

Other advantages: Suppose in vice city, you have a line. we can check if the player is in the right side of this line, or left. it is like line.DirectionOfPoint( p) == Orientation.right
Hope it all works. Comments, bugs, advices all are welcome ;)

DizzasTeR

Nice work

:edit: Perhaps you could've added these functionalities in the vector class itself but this would do as well :P

habi

Quote from: Doom_Kill3R on Mar 30, 2020, 04:48 AMPerhaps you could've added these functionalities in the vector class itself but this would do as well
ok

aXXo

Quote from: habi on Mar 30, 2020, 04:41 AMOther advantages: Suppose in vice city, you have a line. we can check if the player is in the right side of this line, or left. it is like line.DirectionOfPoint( p) == Orientation.right
Nice work.
I think the direction should be changed to East/West/North/South rather than Right/Left/Top/Bottom. Since Right/Left are relative directions and can change depending on where player is facing.

NicusorN5

There was InPoly function, anyway, nice work. Is this separating axis theorem?