Home > Delphi, Object Pascal, OP4JS, Smart Mobile Studio > Plotting angles in Object Pascal

Plotting angles in Object Pascal

February 12, 2015 Leave a comment Go to comments

From time to time someone hears that I’m a software engineer, and immediately they add to the conversation “oh you must be so good at mathematics, I suck at Maths”. I wish! In fact my math skills are ordinary at best. Like most creative individuals I tend to only learn something when I really need it. And sadly this was a personality trait even in highschool so I had a hard time surviving Math classes (or the teachers had a hard time dealing with me is more precise, I got expelled for beating up two of them).

The result is that whenever I face a trigonometry problem, I really am the mercy of the internet and friends. So I have decided to do something about this. I mean, I am 41 years of age after all. So off I go to Amazon.com to order a “trig for dummies” book, as I scavenger my son’s math curriculum for clues 🙂

Why trig?

From what I understand, programmers seem to be divided in two groups; The first group believes angular calculations is a waste of time and should be avoided if you can, because interpolation delivers more or less the same results but at a fraction (pun intended) of the cpu cost. The second group like to apply “real world maths” onto the flat landscape of Our cardassian coordinate system, which costs more but tend to deliver accurate results down to the last pixel.

The problem for a person who doesn’t really know that much about trigonometry, is that: if you don’t know how to do it properly, then you can’t possibly know how to derive an alternative. In other words, how can I write code which yields the same results if I have no clue about either Method?

Oh, and I’m on a new PC which uppercases every single word by force, so if this post looks a mess – that’s why. Windows 8.1 is just the gift that keeps on giving..

Hands on stuff

Imagine for a second that you want to create a shooting game. You have a top-down view of a battlefield and some mini-soldiers running around at your command. Whenever you click an enemy on the map, your soldiers should attempt to get within range of the enemy, and then start shooting. But that doesnt mean every single shot actually hit’s the target.

What if the sniper misses? Where does the bullet go?

What if the sniper misses? Where does the bullet go?

Now shooting “in a direction” is not as easy as it sounds. If your soldier is positioned at 100,100 on-screen – and your target is at 260,49 “or there about”, it wouldn’t be much of a game if your soldier hits the target every single time. Also, the enemy soldier will be moving away from the hit-zone and shoot back.

Another thing to consider is the strength of the weapon. The bullet should, when missing it’s target, continue on its trajectory. It doesn’t fall dead to the floor when it reaches the x2,y2 region of the map.

So how do we calculate such a shot?

Well, in my head the first thing that came up was:

  • Find the angle at which the enemy is positioned (pt2) from the soldier (pt1)
  • Calculate a path from p1 THROUGH p2, continuing forever; Affected by speed, wind, resistance and the size of the map

Naturally the calculation of the path should not be “pre calculated”, that was just the first way for me to approach the problem. All games have a main-loop and each element on the screen is only slightly updated per-frame. When this happens very fast the game comes to life.

Interpolation

Using interpolation means that you update the position of the bullet by fractions. More or less dividing the total difference between start and stop into smaller “chunks” –and then iterating through these steps until you reach the goal. This allows us to calculate an array of TPointF (floating point x,y record) which goes from start-position towards the end-position. Should it miss the target it will simply continue past the original target co-ordinate and ultimately go out of sight. At which point the game-engine can choose to either just drop the sprite object all together, or allow it to continue without being rendered until it’s velocity results in a halt (or you hit something else). At which point it should also be eliminated from the update-loop).

This would be a quick and dirty interpolated Version (thanks for the example Primoz!):

function TForm1.BuildList(x1,y1,x2,y2:float):Array of TPointF;
var
  x:    Integer;
  mlen: Integer;
begin
  (* Single point? *)
  if (x1=x2) and (y1=y2) then
  begin
    result.add(TPointF.Create(x1,y1));
  end else
  begin
    (* Add start point *)
    result.add(TPointF.Create(x1,y1));

    (* Calculate axis length.
       Note:  This controls the amount of distance you
       expand from the original position towards the target *)
    mlen:= round( (abs(x1-x2) + abs(y1-y2))  );

    (* calculate mid-part *)
    for x:=1 to mLen do
    result.add(TpointF.Create
    (
      x1 + x/100 * (x2-x1),
      y1 + x/100 * (y2-y1)
    ));

    (* Add end-point *)
    result.add(TPointF.Create(x2,y2));
  end;
end;

Please note that this is just quick and dirty code. It pre-calculates a given path from X1,Y1 through X2,y2; which is not something you want to use in a game. There you want to have fast and small calculations per item which is repeated in sync With the screen-redraw.

But this solves my problem quite elegantly: being able to derive a position through a general direction.

Using angles

Angles are different, here I can use the initial idea on how to solve it: namely to first get the angle Pt2 is in conjunction With Pt1 – and then calculate a path through that angle.

function AngleOfLine(const P1, P2: TPointF): Double;
begin
  Result := RadToDeg(ArcTan2((P2.Y - P1.Y),(P2.X - P1.X)));
  if Result < 0 then
  Result += 360;
end;

function TForm1.BuildList(x1,y1,x2,y2:float):Array of TPointF;
var
  Angle:  Float;
  start,stop:  TPointF;
  x:  Integer;
begin
  //build and add starting point
  start:=TPointF.Create(x1,y1);
  result.add(start);

  //build end-point record
  stop:=TPointF.Create(x2,y2);

  //Get angle of PT2 in context With PT1
  Angle:=AngleOfLine(start,stop);

  //Generate 100 Points along angle
  for x:=1 to 100 do
  begin
    X2 := X1 + X * COS(DegToRad(Angle));
    Y2 := Y1 + X * SIN(DegToRad(Angle));
    result.add(TpointF.Create(x2,y2));
  end;

  result.add(stop);
end;

Classifying it

Right, with those two Experiments behind me, I took the time to extract what I have learned into a record which represents a position. This record has functions which allows you to update the position using a next() mechanism – so you can quickly update a bunch of bullets in a main-game-loop.

This allows us to not only keep track of the position, but also to calculate the next step with a single call. Turning this into a class, or using it as a property of a “bullet” class should be easy enough later.

type
  TQTXPosition = record
    Start:            TPointF; //Where the bullet is shot from
    Target:           TPointF; //Where the bullet is shot towards
    Position:         TPointF; //Current position of bullet
    Iterator:         Integer; //iterator value
    DistanceToTarget: Float;   //Approx distance between start and target

    function  Beyond:Boolean;

    function  Next:TPointF;
    function  Create(x1,y1,x2,y2:Float):TQTXPosition;overload;
    function  Create(aStart,aTarget:TPointF):TQTXPosition;overload;
  end;

//#############################################################################
// TQTXPosition
//#############################################################################

function TQTXPosition.Create(x1,y1,x2,y2:Float):TQTXPosition;
begin
  result.start.x:=x1;
  result.start.y:=y1;
  result.target.x:=x2;
  result.target.y:=y2;
  result.iterator:=0;
  result.Position.X:=0;
  result.Position.Y:=0;

  result.DistanceToTarget:= round((
    abs(result.start.x-result.target.x)
  + abs(result.start.y-result.target.y)));
end;

function TQTXPosition.Create(aStart,aTarget:TPointF):TQTXPosition;
begin
  result.start:=aStart;
  result.target:=aTarget;
  result.iterator:=0;
  result.Position.X:=0;
  result.Position.Y:=0;

  result.DistanceToTarget:= round((
    abs(result.start.x-result.target.x)
  + abs(result.start.y-result.target.y)));
end;

function TQTXPosition.Beyond:Boolean;
begin
  result:=Iterator > DistanceToTarget;
end;

function TQTXPosition.Next:TPointF;
begin
  inc(Iterator);
  Position.X:= Start.x + Iterator/100 * (Target.x-Start.x);
  Position.Y:= Start.y + Iterator/100 * (Target.y-Start.y);
  result:=Position;
end;

The “NeXT” function simply adds 1 to the iterating value, calculates the NeXT position, and Returns it. You may want to place this in a real class to make it more smooth to work With. The constructors allows you to define where to start and the target aim. The Beyond() function Returns true if the bullet has passed the target aim, and the DistanceToTarget Field represents the approx distance from start to aim.

Again, this is quick and dirty stuff – but for a novice in trig’ like myself it’s easier to understand the idea of “angle” as “slant towards” and radians as “reaching out towards a circumference”. Well, I just ordered my “for dummies” book and will hopefully get this under wraps. Special thanks to Eric, Primoz and Jørn for giving me extremely simple starter pointers and one-liner examples of various topics. Suddenly what used to be very booring at school is becoming exciting and useful as an adult 🙂

Never to old to learn new stuff!

Vector math’s is what seems to be the best way to solve most of this, so that’s on my list.

 Tip for teachers

There will always be young boys that hates theory. Most of these kids are visual and tend to approach problems by “seeing” them mentally, and as such pure theory is regarded (or experienced) as the most boring thing in the universe. But the same kids, often creative and inventive, loves video games!

So my tip for teachers who have students like that (or as wild as I was) is to try to find a context for knowledge that these kids can find useful. Had someone told me in school that I could use this to create games – I would have consumed the books as fast as i could, because coding games and demo’s was something I really loved doing.

In fact, co-sinus and sinus was something I got to grips with when coding a “sinus scroll text” at early high-school. But like all kids I was under the impression that adults had it wrong and we had nothing to learn from them.

So if you are a teacher, try to make math’s more interesting by connecting it with computer games and coding (if the pupil responds to that). Who knows, one of your worst pupils may turn out to be the one who actually uses what you taught decades lates 😉

Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: