December 22, 2024, 04:47:34

Author Topic: Code improving (check wether a ship is headed towards an object or not)  (Read 14424 times)

Offline Mularac

  • Lieutenant
  • ***
  • Posts: 531
  • Karma: 11
    • View Profile
I built this code for my latest mission as a way to check wheter a ship was headed towards an other or not (I've removed any way for the player to change the velocity, which is set always at 200)
The thing is, the code doesn't work too well. It often gives false readings, like 0, or very low numbers or too high ones.
I was hoping you guys could help me to improve it.

Code: [Select]
Rule event tick
name "HeadingCheck"
:action
Dist1:=Distance(m.al, m.ac); //first, we take the initial distance between the alkesh and the achilles
delay(0,0.5,
Dist2:=Distance(m.al, m.ac); //we wait half a second and then we take it again
dif:=dist1-dist2;  //and we compare and take the difference of those two distances
debug(dif);  //we show it on the console, for debugging purposes
if(Dist1-dist2>100,  //if the difference is higher than 100, it means that the distance between the two ships is decreasing rapidly, which means one thing: you're headed straight to the alkesh. Bad mojo, bro.
Debug("too close for confort");
counter2:=counter2+1;  //we take a counter of how many times this crossing appears
if(warn1!=1,
LocalEvent(TooCloseForConfort);  //if the difference is higher than 100 this event is issued (see that event for the use of the "warn" variable)
);
,  //this coma means "else"
if(dist1-dist2!=0&warn1=1&nonrepeated=1,  //sometimes the code malfunctions for some extrange reason and it gives a "0" reading, as both distances were the same, it also gives false readings (lower than the real deal)   
milestone:=counter2; //so, in order to counter that, we take a "milestone" of how many crossings took place and we re-check it 5 secons later
nonrepeated:=0; //this is meant so that this doesn't happen over and over again
delay(0,5,
Milestone2:=counter2;
if(milestone=milestone2,  //if the milestone 5 seconds later is the same as the one five seconds earlier, it means that "counter" hasn't changed, which means that no number higher than 100 has appeared, and then we can safely assume that the player has changed course (in order to come up with this I spent a long time looking at the debugging windown...)
ReRouted:=1;  //we "tell" the script that the ship has rerouted
);
nonrepeated:=1;  //with this we re-open the check for future porpuses
,0);
);

);
,0);
:end
END

tick 0.5





EDIT: Nevermind, solved it... I turned the tick rule into a regular one and added a localevent(*rule in question*) in the bottom of it to make it a little like a "tick" rule of my own, hehehehehe
« Last Edit: April 05, 2011, 04:34:44 by Mularac »

Offline Arparso

  • Administrator
  • Lieutenant
  • *****
  • Posts: 558
  • Karma: 14
  • I can see you...
    • View Profile
    • http://arparso.de/nexus
(No subject)
« Reply #1 on: January 08, 2010, 22:38:43 »
I'd guess, your false readings were caused by the delay function and because of variable sharing. Let me elaborate: your tick event runs every 0.5 seconds and stores the current distance in "Dist1". Then it orders some script commands to be run 0.5 seconds later using the delay function before finishing its job. So approximately 0.5 seconds later both the next tick event and your delayed commands get executed with the latter one measuring the distance again in Dist2 and comparing it to the old value in Dist1. There are two cases to think about now:

1) the delayed commands start first and the comparison works smoothly as expected... almost immediately afterwards the tick event gets to work, saves a new value in Dist1 and orders the next batch of delayed commands - everything works fine

2) the tick event manages to start first, saves a new value in Dist1 and orders the next batch of delayed commands... immediately afterwards the old batch of delayed commands get their CPU time, measure the current distance and compare that to to the NEW Dist1 the tick event just saved... which is the wrong one, obviously. That's why you get readings of 0 or other extremely small values

The second causes the problems here. Keep in mind, that both the tick event and the delay don't work THAT precise - you can't predict, when exactly both get executed... depending on the current CPU load and game situation, both the tick length and the delay amount will vary. It's generally best to avoid delays or overly complex tick commands because of that. That's actually a very common problem with multithreading in programming as well, which makes using multiple CPU cores to their fullest so hard to do.

I think, I've got a pretty simple and more robust sample measuring distance changes between both vessels:
Code: [Select]
TICK 0.5

RULE event Tick
    :action
        // store distance in temporary variable
        E.temp := distance(m.al, m.ac);
       
        // only compare the distances, if the lastDistance-variable has
        // already been initialized by our script
        If(lastDistance != 0,
            // store distance difference in local variable
            E.diff := lastDistance - E.temp;
            debug(E.diff);
        );
       
        // store the current distance in lastDistance-variable
        lastDistance := E.temp;
    :end
END

That code only uses one variable (lastDistance) to store the distances and avoid's delay entirely. The returned distance changes will not be constant, however, even with constant speeds and flight vectors - that's because Tick isn't executed exactly every 0.5 seconds... some pauses between Ticks just take longer than others. You can't really avoid that.

Offline Mularac

  • Lieutenant
  • ***
  • Posts: 531
  • Karma: 11
    • View Profile
(No subject)
« Reply #2 on: January 08, 2010, 23:07:43 »
yeah, I also think it has something to do with the lenght of the event... However, it got more complicated now, as I added the ships's current velocity to it, and using the "delay" command to re start the event instead of the "tick" solved the 0 thing and the extremely low values, however the values still fluctuate a bit, probably due to the delay command not working too well. I'll take a look at that now, see what I come up with.


EDIT: I'm sorry, but that way is no good, it fluctuates a lot. here's a log of what it read (constant speed and heading):
        233.5
        248.633
        250.914
        283.848
        167.789
        225.223
        252.934
        174.43
        226.711
        268.512
        238.773
        214.434
        205.113
        207.535
        181.633
        180.781

with my old code it only variated about 20 points top, here it gets up to 80. So it seems that a tick rule is not very reliable....

Offline Arparso

  • Administrator
  • Lieutenant
  • *****
  • Posts: 558
  • Karma: 14
  • I can see you...
    • View Profile
    • http://arparso.de/nexus
(No subject)
« Reply #3 on: January 09, 2010, 00:17:44 »
What exactly are you trying to achieve? If you only want to know, if ship A is getting nearer to ship B, you don't really need much precision... just seeing the distance getting bigger or smaller would be enough to do that - and we already have that kind of information.

Offline Mularac

  • Lieutenant
  • ***
  • Posts: 531
  • Karma: 11
    • View Profile
(No subject)
« Reply #4 on: January 09, 2010, 00:26:25 »
What I'm trying to do is a rule that can identify when youre headed towards a ship or not, and also, part of the rule is to detect when you are NOT headed towards that ship. And if the readings vary so much, it would either give me false positive or false negatives values.

On an other, completely unconnected question: is it posible to disable a fighter or commando's engines or other fighter device for that matter?

Offline Arparso

  • Administrator
  • Lieutenant
  • *****
  • Posts: 558
  • Karma: 14
  • I can see you...
    • View Profile
    • http://arparso.de/nexus
(No subject)
« Reply #5 on: January 09, 2010, 01:43:50 »
Quote
And if the readings vary so much, it would either give me false positive or false negatives values.
The readings may vary, but they're not wrong. My code snippet above will reliably tell you how much distance was gained or lost between both ships since the last Tick. If you only care about if a ship is coming closer or if it's gaining distance, then that's enough already. You won't get false positives or false negatives.

The varying readings are a direct result of Tick not getting called exactly every 0.5 seconds. There are lots of technical reasons for that... it's just the way the game (and actually PCs in general) works.

Of course, the whole measuring-the-change-in-distance-thing is kinda flawed anyway, because it's not precise enough. It just tells you, if the ship is currently coming any closer or not - but it doesn't tell you anything about it's current course and if it's really headed for that other ship. To know that, you'll need to examine the actual movement vectors... but the question remains, if you really need to do that!?

Quote
On an other, completely unconnected question: is it posible to disable a fighter or commando's engines or other fighter device for that matter?
Haven't tested it myself, but should be possible. Once they've undocked, fighters are mostly treated just like any other ship.

Offline Mularac

  • Lieutenant
  • ***
  • Posts: 531
  • Karma: 11
    • View Profile
(No subject)
« Reply #6 on: January 09, 2010, 03:52:57 »
It's part of a mission, and this is basically what I'm doing: I take a ship velocity and then compare it to the difference between the distances one second later, if the ship is moving towards the object i used to measure the distance, both readings should be similar (The difference is, in fact, arround 10 or so points lower, but that's pretty constant, regardless of the actual speed). The code works pretty well, and I didn't find a better way of doing... (I did thought of creating a sort of "path" made of navig points with small areas that got updated every second or so, and if the ship got near those areas a warning would be issued, but I dropped as it seemed to be too inefficient and complicated)

Offline The Old Dragon

  • Ensign
  • ***
  • Posts: 362
  • Karma: 6
    • View Profile
    • http://
(No subject)
« Reply #7 on: January 09, 2010, 10:29:29 »
Sorry if I muddy the waters here a little, but if you wish to know if you're travelling to or from a known object, why not simply measure the distance?

This seems to do the job to me...

         
Code: [Select]
RULE event Tick

:action

Rangeold:=Rangenew;

Debug("Old range is ",rangeold);

Rangenew:=Distance(M.PS,M.ES);
Debug("Distance between ships is ",Rangenew);

If(RangeOld>RangeNew,Debug("Danger! Danger Will Robinson!"));

:end //end action

END //end rule

TICK 3
Better to look the fool by asking, then prove them right with ignorance.

Offline Arparso

  • Administrator
  • Lieutenant
  • *****
  • Posts: 558
  • Karma: 14
  • I can see you...
    • View Profile
    • http://arparso.de/nexus
(No subject)
« Reply #8 on: January 09, 2010, 12:05:49 »
Yep, we're already doing that, more or less. However, if you really want to know, if ship A is directly approaching ship B, simple distance measuring isn't going to cut it. Ship A could be headed for a totally different destination and still come a little closer to ship B during its flight.

You could compare the measured distance to the velocity of the ship, to see if close to 100% of its thrust are used to approach ship B... but that assumes you know about the average velocity since the last tick and you can measure time precisely enough to know how much distance the ship should have travelled since the last tick. The first one can be achieved by setting the speed to a specific value and not allowing the player to change it. The second, however, is quite impossible, I fear. At least I don't know a good way to measure time in Nexus - Tick just isn't precise enough, as already shown.

The only reliable way of knowing, wether ship A is approaching ship B would be to have a look at its movement vector like, for example:
Code: [Select]
// calculates, if E.ship approaches E.target or departs from it
// returns 1 for approach, -1 for departing or 0 for every other course
RULE event GetShipApproachesShip
    condition IsValid(E.ship)&IsValid(E.target)
    :action
        // return 0, if we don't have a last position value for the ship
        if(E.ship:lastPosition = 0,
            return(0);
            , // else
            // calculate vector from ship to target
            E.targetVector := VNorm(VSub(E.target:position, E.ship:position));
            // calculate actual movement vector of ship
            E.actualVector := VNorm(VSub(E.ship:position, E.ship:lastPosition));
           
            // calculate dot product of both vectors
            // (equals cosine of angle between vectors)
            E.dot := VDot(E.targetVector, E.actualVector);                    
           
            // maximum allowed deviation from perfectly accurate approach/departing
            // lower margin means ships have to fly directly to the target to
            // count as "approaching", higher margin allows for more generous approaches
            E.margin := 0.1;
           
            if(E.dot + E.margin > 1,
                // approaching
                return(1);
                ,
                if(E.dot - E.margin < -1,
                    // departing
                    return(-1);
                    ,
                    // indifferent
                    return(0);
                );
            );
        );
        E.ship:lastPosition := E.ship:position;
    :end
END

You could use that rule as such:

Code: [Select]
TICK 1

RULE event Tick
    :action
        approaching := LocalEvent(GetShipApproachesShip, E.ship:=m.al; E.target:=m.ac);
        if(approaching = 1,
            Debug("Ship approaches target");
        );
    :end
END

Offline Mularac

  • Lieutenant
  • ***
  • Posts: 531
  • Karma: 11
    • View Profile
(No subject)
« Reply #9 on: January 09, 2010, 14:49:46 »
Of course... how didn't I see this before? It seems I'm starting to forget everything I saw in the entry course to college :P
If the vectors are perpendicular, the Vdot will be 0, as it would be the cosenus of 90, but you're also forgetting that the scalar product of two vectors is not only the cosenus of those vectors but also the product of the modules of those vectores, so the answer could very well be higher than 1. Or at least that's what I learned in college about this tipe of operation.
So in orther to make this code comple we will have to add this:

Code: [Select]
E.dot := VDot(E.targetVector, E.actualVector)/Vlen(E.targetVector).Vlen( E.actualVector);  

so it'd be like this:
lets say that e.targetvector=A and e.actualvector=B

the operation would be this:
e.dot=A.B/||A||.||B||=||A||.||B||.Cos(angle formed by A.B)/(||A||.||B||)
so in the end it would be:
e.dot=cos(angle formed by A.B)

Offline Arparso

  • Administrator
  • Lieutenant
  • *****
  • Posts: 558
  • Karma: 14
  • I can see you...
    • View Profile
    • http://arparso.de/nexus
(No subject)
« Reply #10 on: January 09, 2010, 15:28:05 »
Quote from: Mularac
If the vectors are perpendicular, the Vdot will be 0, as it would be the cosenus of 90, but you're also forgetting that the scalar product of two vectors is not only the cosenus of those vectors but also the product of the modules of those vectores, so the answer could very well be higher than 1. Or at least that's what I learned in college about this tipe of operation.
That's about right, except the part about me forgetting... :P

If you just take the dot product of two vectors, you'd be right, but if you're calculating the dot product of two normalised vectors, you'll just get the cosine of the angle and don't need any further calculation. Now guess, what VNorm() does in my sample script - it normalises the vectors before calculating the dot product ;)

I use that kind of operation quite often when working on shaders and have yet to see it going above 1.

Offline Mularac

  • Lieutenant
  • ***
  • Posts: 531
  • Karma: 11
    • View Profile
(No subject)
« Reply #11 on: January 09, 2010, 15:33:50 »
normalized vectors... haven't seen that yet in college. Does it mean that the module (lenght) of those vectors is 1?

Offline Arparso

  • Administrator
  • Lieutenant
  • *****
  • Posts: 558
  • Karma: 14
  • I can see you...
    • View Profile
    • http://arparso.de/nexus
(No subject)
« Reply #12 on: January 09, 2010, 15:34:39 »
Yes. To normalize a vector you just divide every vector component by the length of the vector and you'll get a vector with the same direction, but with a length of 1.

Offline Mularac

  • Lieutenant
  • ***
  • Posts: 531
  • Karma: 11
    • View Profile
(No subject)
« Reply #13 on: January 09, 2010, 15:51:57 »
well... it doesn't work, for some reason. E.actualVector is always 0. Apparently, somehow, e.ship:position and e.ship:lastposition represent the same value.

Nevermind... The values of the ship were inverted... m.ac is the ship and m.al is the target, and it was the other way round :P