Search code examples
swiftxcodemathuibezierpath

How do I compute the intersection of two circle's third set of coordinates under these conditions?


The small circle can move left and right by any amount and I have to calculate the red dot’s coordinates wherever its location is, if they intersect. I only calculate this under that condition. I must find the intersection and be sure that it is the intersection on the red dot, and not the intersection below it, so always the one with the higher Y value.

I have solved for all the distances of the triangles and blue dots. How do I compute the red point?

enter image description here

If you want to look at my current code to help debug it, try this.

My Playground To Test:

//: Playground - noun: a place where people can play

import UIKit
infix operator **

let pretendWidth: CGFloat = 374
let pretendHeight: CGFloat = 7

// Testing scenario is pretendWidth..<(pretendWidth + (pretendHeight / 2))
let spacer: CGFloat = 0.5

extension CGFloat {


    public static func **(base: CGFloat, exp: CGFloat) -> CGFloat {
        return CGFloat(pow(Double(base), Double(exp)))
    }

}

class BottomBarGradientNode: UIView {

    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        context.saveGState()
        context.clip(to: bounds)

        // Gradient Creation
        let locations: [CGFloat] = [0, 1]
        let components: [CGFloat] = [0.2706, 0.6863, 0.8902, 1, 0, 0.8745, 0.7294, 1]
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        let gradient: CGGradient = CGGradient(colorSpace: colorSpace, colorComponents: components, locations: locations, count: 2)!
        let startPoint = CGPoint(x: bounds.maxX, y: bounds.maxY)
        let endPoint = CGPoint(x: bounds.minX, y: bounds.minY)


        let halfHeight = bounds.height / 2
        let path = UIBezierPath()
        let startPointForPath = CGPoint(x: bounds.width - halfHeight, y: 0)
        path.move(to: startPointForPath)
        let firstCenterPoint = CGPoint(x: bounds.width - halfHeight, y: halfHeight)
        let secondCenterPoint = CGPoint(x: pretendWidth - bounds.height, y: 0)
        Computation: if bounds.width > (pretendWidth + halfHeight) {
            path.addArc(withCenter: secondCenterPoint, radius: bounds.height, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: true)
        } else if bounds.width > pretendWidth {
//
// ------------------------------------------------------------------------------------------------------------------------------------
//            Though this looks like a complicated geometry problem, this is really best done as an ugly algebra problem.
//            We want the coordinates of the red dot: (x,y)
//            We know the red dot is on the big circle and since that circle is not moving I'm going to call it's center (0,0) thus:
//            x^2 + y^2 = 49
//            We also know that the red dot is on the little circle, it has a moving center but we know that the y value for that
//            center is always -3.5. so we'll let the x-value of that center be t:
//            (x-t)^2 + (y-3.5)^2 = (3.5)^2
//            which expands to:
//            x^2 - 2xt + t^2 + y^2 -7y + (3.5)^2 = (3.5)^2
//            which when we plug in our other equation simplifies to:
//            y = (1/7)(-2tx + 49 + t^2)
//            plugging that back into the first equation gives:
//            x^2 + ((1/7)(-2tx + 49 + t^2))^2 = 49
//            which is terrible to look out but turns out to be a quadratic equation in x, so from this point you'd just simplify
//            and plug it into the quadratic formula. Pick the value of x that is smaller in magnitude (be careful about negatives
//            here). Then plug that x back into the first equation to solve for y.
// ------------------------------------------------------------------------------------------------------------------------------------
//
            let boundsHeightSquared = bounds.height ** 2
            let distanceFromOtherCenter = firstCenterPoint.x - secondCenterPoint.x

            // x^2 + ((1/7)(-2tx + 49 + t^2))^2 = 49 <<<< translates to VVVVVV
            //
            // ((4/49)t^2 + 1)(x^2) + (-4t - (4t^3/49))(x) + (2t^2 + (t^4)/49) = 0
            // ^^^^^^^^^^^^^^^        ^^^^^^^^^^^^^^^^^      ^^^^^^^^^^^^^^^^^
            //     value1(a)               value2(b)             value3(c)
            let value1 = ((4 * (distanceFromOtherCenter ** 2)) / boundsHeightSquared) + 1
            let value2 = (-4 * distanceFromOtherCenter) - ((4 * (distanceFromOtherCenter ** 3)) / boundsHeightSquared)
            let value3 = (2 * (distanceFromOtherCenter ** 2)) + ((distanceFromOtherCenter ** 4) / boundsHeightSquared)

            let (first, second) = getQuadraticValues(a: value1, b: value2, c: value3)
            // guarentee positive values
            var areBothGreaterThanZero: Bool = false
            var chosenX: CGFloat!
            if first < 0 { chosenX = second }
            else if second < 0 { chosenX = first }
            else { chosenX = first < second ? first : second; areBothGreaterThanZero = true }

            // y = (1/7)(-2tx + 49 + t^2)
            var chosenY = (1 / bounds.height) * ((-2 * distanceFromOtherCenter * chosenX) + boundsHeightSquared - (distanceFromOtherCenter ** 2))

            // last check on weird values
            if chosenY < 0 && areBothGreaterThanZero {
                chosenX = first < second ? first : second
                chosenY = (1 / bounds.height) * ((-2 * distanceFromOtherCenter * chosenX) + boundsHeightSquared - (distanceFromOtherCenter ** 2))
            }

            // Computatation failed. Show full segment.
            if chosenY < 0 {
                print("Computation Failed")
                path.addArc(withCenter: secondCenterPoint, radius: bounds.height, startAngle: 0, endAngle: CGFloat.pi / 2, clockwise: true)
                break Computation
            }

            // true point
            let intersectingPoint = CGPoint(x: chosenX + secondCenterPoint.x, y: chosenY)

            // c^2 = a^2 + b^2 - 2abCOS(C)
            // (a^2 + b^2 - c^2) / 2ab = COS(C)
            let topPoint = CGPoint(x: firstCenterPoint.x, y: 0)
            // compute c (distance)
            let firstDistanceBetweenPoints = getDistanceBetweenTwoPoints(firstPoint: intersectingPoint, secondPoint: topPoint)
            // where a and b are halfHeight
            let firstCosC = getCosC(a: halfHeight, b: halfHeight, c: firstDistanceBetweenPoints)
            let firstAngle = acos(firstCosC)
            path.addArc(withCenter: firstCenterPoint, radius: halfHeight, startAngle: CGFloat.pi * 1.5, endAngle: CGFloat.pi * 1.5 + firstAngle, clockwise: true)

            // c^2 = a^2 + b^2 - 2abCOS(C)
            // (a^2 + b^2 - c^2) / 2ab = COS(C)
            let lastPoint = CGPoint(x: pretendWidth, y: 0)
            // compute c (distance)
            let secondDistanceBetweenPoints = getDistanceBetweenTwoPoints(firstPoint: lastPoint, secondPoint: intersectingPoint)
            // where a and b are bounds.height
            let secondCosC = getCosC(a: bounds.height, b: bounds.height, c: secondDistanceBetweenPoints)
            let secondAngle = acos(secondCosC)
            path.addArc(withCenter: secondCenterPoint, radius: bounds.height, startAngle: secondAngle, endAngle: CGFloat.pi / 2, clockwise: true)
        } else {
            path.addArc(withCenter: firstCenterPoint, radius: halfHeight, startAngle: CGFloat.pi * 1.5, endAngle: CGFloat.pi / 2, clockwise: true)
        }
        path.addLine(to: CGPoint(x: bounds.height, y: bounds.height))
        let finalCenterPoint = CGPoint(x: bounds.height, y: 0)
        path.addArc(withCenter: finalCenterPoint, radius: bounds.height, startAngle: CGFloat.pi / 2, endAngle: CGFloat.pi, clockwise: true)
        path.addLine(to: startPointForPath)
        path.close()
        path.addClip()
        context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: .drawsAfterEndLocation)
        context.restoreGState()
    }

}

func getDistanceBetweenTwoPoints(firstPoint: CGPoint, secondPoint: CGPoint) -> CGFloat {
    let diffX = (firstPoint.x - secondPoint.x) ** 2
    let diffY = (firstPoint.y - secondPoint.y) ** 2
    return sqrt(diffX + diffY)
}

func getSlopeBetweenTwoPoints(firstPoint: CGPoint, secondPoint: CGPoint) -> CGFloat {
    let diffY = firstPoint.y - secondPoint.y
    let diffX = firstPoint.x - secondPoint.x
    return diffY / diffX
}

func getHypotenuse(firstDistance: CGFloat, secondDistance: CGFloat) -> CGFloat {
    return sqrt((firstDistance ** 2) + (secondDistance ** 2))
}

func getQuadraticValues(a: CGFloat, b: CGFloat, c: CGFloat) -> (CGFloat, CGFloat) {
    let first = (-b + sqrt((b ** 2) - (4 * a * c))) / (2 * a)
    let second = (-b - sqrt((b ** 2) - (4 * a * c))) / (2 * a)
    return (first, second)
}

func getCosC(a: CGFloat, b: CGFloat, c: CGFloat) -> CGFloat {
    // (a^2 + b^2 - c^2) / 2ab = COS(C)
    return ((a ** 2) + (b ** 2) - (c ** 2)) / (2 * a * b)
}

// Testing scenario is pretendWidth..<(pretendWidth + (height / 2))
let bounds = CGRect(x: 0, y: 0, width: pretendWidth + spacer, height: pretendHeight)
let bar = BottomBarGradientNode(frame: bounds)

Solution

  • Find both points of intersection then pick the appropriate one. Or formulate solution in terms of y coordinate then pick higher solution there to compute x coordinate for that.

    The equation of a circle 1 is (x2+y2)+a1x+b1y+c1=0. Write both circles in this form, then subtract one equation from the other. The quadratic terms will cancel out, and the remaining equation describes the radical axis of the circles. ax+by+c=0 where a=a1a2 and so on. Solve for x=−(by+c)/a. Plug this term into one of the original equations for the circle, and you have a quadratic equation in y.

    Now a quadratic equation in y is of the form py2+qy+r=0 and has solutions −q±sqrt(q2−4pr)/2p. Look at the sign of p, then pick that same sign in front of the square root to get the solution with larger y value. Plug that back into the equation of the radical axis to compute the x coordinate.

    If there is no intersection, q2−4pr < 0 and your solutions would become complex. If a=0 your radical axis is horizontal so you can't parametrize it by y value, and picking a solution by y value doesn't make any sense.