snl3k4n
snl3k4n

Reputation: 31

Draw parallel lines along a path - PHP GD

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.

Upvotes: 3

Views: 895

Answers (2)

Wrikken
Wrikken

Reputation: 70510

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.

Upvotes: 2

CodeSmile
CodeSmile

Reputation: 64477

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

Upvotes: 0

Related Questions