The online racing simulator
PHP5 - PTH File Parser.
A nice change of pace for me, I did a little script that will parse PTH files. It is licensed out as MIT as with all of my work on LFS, so feel free to use it for anything.
Attached files
PTH.zip - 759 B - 898 views
Is it possible to explain this a little further?

How can I determine where a typical UCID is driving on a certain moment?
In insim ISP_NLP
struct NodeLap // Car info in 6 bytes - there is an array of these in the NLP (below)
{
[B]word Node; // current path node[/B]
word Lap; // current lap
byte PLID; // player's unique id
byte Position; // current race position : 0 = unknown, 1 = leader, etc...
};

Node corresponds with the nodenumber inside pth
Quote from avetere :Node corresponds with the nodenumber inside pth

Yeah, pretty much.

Cargame, what are you trying to do?
I want to be able to check if a racer is on the track or not. Continuously

(fyi, PRISM is running now 24/7 on our main server )

.
Quote from cargame.nl :I want to be able to check if a racer is on the track or not. Continuously

Ok, I'll see if I can't cook some pesudo code up for you, or a PRISM plugin that will do this.

Quote from cargame.nl :(fyi, PRISM is running now 24/7 on our main server )

Sweet! Noticed your running 0.3.3 still. But very cool.
I'm working a something for LVS. I'm just not getting all of the parts down in my head and it's confusing me a little bit.

So, let's think it out ... We need to know if the player is in node's polygon. To do this we need to get each node's polygon, and to do that we need to load the pth file. So we are going to need to know what track we are on, for this we are going to have to hook into the State packet (IS_STA). We are also going to need to have the pth files some where that is known. (I've made it so that they are stored in $PRISM/data/pth.


<?php 
public function onTrackChange(IS_STA $STA)
{
    
$this->PTH = new PTH(ROOTDIR '/data/pth/' $STA->Track '.pth');
}
?>

Now that we have that down, we need to know the Node the player is currently in, as well as the player's X & Y coords. For this we can only hook into the MCI packet, as it is the only packet that offers this level of detail.


<?php 
public function onMultiCarInfo(IS_MCI $MCI)
{
    foreach (
$MCI->Info as $Info)
    {
        if (
$this->isInPoly($Info->X$Info->Y$this->PTH->Nodes[$Info->Node]->toPolyRoad())
            continue; 
# They are within the node.
        # If we get here, then they are NOT on the track.
    
}
}
?>

I am going to update the toPolyRoad() function to the Node class within the PTH module so that the code really is as simple as that.

Speaking of those changes to the Node class, here they are.


<?php 
    
public function toPolyLimit()
    {
        return array
        (
            array
            (
                
'x' => $this->Center->$this->Limit->Left,
                
'y' => $this->Center->$this->Limit->Left
            
), array
            (
                
'x' => $this->Cetner->$this->Limit->Right,
                
'y' => $this->Cetner->$this->Limit->Right
            
)
        );
    }
    public function 
toPolyRoad()
    {
        return array
        (
            array
            (
                
'x' => $this->Center->$this->Road->Left,
                
'y' => $this->Center->$this->Road->Left
            
), array
            (
                
'x' => $this->Cetner->$this->Road->Right,
                
'y' => $this->Cetner->$this->Road->Right
            
)
        );
    }
?>

Couple this with the lfs provided PTH files found on lfs.net and extract them to your $PRISM/data/pth directory and you'll be all set to go with these.

This is providing that I'm not completely out of my mind, as all of this is very much untested.
*grmblfx* I had a more lenghty version of this but then my computer froze and I'm quite annoyed by that, so here the short version:

If I'm not completely mistaken you'd have to multiply your Limit and Road values with:
cos(atan2($this->Direction->X, $this->Direction-Y)) for x-values and with:
sin(atan2($this->Direction->X, $this->Direction-Y)) for y-values

$this->Center->X + $this->Limit->Left*cos(atan2($this->Direction->X, $this->Direction->Y))
or
$this->Center->Y + $this->Limit->Left*sin(atan2($this->Direction->X, $this->Direction->Y))
Quote from avetere :If I'm not completely mistaken you'd have to multiply your Limit and Road values with:

Ok, if you could explain this part to me. Should it not be that the Road and Limit would be giving me the delta from that of the center of the node's X & Y? There by all I should have to do is add them to the X & Y coords to get the information I need. Or am I missing some bit of information.
Limit (or Road) give the distance of the corresponding boundary (drivable area or track) to the point of the node (given by Center). This is simply a length of a vector.
But you also have to consider the orientation (direction) of the node to see "where to" this distance is drawn.
Your approach makes every node being rotated by 45° ...
For clarification I append an image:
left: my approach
right: your's
Here you can easily see, that Your approach must be wrong, as it creates very small tracks around the apex of a sharp turn.

To be precise, You'd also have to consider rotation around x- and y-axis for elevations but this would be minor differences of some cm only as the left one fits very well, when layered over the track images found here:
http://www.lfsforum.net/showthread.php?p=676857#post676857
Attached images
pthread_5.PNG
Quote from avetere :Here you can easily see, that Your approach must be wrong.

You see, this is why I love Germans. They are just so direct! Thank you, avetere, I'll take a closer look at your approach.


<?php 
    
public function toPolyLimit()
    {
        return array
        (
            array
            (
                
'x' => $this->Center->$this->Limit->Left cos(atan2($this->Direction->X$this->Direction->Y)),
                
'y' => $this->Center->$this->Limit->Left sin(atan2($this->Direction->X$this->Direction->Y))
            ), array
            (
                
'x' => $this->Center->$this->Limit->Right cos(atan2($this->Direction->X$this->Direction->Y)),
                
'y' => $this->Center->$this->Limit->Right sin(atan2($this->Direction->X$this->Direction->Y))
            )
        );
    }
    public function 
toPolyRoad()
    {
        return array
        (
            array
            (
                
'x' => $this->Center->$this->Road->Left cos(atan2($this->Direction->X$this->Direction->Y)),
                
'y' => $this->Center->$this->Road->Left sin(atan2($this->Direction->X$this->Direction->Y))
            ), array
            (
                
'x' => $this->Center->$this->Road->Right cos(atan2($this->Direction->X$this->Direction->Y)),
                
'y' => $this->Center->$this->Road->Right sin(atan2($this->Direction->X$this->Direction->Y))
            )
        );
    }
?>

I guess I'll commit this later on today to github for testing.
Well, didn't mean to harm you ... I guess the directness is merely a lack of speech
Quote from avetere :Well, didn't mean to harm you ... I guess the directness is merely a lack of speech

No harm inflected, I found it quite amusing.
Actually I reviewed my code and there was a factor, because x,y and z are given as 65536=1meter whereas Limit and Road are already given in meters ... so the functions I really used were:

<?php 
function getOuterX($x$deltaX$dirXdirY) {
    return 
1280 $x/65536 $deltaX*cos(atan2($dirX$dirY));
}
function 
getOuterY($y$deltaY$dirXdirY) {
    return 
1280 $y/65536 $deltaY*sin(atan2($dirX$dirY));
}
?>

with "delta" corresponding to either Limit or Road

1280 offset was used here to fit everything within coordinates 0,0 and 2560,2560.
Inversion for y axis was to achieve same orientation as ingame in the generated images ... alternatively you could do:

<?php 
function getOuterX($x$deltaX$dirXdirY) {
    return 
1280 $x/65536 $deltaX*cos(atan2($dirX$dirY));
}
function 
getOuterY($y$deltaY$dirXdirY) {
    return 
1280 $y/65536 $deltaY*sin(atan2($dirX$dirY));
}
?>

depending on your coordinate system ...
I'm doing it straight from the MCI packets, and from the PTH file.
So, it should be:

<?php 
    
public function toPolyLimit()
    {
        return array
        (
            array
            (
                
'x' => $this->Center->$this->Limit->Left cos(atan2($this->Direction->X$this->Direction->Y)) * 65536,
                
'y' => $this->Center->$this->Limit->Left sin(atan2($this->Direction->X$this->Direction->Y)) * 65536
            
), array
            (
                
'x' => $this->Center->$this->Limit->Right cos(atan2($this->Direction->X$this->Direction->Y)) * 65536,
                
'y' => $this->Center->$this->Limit->Right sin(atan2($this->Direction->X$this->Direction->Y)) * 65536
            
)
        );
    }
    public function 
toPolyRoad()
    {
        return array
        (
            array
            (
                
'x' => $this->Center->$this->Road->Left cos(atan2($this->Direction->X$this->Direction->Y)) * 65536,
                
'y' => $this->Center->$this->Road->Left sin(atan2($this->Direction->X$this->Direction->Y)) * 65536
            
), array
            (
                
'x' => $this->Center->$this->Road->Right cos(atan2($this->Direction->X$this->Direction->Y)) * 65536,
                
'y' => $this->Center->$this->Road->Right sin(atan2($this->Direction->X$this->Direction->Y)) * 65536
            
)
        );
    }  
?>

that should be constistent with the coordinate system provided by MCI and give you something like this:
Attached images
as3_new.png
Yeah, had a feeling. I'm heading home now (Girlfriend is driving) so I'll make the changes to the source code and see how it works after I've had something to eat.
Yeah, that did not work. If you download this version of PRISM from here and then download the LVS patch file from this thread, you'll have all of the information I have to see what I am doing.

Just extract to the same location, the PRISM file first then the LVS patch, and you'll have everything I have up until this point.

Important locations:
  • $PRISMDIR/data/pth/* - Has all of the pth files that I'm using.
  • $PRISMDIR/modules/prism_plugins.php - Line 449 is the start of the inPoly function.
  • $PRISMDIR/modules/prism_pth.php - Is the PTH file parser.
  • $PRISMDIR/plugins/LVS.php - Is the Lap Verification System Plugin, you'll need to activate this plugin to see what I am doing.
Attached files
LVS-Patch-0.1.0.zip - 787.7 KB - 645 views
I'll have a look at it tomorrow ... here's bedtime now
Error is due to the inPoly function, going to fix it tonight.
ok ... but as I understand, you can't really determine whether a car is on the track or not by simply looking at one node(line).

If I see it correctly, MCI gives you:
- the last node (regularly) crossed
- current coordinates

So, what you should do is:
- span an area between the last node crossed (poly1, red) and the next one to come (poly2 , blue)
(in the image this area is closed by green lines, giving you a "virtual boundary" ... actually this boundary should be curved as seen in the dashed green lines, so even this gives an error, but I currently have no idea how to simply avoid this)

- check, whether the current position (orange dot, point x) is within this polygon

... or have I missed something?!



EDIT: here a basic approach for checking (function requires 2 nodes being given as poly1 and poly2 (see image))
This basically splits the polygon into two triangles 123 and 134 and for each one checks if the point is within the triangle (by checking if the the point lies on the same side of a baseline as the opposing triangle point (dot product of cross products) ... no check for 1->3 as this isn't necessary).

untested:

<?php 
    
public function isInPoly($x$y, array $poly1, , array $poly2)
    {

        
$x12 $poly1[1]['x'] - $poly1[0]['x'];
        
$x21 $poly1[0]['x'] - $poly1[1]['x'];
        
$x13 $poly2[1]['x'] - $poly1[0]['x'];
        
$x31 $poly1[0]['x'] - $poly2[1]['x'];
        
$x23 $poly2[1]['x'] - $poly1[1]['x'];
        
$x41 $poly1[0]['x'] - $poly2[0]['x'];
        
$x34 $poly2[0]['x'] - $poly2[1]['x'];
        
$x43 $poly2[1]['x'] - $poly2[0]['x'];
        
$x1p $x $poly1[0]['x'];
        
$x2p $x $poly1[1]['x'];
        
$x3p $x $poly2[1]['x'];
        
$x4p $x $poly2[0]['x'];

        
$y12 $poly1[1]['y'] - $poly1[0]['y'];
        
$y21 $poly1[0]['y'] - $poly1[1]['y'];
        
$y13 $poly2[1]['y'] - $poly1[0]['y'];
        
$y31 $poly1[0]['y'] - $poly2[1]['y'];
        
$y23 $poly2[1]['y'] - $poly1[1]['y'];
        
$y41 $poly1[0]['y'] - $poly2[0]['y'];
        
$y34 $poly2[0]['y'] - $poly2[1]['y'];
        
$y43 $poly2[1]['y'] - $poly2[0]['y'];
        
$y1p $y $poly1[0]['y'];
        
$y2p $y $poly1[1]['y'];
        
$y3p $y $poly2[1]['y'];
        
$y4p $y $poly2[0]['y'];

        return (( (
$x12*$y13 $y12*$x13)*($x12*$y1p $y12*$x1p) >= ) && ( ($x23*$y21 $y23*$x21)*($x23*$y2p $y23*$x2p) >= ) && ( ($x34*$y31 $y34*$x31)*($x34*$y3p $y34*$x3p) >= ) && ( ($x41*$y43 $y41*$x43)*($x41*$y4p $y41*$x4p) >= )) ? TRUE FALSE;
    }
?>

Attached images
inpoly2.jpg

<?php 
php
/*# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # *
#                                                                           #
#    This file contains a PHP adaptation of code (imagemap.c) originally    #
#         written by Brian J. Fox ([email protected]) for MetaHTML 5.01          #
#                                                                           #
#                   http://directory.fsf.org/GNU/metahtml.html              #
#                                                                           #
#    Draw a horizontal line from $X, $Y extending inf in the positive       #
#    X-axis. Count the number of times that line crosses the lines created  #
#    by connecting adjacent vertices of the polygon. If that number is      #
#    even, then $X, $Y is "outside" of the polygon, if odd, then "inside".  #
* # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #*/
class Point
{
    public 
$x;
    public 
$y;
};
function 
inPoly ($X$Y, array $points)
{
    
$min_x $max_x $min_y $max_y NULL;
    
    
# Count vertices
    
$vertices count($points);

    
# Close the polygon if it isn't already closed.
    
$i $vertices 1;
    if ((
$points[$i]->!= $points[0]->x) OR ($points[$i]->!= $points[0]->y))
    {
        ++
$i;
        ++
$vertices;
        
$points[$i] = new Point;
        
$points[$i]->$points[0]->x;
        
$points[$i]->$points[0]->y;
        
$points[$i 1] = NULL;
    }

    
# Now check to see if the point falls within the rectangle which encloses the entire polygon.
    # If not, it certainly isn't inside.
    
for ($i 0$points[$i] != NULL; ++$i)
    {
        
$min_x = (($min_x === NULL) OR ($points[$i]->$min_x)) ? $points[$i]->$min_x;
        
$min_y = (($min_y === NULL) OR ($points[$i]->$min_y)) ? $points[$i]->$min_y;
        
$max_x = (($max_x === NULL) OR ($points[$i]->$max_x)) ? $points[$i]->$max_x;
        
$max_y = (($max_y === NULL) OR ($points[$i]->$max_y)) ? $points[$i]->$max_y;
    }

    
# Is $X, $Y within the rectangle defined by $min_x, $max_y, $max_x, $min_y?
    
if (($X $min_x) OR ($X $max_x) OR ($Y $min_y) OR ($Y $max_y))
        return 
FALSE;

    
# The point falls within the polygon. Check adjacent vertices.
    
$lines_crossed 0;
    for (
$i 1$points[$i] != NULL; ++$i)
    {
        
$p1 =& $points[$i 1];
        
$p2 =& $points[$i];

        
$min_x min ($p1->x$p2->x);
        
$max_x max ($p1->x$p2->x);
        
$min_y min ($p1->y$p2->y);
        
$max_y max ($p1->y$p2->y);

        
# We need to know if the point falls within the rectangle defined by the maximum vertices of the vector.
        
if (($X $min_x) OR ($X $max_x) OR ($Y $min_y) OR ($Y $max_y))
        {
            
# Not within the rectangle. Great!
            # If it is to the left of the rectangle and in between the $Y then it crosses the line.
            
if (($X $min_x) AND ($Y $min_y) AND ($Y $max_y))
                ++
$lines_crossed;
            continue;
        }

        
# Find the intersection of the line -inf $Y, +inf, $Y] and $p1-x, $p1-y, $p2-x, $p2-y].
        # If the location of the intercept is to the right of $X, then the line will be crossed.
        
$slope = ($p1->$p2->y) / ($p1->$p2->x);
    
        if (((
$Y - ($p1->- ($slope $p1->x))) / $slope) >= $X)
            ++
$lines_crossed;
    }

    return (
$lines_crossed 1) ? TRUE FALSE;
}
?>

When I was talking to filur about this, quite a few years ago now, I think in around 2006, we decided that we where going to close the polygon ourselfs. I forgot about this, and so I did not do it in my last code update. I don't really expect for anyone else using this function to also have to worry about closing the polygon, so I found the basis of the Source that me and filur used and for this function and re did it in PHP again. This should provide us with a good answer providing that I take the node & node + 1 from the current cars position like you said to check if they are on the track.
Might be a little overkill, since the polygon will always be a trapezium, but this way it stays compatible in case one wishes to use other polygons ...
Saying this, it might be worth a try not to check only actual and following nodes, which might produce errors if one takes an extra turn (is the node changed, when I pass one in reverse?!?) but the entire track, which with your function should not be any problem ... all you'd have to check was if the car is within the polygon set by the outer boundary but not that set by the inner boundary.
Quote from avetere :but the entire track, which with your function should not be any problem ... all you'd have to check was if the car is within the polygon set by the outer boundary but not that set by the inner boundary.

Funny you should say that, as that's what I am attempting right now!
As I get deep and deeper into this, I'm seeing some of the power, and some of the problems with this setup. First off the problems. Where as I can provide it with a set of points, do I have to provide the inside edge first, then the outside?Should I make sure that I supply the left side of the road, then the right side should it be the other way around when the track is in reverse? Does any of this matter really?

The power of it really comes from the fact that no matter what layout is loaded, the forward or the reverse, I will be able to tell if they are on the track or not no matter the direction of the client. This becomes pretty important for the open track types should someone want to support them in the future. I don't have any plans right now of supporting unofficial tracks, however it should be pretty easy for someone else to make their own nodes should they need too using a path making plugin for PRISM.

On that subject, because I'm supporting making paths within PRISM, should someone need to for what ever reason. I'm going to be doing quite a large update to the PTH module, that will allow it to both read and write pth files. I'm also making it much more OOP, where everything within the object is an object. This mean that you can do $PTH->Nodes[$NodeID]->Limit->Left->toPoint(); or $PTH->roadToPoly(); This should provide for a pretty powerful syntax that is simply not seen within the LFS tool chest.
1

FGED GREDG RDFGDR GSFDG