LUA How to check if a vessel has landed within a specified rectangular area?

dgatsoulis

ele2png user
Donator
Joined
Dec 2, 2009
Messages
1,939
Reaction score
367
Points
98
Location
Sparta
I am trying to setup a challenge in Lua script, in which the player will have to perform an unpowered landing on a runway.

I want to check if the spacecraft touches down within a specified area and then stops within that area (success). If the vessel goes out of the area while it touches the ground then it is a failure. This way I can allow for "touch'n go" attempts but if the "touch" is outside the specified area then the mission is a failure.

I know how to set it up, if it was just a circular area with a radius R and a central point A with coordinates X and Y.

In this case, the part of the script that checks for the landing, would be something like this:

(pseudo-script)
Code:
TGT_A= coordinates of point A
groundcontact=false
alt=getaltitude
   if alt < (number) then groundcontact=true
      elseif alt > (number) then groundcontact=false [COLOR="RoyalBlue"]--The altitude determines if the spacecraft has touched-down[/COLOR]
      end
   end
R_A=getdistance from TGT_A
If groundcontact=true and  R_A =< (the radius R) then success
end
If groundcontact=true and R_A > (the radius R) then failure
end [COLOR="RoyalBlue"]--(So far I can determine if the spacecraft has touched-down within the area.  Now I can either get vel.x or v:islanded, to check if the vessel has stopped, to complete the challenge and return a "score").[/COLOR]

But how does one set it up for a rectangular area?

The first method I thought, was to setup many small circular areas to cover most of the runway and check if the vessel touches down within those areas. (See pic)

landing1_zpsc9daccc5.jpg


But I'm not sure what the syntax would be so I can check if the vessel touches down within area 1 or within area 2 and so on. Could it be as simple as this?

Code:
TGT_1 = coordinates of point 1
TGT_2 = coordinates of point 2
[COLOR="RoyalBlue"]--and so on[/COLOR]
R_1 = getdistance from center of area 1
R_2 = getdistance from center of area 2
[COLOR="RoyalBlue"]-- ...and so on[/COLOR]
If groundcontact=true and R_1 =< (1/2 the width of the runway) or R_2 =< (1/2 the width of the runway) [COLOR="RoyalBlue"]...and so on[/COLOR] then success
  else if R_1 > (1/2 the width of the runway) or R_2 > (1/2 the width of the runway) [COLOR="RoyalBlue"]...and so on[/COLOR] then failure

This syntax doesn't feel right. The vessel could touchdown within area 3 but since R_1 is bigger than 1/2 the width of the runway, it would return a failure instead of a success, wouldn't it?

So my question for this method is: What would be the correct syntax to check if the vessel has touched-down inside any of these areas and "return" a success or a failure if it touches-down outside those areas?

I also thought of something else. Instead of defining many small areas, would it be possible to define a rectangular area using the coordinates of the corners of the runway and then get the coordinates of the vessel (while it is in "groundcontact")?

For example (see pic):

landing2_zps92a157d0.jpg


X_1= longitude of corner1
Y_1= latitude of corner1
X_2= longitude of corner2
Y_2= latitude of corner2
X_3= longitude of corner3
Y_3= latitude of corner3
X_4= longitude of corner4
Y_4= latitude of corner4

(This would be easier for North-South or East-West runways since every next corner would have one coordinate the same.)

Then "getaltitude" to determine if vessel has touched-down and have it return its X and Y coordinates to check if they are within the "limits" specified above. It seems to me that this is a better method but I don't even know where to begin as far as the syntax goes.

Any help is much appreciated, thanks in advance.
 

Urwumpe

Not funny anymore
Addon Developer
Donator
Joined
Feb 6, 2008
Messages
37,676
Reaction score
2,406
Points
203
Location
Wolfsburg
Preferred Pronouns
Sire
What about defining the runway with barycentric coordinates?
 

dgatsoulis

ele2png user
Donator
Joined
Dec 2, 2009
Messages
1,939
Reaction score
367
Points
98
Location
Sparta
What about defining the runway with barycentric coordinates?

:hmm: That would be a perfect solution for determining if the vessel's coordinates (while it is in "groundcontact") are within a specified area of any 2D shape. (or even 3D shape).

In the case of a runway, I would need to define a matrix and then check if the vessel is within that matrix. I looked at the example in wikipedia (triangle).
For now I can't say that I know the math to do it, but I'll look it up and return here if I have more questions.

Thanks :tiphat:
 

RisingFury

OBSP developer
Addon Developer
Joined
Aug 15, 2008
Messages
6,427
Reaction score
492
Points
173
Location
Among bits and Bytes...
Give me a few minutes to check my OBSP code. I did exactly that...

---------- Post added at 23:16 ---------- Previous post was at 22:59 ----------



This code is in C++. You'll have to write you own version of it in LUA, but I imagine it shouldn't be too difficult.

Code:
bool IsOnRunway(VECTOR3 Position, VECTOR3 EndOne, VECTOR3 EndTwo, double Width, double Radius)
{
	Position.z = 0;
	EndOne.z = 0;
	EndTwo.z = 0;

	double HeadingOne = GetRequiredHeading(EndOne, Position);
	double HeadingTwo = GetRequiredHeading(EndTwo, Position);
	double Heading = GetRequiredHeading(EndTwo, EndOne);

	double HeadingDifferenceOne = 0;
	double HeadingDifferenceTwo = 0;

	HeadingDifferenceTwo = GetHeadingDifference(Heading, HeadingTwo);

	Heading = Heading + PI;

	Heading = CorrectAngle(Heading);

	HeadingDifferenceOne = GetHeadingDifference(Heading, HeadingOne);

	if ((HeadingDifferenceOne > (PI / 2)) || (HeadingDifferenceTwo > (PI / 2)))
	{
		return false;
	}

	else
	{
		double Angle = oapiOrthodome(EndOne.x, EndOne.y, Position.x, Position.y);
		double Distance = Radius * asin(sin(Angle) * sin(HeadingDifferenceOne));

		if (Distance < (Width / 2))
		{
			return true;
		}
	}

	return false;
}

Position is the current vessel position, with Position.x being longitude, Position.y being latitude and Position.z being altitude. EndOne and EndTwo are the end points of the runway that define the center line. Width is the width of the runway and Radius is the planet's radius.

You'll notice a reference to GetRequiredHeading, GetHeadingDifference and CorrectAngle.

GetRequiredHeading:
Code:
double GetRequiredHeading(VECTOR3 CurrentPosition, VECTOR3 DesiredPosition)
{
	double Heading = 0;
	double LongitudeDifference = DesiredPosition.x - CurrentPosition.x;
	double LatitudeDifference = DesiredPosition.y - CurrentPosition.y;
	
	double Y = sin(LongitudeDifference) * cos(DesiredPosition.y);
	double X = cos(CurrentPosition.y) * sin(DesiredPosition.y) - sin(CurrentPosition.y) * cos(DesiredPosition.y) * cos(LongitudeDifference);

	if (Y < 0)
	{
		if (X > 0)
		{
			Heading = (2 * PI) + atan(Y / X);
		}

		else if (X < 0)
		{
			Heading = PI - atan(-Y / X);
		}

		else
		{
			Heading = 3 * PI / 2;
		}
	}

	else if (Y > 0)
	{
		if (X > 0)
		{
			Heading = -atan(-Y / X);
		}

		else if (X < 0)
		{
			Heading = PI + atan(Y / X);
		}

		else 
		{
			Heading = PI;
		}
	}

	else
	{
		if (X > 0)
		{
			Heading = 0;
		}

		else if (X < 0)
		{
			Heading = PI;
		}

		else 
		{
			Heading = 0;
		}
	}

	return Heading;
}

This gives you the heading you need to go to on a sphere, if you want to get from CurrentPosition to DesiredPosition. Same kind of format, with x being longitude, y being latitude and z being altitude.


GetHeadingDifference:
Code:
double GetHeadingDifference(double HeadingOne, double HeadingTwo)
{
	double HeadingDifference = HeadingOne - HeadingTwo;

	if (HeadingTwo > HeadingOne)
	{
		HeadingDifference = HeadingTwo - HeadingOne;
	}

	if (HeadingDifference > PI)
	{
		HeadingDifference = (2 * PI) - HeadingDifference;
	}

	return HeadingDifference;
}

This gives you the difference in heading between HeadingOne and HeadingTwo. Let's say that HeadingOne is 0 and HeadingTwo is 3/2 Pi (due west), the function will return a heading difference of Pi/2, not 3/2 Pi.


The last one you need is CorrectAngle:
Code:
double CorrectAngle(double Angle) 
{
	if (Angle > 2 * PI)
	{
		return Angle - int(Angle / (2 * PI));
	}

	if (Angle < 0)
	{
		return Angle + (int(abs(Angle) / (2 * PI)) + 1) * 2 * PI;
	}

	return Angle;
}

This one just makes sure that the angle is always between 0 and 2 Pi. It's unlikely to ever happen, but let's check anyway, right? ;)


I wrote most of this code quite early into OBSP development and haven't touched it since. I'd put in a reference to the "good old days", but I'd say the best days are still ahead...
 
Last edited:

Urwumpe

Not funny anymore
Addon Developer
Donator
Joined
Feb 6, 2008
Messages
37,676
Reaction score
2,406
Points
203
Location
Wolfsburg
Preferred Pronouns
Sire
Not really the best algorithm, since you use trigonometry there for solving the problem.

I would look at this for a solution:

http://en.wikipedia.org/wiki/Barycentric_coordinate_system_(mathematics)

Since you can describe a runway by two triangles, you can even avoid the general form equations and simply check if the point is in at least one of the two runway triangles. And you only need to calculate this transformation once per runway.
 

dgatsoulis

ele2png user
Donator
Joined
Dec 2, 2009
Messages
1,939
Reaction score
367
Points
98
Location
Sparta
Thanks alot RisingFury.:tiphat:

This is pretty new stuff for me, so I'll have to take my time and let it sink in, but the example of the code is great.

If I'm understanding it correctly, in the case of the challenge I want to setup, it's even simpler than I thought it would be.

Instead of defining a matrix for the 4 corners of the runway , I can define the line that passes through the center along the length of the runway with endpoints 1 and 2 and also define the width (W) of the runway. If the vessel touches down (and stops) at a distance more than W/2 then it's a failure, otherwise it is a success.

It would mean that it would allow for the vessel to touchdown at W/2 before or after the center of the end of the runway, but compared to the length of a runway, half it's width is barely noticable.

Thanks again
:cheers:

EDIT:
Urwumpe said:
Since you can describe a runway by two triangles, you can even avoid the general form equations and simply check if the point is in at least one of the two runway triangles. And you only need to calculate this transformation once per runway.

That's exactly the example that I looked at. Wouldn't it be better to define a rectangle (4 points instead of 6 -two triangles)?

(x= λ1x1 + λ2x2 + λ3x3 + λ4x4, y= λ1y1 + λ2y2 + λ3y3 + λ4y4)
 
Last edited:

Urwumpe

Not funny anymore
Addon Developer
Donator
Joined
Feb 6, 2008
Messages
37,676
Reaction score
2,406
Points
203
Location
Wolfsburg
Preferred Pronouns
Sire
That's exactly the example that I looked at. Wouldn't it be better to define a rectangle (4 points instead of 6 -two triangles)?

(x= λ1x1 + λ2x2 + λ3x3 + λ4x4, y= λ1y1 + λ2y2 + λ3y3 + λ4y4)

It is a bit harder to find a solution then. The triangle case is pretty well documented today and has many good optimized algorithms available.
 

dgatsoulis

ele2png user
Donator
Joined
Dec 2, 2009
Messages
1,939
Reaction score
367
Points
98
Location
Sparta
It is a bit harder to find a solution then. The triangle case is pretty well documented today and has many good optimized algorithms available.

I see. Sorry if this next question sounds stupid, before this thread I didn't even know about barycentric coordinates.

For the purposes of the challenge I want to setup, what about a combination of what RisingFury did with trig, but using the barycentric coordinates?

Instead of two triangles, Ι define a line with endpoints 1 and 2 that passes through the center of the length of the runway. Then Ι would have

x= λ1x1 + λ2x2, y= λ1y1 + λ2y2

Then I could allow for the vessel to touchdown at a distance R which would be half the width of the runway. This would also mean that the vessel would be permitted to land slightly before or slightly after the end of the runway (half a width from the endpoints), but compared to the length it's no big deal.
 
Last edited:

Urwumpe

Not funny anymore
Addon Developer
Donator
Joined
Feb 6, 2008
Messages
37,676
Reaction score
2,406
Points
203
Location
Wolfsburg
Preferred Pronouns
Sire
Just thinking of it, you could also just calculate, if the position of the spacecraft is perpendicular to the runway vector and the closest point to that runway is less than W/2 away from it. This can be calculated without trigonometry as well.
 

RisingFury

OBSP developer
Addon Developer
Joined
Aug 15, 2008
Messages
6,427
Reaction score
492
Points
173
Location
Among bits and Bytes...
Not really the best algorithm, since you use trigonometry there for solving the problem.

Well... yea. It was meshed together to reuse the code I already had. OBSP is full of unoptimized code, with lots of trigonometry for navigation. The reason it's so is because it needs to get done and if I spent my time researching optimal algorithms, I'd never get anything done.

Though to put your mind at ease... OBSP creates a pretty much immeasurable loss of FPS when run. The only case where OBSP does anything nasty is when it needs to calculate unguided bomb impact point, which is done numerically.
 

Urwumpe

Not funny anymore
Addon Developer
Donator
Joined
Feb 6, 2008
Messages
37,676
Reaction score
2,406
Points
203
Location
Wolfsburg
Preferred Pronouns
Sire
Well... yea. It was meshed together to reuse the code I already had. OBSP is full of unoptimized code, with lots of trigonometry for navigation. The reason it's so is because it needs to get done and if I spent my time researching optimal algorithms, I'd never get anything done.

Though to put your mind at ease... OBSP creates a pretty much immeasurable loss of FPS when run. The only case where OBSP does anything nasty is when it needs to calculate unguided bomb impact point, which is done numerically.

Still no reason to not use different math in general there, since researching a set of basic math for the project would only be done once. And even if modern computing power is enough for a while: It is limited.

(And why are you not using orbiter as solver for the bomb impact?)
 

Urwumpe

Not funny anymore
Addon Developer
Donator
Joined
Feb 6, 2008
Messages
37,676
Reaction score
2,406
Points
203
Location
Wolfsburg
Preferred Pronouns
Sire
What do you mean? I need to know where the bomb will land before I drop it. That's before the bomb vessel is even spawned. How does Orbiter let me do that?

I am not sure why you need this information solved iteratively. And why in advance, since the bomb spends quite a long time in the air.
 

dgatsoulis

ele2png user
Donator
Joined
Dec 2, 2009
Messages
1,939
Reaction score
367
Points
98
Location
Sparta
First I used RisingFury's code and it worked great. But I also wanted to find a solution for any 2D shape so I went after what Urwumpe suggested. It was quite an interesting journey (for me). The solution turned out to be even more simple than the trigonometry one and I'm quite pleased with the result.

I "blogged" my experience and the solution.

Many thanks for your answers.
:tiphat:
 

RisingFury

OBSP developer
Addon Developer
Joined
Aug 15, 2008
Messages
6,427
Reaction score
492
Points
173
Location
Among bits and Bytes...
I am not sure why you need this information solved iteratively. And why in advance, since the bomb spends quite a long time in the air.

How on Earth can I drop a bomb on target, if I don't know where I'm supposed to drop it FROM?

Given current velocity, position, bomb characteristics and atmospheric model, I need to figure out when to drop the bomb, so that in the future, as it hits the ground, it lands on target. It's bomb landing point prediction. CCIP.
 

dgatsoulis

ele2png user
Donator
Joined
Dec 2, 2009
Messages
1,939
Reaction score
367
Points
98
Location
Sparta
@RisingFury (Answer from blog)

I'm attaching here a zip with the scripts and two test scenarios. It seems to be working ok for lattitudes up to 87 degrees. Unzip and run the CheckIfOnRunway1 and 2 to test. The zip will also add two bases, Runway1 and Runway2 on Earth. The first one is 50m wide 5km long at 045 heading.
The second one is at 87 lattitude where it starts to produce a noticable error. I tested on other lattitudes and orientations inbetween and I did not notice any problems.
 

Attachments

  • CheckIfOnRunway.zip
    7.6 KB · Views: 4

dgatsoulis

ele2png user
Donator
Joined
Dec 2, 2009
Messages
1,939
Reaction score
367
Points
98
Location
Sparta
I tested the script for runways of various lengths and widths and I did not find any problems for lattitudes lower than 85°. Above that there starts to be a small but noticable error which gets larger with lattitude and exactly at the poles it doesn't work at all.

I had some time today to make another test for irregular 2D shapes.

I set up a "base" and textured a triangle, rectangle and pentagon, all irregular. I got the coordinates of each point and I used the lua script to check if the vessel was inside or outside. It worked perfectly.

Here is a video of the test. look at the left side of the screen as the DG goes from the yellow area to the cyan one.

http://www.youtube.com/watch?v=K46fH8-MjJY&feature=youtu.be

Here is the script that I used in the video:

Code:
-- sanity checks
v = vessel.get_interface('GL1')
if v == nil then
    term.out ('Could not find 1. Aborting')
    return
elseif v:get_classname() ~= 'DeltaGlider' then
    term.out ('Wrong vessel class. Aborting')
    return
end

-- set the types of notes position size and color on screen
note = oapi.create_annotation()
note:set_pos (0.002,0.23,0.5,0.5)
note:set_colour ({r=1,g=1,b=0})
note:set_size (0.8)

gnote = oapi.create_annotation()
gnote:set_pos (0.815,0.23,0.998,0.5)
gnote:set_colour ({r=1,g=1,b=0})
gnote:set_size (0.8)


-- wait for goals to be completed (I'm using this code for challenges, so I'm keeping this format)
contact = false
vessel_inside=0
goal2=0
goals=vessel_inside+goal2

while goals < 5 do --arbitrary number larger than the number of goals so the script doesn't close. In my challenge I set this to the number of goals
    gnote:set_text (string.format ("                GOALS\n---------------------------------\n• Check If the DeltaGlider is\n  on the ground or in the air\n• Return Ground or Airborn\n• If Ground Contact is true \n  check if inside cyan area\n• Return Inside or Outside"));
    
vessel_inside=0

    --my vessel's coordinates. I need to have them in the same units as the coordinates of the shapes
    hobj = v:get_surfaceref()
    equ = oapi.global_to_equ (hobj, v:get_globalpos())
    x0 = equ.lng*180/PI
    y0 = equ.lat*180/PI

    -- coordinates of the triangle points. important to move counter-clockwise to get to the next point
    xa1 = 27.997948
    xa2 = 28.003588
    xa3 = 27.996508
    ya1 = 35.998773
    ya2 = 35.997393
    ya3 = 36.002883

    -- areas of the triangles defined by each side of the shape above and my coordinates
    a_1 = 0.5*((xa1*ya2)-(ya1*xa2)-(x0*ya2)+(y0*xa2)+(x0*ya1)-(y0*xa1))
    a_2 = 0.5*((xa2*ya3)-(ya2*xa3)-(x0*ya3)+(y0*xa3)+(x0*ya2)-(y0*xa2))
    a_3 = 0.5*((xa3*ya1)-(ya3*xa1)-(x0*ya1)+(y0*xa1)+(x0*ya3)-(y0*xa3))

    -- check if all areas are positive
    if a_1>0 and a_2>0 and a_3>0 then 
       vessel_inside=1
    end

     -- coordinates of the irregular rectangle points. Important to move counter-clockwise to get to the next point
    xb1 = 28.010138
    xb2 = 28.012718
    xb3 = 28.010758
    xb4 = 28.005328
    yb1 = 35.997543
    yb2 = 35.997983
    yb3 = 36.000623
    yb4 = 36.000973

    -- areas of the triangles defined by each side of the shape above and my coordinates
    b_1 = 0.5*((xb1*yb2)-(yb1*xb2)-(x0*yb2)+(y0*xb2)+(x0*yb1)-(y0*xb1))
    b_2 = 0.5*((xb2*yb3)-(yb2*xb3)-(x0*yb3)+(y0*xb3)+(x0*yb2)-(y0*xb2))
    b_3 = 0.5*((xb3*yb4)-(yb3*xb4)-(x0*yb4)+(y0*xb4)+(x0*yb3)-(y0*xb3))
    b_4 = 0.5*((xb4*yb1)-(yb4*xb1)-(x0*yb1)+(y0*xb1)+(x0*yb4)-(y0*xb4))
    
    -- check if all areas are positive
    if b_1>0 and b_2>0 and b_3>0 and b_4>0 then 
       vessel_inside=1
    end

    -- coordinates of the irregular pentagon points. important to move counter-clockwise to get to the next point
    xc1 = 28.016268
    xc2 = 28.020388
    xc3 = 28.021108
    xc4 = 28.018698
    xc5 = 28.014218
    yc1 = 35.997183
    yc2 = 35.998113
    yc3 = 36.000933
    yc4 = 36.002633
    yc5 = 36.001333

    -- areas of the triangles defined by each side of the shape above and my coordinates
    c_1 = 0.5*((xc1*yc2)-(yc1*xc2)-(x0*yc2)+(y0*xc2)+(x0*yc1)-(y0*xc1))
    c_2 = 0.5*((xc2*yc3)-(yc2*xc3)-(x0*yc3)+(y0*xc3)+(x0*yc2)-(y0*xc2))
    c_3 = 0.5*((xc3*yc4)-(yc3*xc4)-(x0*yc4)+(y0*xc4)+(x0*yc3)-(y0*xc3))
    c_4 = 0.5*((xc4*yc5)-(yc4*xc5)-(x0*yc5)+(y0*xc5)+(x0*yc4)-(y0*xc4))
    c_5 = 0.5*((xc5*yc1)-(yc5*xc1)-(x0*yc1)+(y0*xc1)+(x0*yc5)-(y0*xc5))

    -- check if all areas are positive
    if c_1>0 and c_2>0 and c_3>0 and c_4>0 and c_5>0 then 
       vessel_inside=1
    end

    --msgs and info on screen
    msg = string.format("-------------------\n        Info\n-------------------\n• X: %.6f \n• Y: %.6f\n-------------------\n    Messages\n------------------- ",x0, y0)
    note:set_text (msg)
    alt = v:get_altitude()
    contact = false
    if contact == false then
        contact = v:get_groundcontact()
        if contact == true then
           msg = msg.."\n• Ground"
        end
        note:set_text (msg)
        if alt >= 3 then
          contact = false
          msg = msg.."\n• Airborn"
        end
     end
    if vessel_inside<1 and contact == true then
        msg = msg.."\n• Outside"
    end
    if vessel_inside>0 and contact == true then
        msg = msg.."\n• Inside"
    end 
    note:set_text (msg)
    	
    proc.skip()
	
    -- for challenges. check if goals have been met and close the script
    hobj = v:get_surfaceref()
       planet = v:is_landed() 
       if oapi.get_objname(planet) ~= 'Earth' then 
         planet = nil 
       end
         goal2=0
         if planet ~= nil then goal2=1
         end
         goals=vessel_inside+goal2
         if goals > 4 then -- same as at the beginning minus one. I use it for challenges, but here I don't want to close the script
         end
end
 
oapi.del_annotation (gnote)

This will work great for my challenge, I hope someone else finds it useful too.
 
Top