3

I need to draw something like a subway map (multiple routes along the same path) using PHP's image library. Here's an example:

  *********
  ******** *
  ******* * *
         * * *
          * * *
          * * *
          * * *
          * * *                      
          * * *                      
          * * *                      
           * * *                     
            * * *                    
             * * *                   
              * * ********************
               * *********************
                **********************

It's easy enough to draw one line along this path. I don't know how to draw multiple lines that follow the path but have an equal amount of space between them.

Sharjeel Aziz
  • 8,495
  • 5
  • 38
  • 37
snl3k4n
  • 31
  • 1

2 Answers2

2

For a given point A, and more lines through it, for the first points you'll have to decide whether points go 'inside'(B) the track, or 'outside'(C):

  ********C
  D******A *
  Q*****B * *
         * * *
          * E *

Now, you can calculate the offset of your point B to point A as a path from with length=offset (5px for instance) along the angle the which is half the clockwise angle between AE & AD for the 'inside' B (or the clockwise angle from AD to AE for the 'outside' C, or just use a negative offset later on). You'll want point B on a distance of 5px from A along the line through A with an angle angle AE + ((angle AD - angle AE) / 2)

I'm by no means a Math wiz, and the only time I needed to calculate angles like those were in javascript, I'll give it as an example, rewrite to PHP as you please (anybody who does know math, feel free to laugh & correct when needed):

var dx = b.x - a.x;
var dy = b.y - a.y;
if(dx == 0 && dy == 0){
    answer = 0;
} else if(dx > 0 && dy >= 0 ){
    answer = Math.atan(dy/dx);
} else if(dx <= 0 && dy > 0){
    answer = Math.atan(dx/dy) + (Math.PI * 0.5);
} else if(dx <= 0 && dy <= 0){
    answer = Math.atan(dy/dx) + Math.PI;
} else if(dx >= 0 && dy <= 0){
    answer = Math.atan(dy/dx) + (Math.PI * 1.5);
}

So, in a grid where D=(0,10),A=(10,10), E=(20,20):

  • The angle through AE = 45° (PI/4 rad),through AD = 180° (PI rad)
  • The angle through AB is then (45 + ((180-45)/2))=> 112.5° (5/8 PI rad)
  • 5px offset from A=(10,10) through angle 112.5° gives you this location for B:
    • Bx = Ax + (cos(angle) * 5) = +/- 8.1
    • By = Ay + (sin(angle) * 5) = +/- 14.6
  • At the 'sibling' point Q next to starting point D you have no previous path to reference / calculate an angle from, so I'd take the perpendicular: angle DQ = angle DA + 90° (PI/2 rad) (in the example you could just do Dy+5, but maybe you don't always start parallel to one of the 2 axis)
  • Rinse and repeat for all other points, draw lines between the calculated coordinates.
Wrikken
  • 69,272
  • 8
  • 97
  • 136
  • Determining which side a point lies is easier done with cross product. Here's and example: http://stackoverflow.com/questions/3461453/determine-which-side-of-a-line-a-point-lies – CodeSmile Jul 15 '12 at 13:39
  • @LearnCocos2D: erm.. this was a while ago (2 years nearly) but: where in this problem do you think we need to 'determine at which side' a point lays? We are not comparing the points here, we are actually making them... Making the point 'inside'/'right' or 'outside'/'left' is (1) relative to the previous line here but more importantly (2) a choice / user-decision made once in the beginning, and just the difference of using a positive (`+`) or negative (`-`) offset (in this example, 5px or -5px). Or am I missing something completely? – Wrikken Jul 15 '12 at 15:00
  • I do appreciate the link to the cross product BTW, reading up on it now, but I genuinely struggle to see it's relevance to the problem at hand, how does this help calculating the coords of point B? – Wrikken Jul 15 '12 at 15:03
  • I was referring to this quote: "for the first points you'll have to decide whether points go 'inside'(B) the track, or 'outside'©" I thought that's what you intended to do, determine the side where the points go. So I searched for that and found the line-side post. I assumed it does the same thing all the atan calculation did. That part seemed somewhat standalone and detached from the rest of the description, I wasn't even sure what it does really. For example, what information does the "answer" variable hold? – CodeSmile Jul 15 '12 at 19:05
  • The magic word is _decide_ there: the OP wants to draw a series of parallel lines / vectors next to an already existing set (`A`). The coords of the existing set are known, those of the new parallel set have to be calculated. The only user decision is to _decide_ whether the path should be the one through `B` or through `C`, the rest is just calculations. The `answer` in the js-match portion there is the angle in radians given 2 known coords, where order of the coords matter (i.e. a line through from `(1,1)` to `(2,2)` is another one then from `(2,2)` to `(1,1)`. – Wrikken Jul 15 '12 at 19:26
  • Hmmm, I also had an existing set and I needed to draw two parallel lines, the one to the left and the one to the right. I think there's a difference in requirements/goals here, but anyway, for whatever reason my solution works in my case and it's slightly different code. :) – CodeSmile Jul 15 '12 at 20:26
0

To complement Wrikken's answer, here's an actual code sample using Objective-C and the cocos2d-iphone engine reconstructed from this thread and others. The atan is not needed, instead the cross product is used, see the C function at the end of the code sample and this link.

I also simply switched the sign of the offset vector from A to B in order to get the vector from A to C. This avoids calling cosf/sinf twice.

PS: This code runs in a for loop from i = 0 to i < numVertices.

CGPoint splinePoint = splinePoints[i];

CGPoint prevPoint = (i == 0) ? splinePoint : splinePoints[i - 1];
CGPoint railPoint = splinePoint;
CGPoint nextPoint = (i == (numVertices-1)) ? splinePoint : splinePoints[i + 1];

CGPoint toPrevPoint = ccpSub(railPoint, prevPoint);
CGPoint toNextPoint = ccpSub(railPoint, nextPoint);
float angleToPrevPoint = ccpAngleSigned(kAngleOriginVector, toPrevPoint);
float angleToNextPoint = ccpAngleSigned(kAngleOriginVector, toNextPoint);
float offsetAngle = 0.0f;

if (i > 0 && i < (numVertices - 1))
{
    offsetAngle = angleToNextPoint + ((angleToPrevPoint-angleToNextPoint) / 2);
}
else if (i == 0)
{
    offsetAngle = angleToNextPoint + M_PI_2;
}
else
{
    offsetAngle = angleToPrevPoint + M_PI_2;
}

CGPoint offsetLeftRail, offsetRightRail, offsetRail;
offsetRail.x = cosf(offsetAngle) * railOffsetFromCenter;
offsetRail.y = sinf(offsetAngle) * railOffsetFromCenter;
offsetLeftRail = ccpAdd(railPoint, offsetRail);
offsetRightRail = ccpAdd(railPoint, ccpMult(offsetRail, -1.0f));

if (isPointToTheLeftOfLine(prevPoint, railPoint, offsetLeftRail))
{
    leftRailSplinePoints[i] = offsetLeftRail;
    rightRailSplinePoints[i] = offsetRightRail;
}
else
{
    leftRailSplinePoints[i] = offsetRightRail;
    rightRailSplinePoints[i] = offsetLeftRail;
}

BOOL isPointToTheLeftOfLine(CGPoint start, CGPoint end, CGPoint test)
{
    return ((end.x - start.x) * (test.y - start.y) -
            (end.y - start.y) * (test.x - start.x)) > 0;
}

This helped me to draw the rails on the railtrack:

enter image description here

Community
  • 1
  • 1
CodeSmile
  • 64,284
  • 20
  • 132
  • 217