8
\$\begingroup\$

These are the steps to determine coordinates of the 4 points (P1, P2, P3, P4) that make up a tangential trapezoid connecting to circles. Another way of looking at it is to think of the tangential segments of being the parts of a belt that would not be wrapped around the pulleys. The math should work regardless of the orientation of the two circles in coordinate space.

diagram of shapes and points

Code (usage @ bottom):

internal class TrapezoidBuilder
{
    private const double RadiansToDegrees = 180/Math.PI;
    private readonly double _bufferDistanceC0;
    private readonly double _bufferDistanceC1;

    private readonly Point _pointC0;
    private readonly Point _pointC1;
    public TrapezoidPoints TrapezoidPoints;

    public TrapezoidBuilder(Point pointC0, Point pointC1, double bufferDistanceC0, double bufferDistanceC1)
    {
        _pointC0 = pointC0;
        _pointC1 = pointC1;
        _bufferDistanceC0 = bufferDistanceC0;
        _bufferDistanceC1 = bufferDistanceC1;

        TrapezoidPoints = new TrapezoidPoints();

        CalculateTrapezoidPoints();
    }


    public void CalculateTrapezoidPoints()
    {
        // Get the angle of the line C0-C1 in degrees. This will be used in conjunction with angleA to determine the vector of these points
        double angleRelativeToPositiveXAxis = CalculateAngleRelativeToXAxis(_pointC0, _pointC1);

        // Get angleA
        double angleA = CalculateAngleA(_pointC0, _pointC1, _bufferDistanceC0, _bufferDistanceC1);

        ////  Calculate P1 and P2 coordinates first

        double positiveAngle = angleRelativeToPositiveXAxis + angleA;

        double cosPositiveAngle = Math.Cos(positiveAngle/RadiansToDegrees);

        double valueToAddToC0X = cosPositiveAngle*_bufferDistanceC0;

        // Set P1's X coordinate
        TrapezoidPoints.P1.X = _pointC0.X + valueToAddToC0X;

        double valueToAddToC1X = cosPositiveAngle*_bufferDistanceC1;

        // Set P2's X coordinate
        TrapezoidPoints.P2.X = _pointC1.X + valueToAddToC1X;

        double sinPositiveAngle = Math.Sin(positiveAngle/RadiansToDegrees);

        double valueToAddToC0Y = sinPositiveAngle*_bufferDistanceC0;

        // Set P1's Y coordinate
        TrapezoidPoints.P1.Y = _pointC0.Y + valueToAddToC0Y;

        double valueToAddToC1Y = sinPositiveAngle*_bufferDistanceC1;

        // Set P2's Y coordinate
        TrapezoidPoints.P2.Y = _pointC1.Y + valueToAddToC1Y;


        ////  Calculate P3 and P4 coordinates

        double negativeAngle = angleRelativeToPositiveXAxis - angleA;

        double cosNegativeAngle = Math.Cos(negativeAngle/RadiansToDegrees);

        valueToAddToC0X = cosNegativeAngle*_bufferDistanceC0;

        // Set P4's X coordinate
        TrapezoidPoints.P4.X = _pointC0.X + valueToAddToC0X;

        valueToAddToC1X = cosNegativeAngle*_bufferDistanceC1;

        // Set P3's X coordinate
        TrapezoidPoints.P3.X = _pointC1.X + valueToAddToC1X;

        double sinNegativeAngle = Math.Sin(negativeAngle/RadiansToDegrees);

        valueToAddToC0Y = sinNegativeAngle*_bufferDistanceC0;

        // Set P4's Y coordinate
        TrapezoidPoints.P4.Y = _pointC0.Y + valueToAddToC0Y;

        valueToAddToC1Y = sinNegativeAngle*_bufferDistanceC1;

        // Set P3's Y coordinate
        TrapezoidPoints.P3.Y = _pointC1.Y + valueToAddToC1Y;


        Debug.WriteLine("C0   " + _pointC0.X + "   " + _pointC0.Y);
        Debug.WriteLine("C1   " + _pointC1.X + "   " + _pointC1.Y);

        Debug.WriteLine("P1   " + TrapezoidPoints.P1.X + "   " + TrapezoidPoints.P1.Y);
        Debug.WriteLine("P2   " + TrapezoidPoints.P2.X + "   " + TrapezoidPoints.P2.Y);
        Debug.WriteLine("P3   " + TrapezoidPoints.P3.X + "   " + TrapezoidPoints.P3.Y);
        Debug.WriteLine("P4   " + TrapezoidPoints.P4.X + "   " + TrapezoidPoints.P4.Y);
    }

    private double CalculateAngleA(Point pointC0, Point pointC1, double radius0, double radius1)
    {
        double xDistance = pointC1.X - pointC0.X;
        double yDistance = pointC1.Y - pointC0.Y;

        double distance = Math.Sqrt((xDistance*xDistance) + (yDistance*yDistance));

        double radius2 = radius0 - radius1;

        double cosA = radius2/distance;

        double angleAInRadians = Math.Acos(cosA);

        double angleAInDegrees = angleAInRadians*RadiansToDegrees;

        return angleAInDegrees;
    }

    private double CalculateAngleRelativeToXAxis(Point point0, Point point1)
    {
        try
        {
            // In order to use ATAN2, point C1 has to be considered as the origin, i.e. 0, 0. 
            // So C1x is subtracted from C2x and C1y from C2y. Note that it’s important to subtract 
            // the 1st value from the 2nd to help determine which quadrant the angle is in.
            double x = point1.X - point0.X;
            double y = point1.Y - point0.Y;

            // Get the angle in radians
            double angleInRadians = Math.Atan2(x, y);

            // Convert to degrees
            double angleInDegrees = angleInRadians*RadiansToDegrees;

            // Subtract from 90 to get the angle relative to the positive X-axis
            double relativeAngleInDegrees = 90 - angleInDegrees;
            // Return result
            return relativeAngleInDegrees;
        }
        catch (Exception err)
        {
            Debug.WriteLine(err.Message);
        }

        // If no result, return zero
        return 0;
    }
}

internal class TrapezoidPoints
{
    public Point P1;
    public Point P2;
    public Point P3;
    public Point P4;
}


// Usage
Point C1 = new Point(5,7);
Point C2 = new Point(6.516, 7.875);
double buffer0 = 1;
double buffer1 = .375;

var trapezoidBuilder = new TrapezoidBuilder(C1, C2, buffer0, buffer1);
\$\endgroup\$
  • \$\begingroup\$ Great question but pseudo code is off-topic \$\endgroup\$ – AD7six May 18 '12 at 10:59
  • 1
    \$\begingroup\$ With all due respect (and I should have read the rules first), this policy isn't helpful. While I'm capable of posting actual code, it will become very specific (harder to wade through), will not provide a concrete example and would result in wasted effort if my general approach is wrong. \$\endgroup\$ – Stonetip May 18 '12 at 13:06
  • \$\begingroup\$ I'm just a member, but pseudo code has one huge disadvantage - you can't check if the code actually works or run it to ensure the changes you proposed would actually work. \$\endgroup\$ – AD7six May 18 '12 at 13:23
  • 1
    \$\begingroup\$ This problem is perfect for F#. I would start out by writing out the formulas in LaTeX. \$\endgroup\$ – Leonid May 18 '12 at 20:27
  • 1
    \$\begingroup\$ Uhm, what's the question being asked here? \$\endgroup\$ – miniBill Jul 11 '12 at 16:12
2
\$\begingroup\$
  1. It's not clear to me what are buffer0 and buffer1? If these represent the radius values (like C1-P1 line for buffer1), then perhaps radius0 (and radius1) would be a better name here.
  2. I'd create and use a data class for each point-radius pair. Say, InputPoint class with Point and Radius or something similar.
  3. I'd separate constructor and results. The results are calculations that should not be part of the constructor. Take, for example, the class named UriBuilder in .NET: you can construct it, change the inputs, and only when you call the property named Uri - you get the calculated uri. The same should apply here, too. Constructing a class gives us an instance with a valid state. Calculations - in their own methods or property-getters (that are practically methods, by the way). So TrapezoidPoints should be the returned type of a GetTrapezoid method (or maybe TryGetTrapezoid if this pattern apply here), and not part of the class' state.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.