UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Problem calculating angle

Forums > SwiftUI

Hi all,

Lately, I have been working on an app in which I use polygonal shapes.

This is the function that I use for drawing the shapes, in which points is the property that holds the CGPoints that are the corners of the shape:

func path(in rect: CGRect) -> Path {
        var path = Path()

        for point in points {
            if point == points.first {
                path.move(to: points.first!)
            } else {
                path.addLine(to: point)
            }
        }

        if path.isEmpty == false {
            path.addLine(to: points.first!)
        }

        return path
    }

I also made a function to calculate the angles, in which I use the cosine rule:

func getAngles() -> [ShapeAngle] {
        var result : [ShapeAngle] = []

        if points.count == 3 {
            for i in 0 ..< 3 {
                let a = getSideInfo()[i].length
                let b = getSideInfo()[i < 2 ? i + 1 : 0].length
                let c = getSideInfo()[i == 0 ? 2 : i - 1].length
                let alfa = acos((pow(a, 2) - pow(b, 2) - pow(c, 2)) / (-2 * b * c))

                result.append(ShapeAngle(degrees: alfa * 180 / Double.pi, rhsDistance: maxX - points[i == 0 ? 2 : i - 1].x, bottomDistance: maxY - points[i == 0 ? 2 : i - 1].y))
            }
        } else {
            for i in 0 ..< points.count {
                let a = sqrt(pow(abs(points[i > 0 ? i - 1 : points.count - 1].x - points[i < points.count - 1 ? i + 1 : 0].x), 2) + pow(abs(points[i > 0 ? i - 1 : points.count - 1].y - points[i < points.count - 1 ? i + 1 : 0].y), 2))
                let b = getSideInfo()[i].length
                let c = getSideInfo()[i > 0 ? i - 1 : points.count - 1].length
                let alfa = acos((pow(Double(a), 2) - pow(b, 2) - pow(c, 2)) / (-2 * b * c))

                result.append(ShapeAngle(degrees: alfa * 180 / Double.pi, rhsDistance: maxX - points[i].x, bottomDistance: maxY - points[i].y))
            }
        }

        return result
    }

Now the second function works totally fine, but it always calculates the smallest angle. (For instance, if the function should return 190 degrees, it will return 360 - 190 = 170 degrees.) So if an angle is more than 180 degrees, the function will return a wrong value.

This might be easy to fix, just by subtracting the value that the function returns from 360, but then I need to be able to check whether or not an angle should be more than 180 degrees. Does anyone have a clue on how to do this?

1      

I haven't found a solution yet, but I'm just wondering if there might be some sort of cosine rule or something similar that works for every polygon. I know this isn't a swift related question, but does anyone of you happen to know the answer to this question? Or should I ask my math teacher?

1      

hi Cas,

the law of cosines can be used with a triangle because all of the vertex angles (e.g., the one measured at B in a triangle ABC by using the length of the opposite side AC) are 180 degrees or less. in short, the line segment AC lies opposite the angle at B, on the same side of B where you measure the angle.

(BTW: the inverse cosine function is defined to return an angle between 0 and 180 degrees: that's the range where $$\theta = \arccos(\cos(\theta))$$ is valid.)

you can continue to apply the law of cosines correctly at any vertex of a convex polygon, because at a vertex B in the vertex sequence is ABC, the line segmant AC is interior to the polygon.

your computation of a vertex angle otherwise may be off with respect to 360 degrees ... exactly because given a vertex sequence ABC, the line segment AC you use to measure the angle (probably) lies outside the polygon.

  • if your polygon is self-intersecting, you could still have a problem; and even then, we don't know exactly where the rest of the polygon is in relation to A, B, and C.

in short, you need to know where the inside and the outside of the polygon are with respect the vertex B. that information is not directly available from the vertices A, B, and C.

one possibility i can think of:

  1. you could look along the line segment S between the midpoint of the line segment AC and the vertex B. if all points on that line segment lie outside the polygon, your angle computation is good; otherwise, you need to fix the angle with respect to 360 degrees.

of course, you can't check all points, but unless you have a wildly complicated polygon with thousands of vertices, it's probably enough to test a handful of points on the line segment S, or better, just one point on S that's "really close" to B (sort of a "within epsilon" distance away).

but how do you know what points are inside or outside the polygon?

you might look at the NSBezierPath class and its methods. one allows you to hit-test whether a point lies in the interior of the polygon. there may be other methods there to help as well ...

hope that helps,

DMG

2      

@delawaremathguy, thank you for your reaction!

1      

However, I still have a question:

In your reaction you say: "one possibility i can think of:

you could look along the line segment S between the midpoint of the line segment AC and the vertex B. if all points on that line segment lie outside the polygon, your angle computation is good; otherwise, you need to fix the angle with respect to 360 degrees."

Correct me if I'm wrong, but if all points on that line lie inside the polygon, my angle computation is good, right?

1      

hi,

yup, you're right ... i read that twice before posting, and still missed it. (apologies.) if all on S lie inside, the angle you want is in the range 0 to 180. if all lie outside, the interior angle (the one measured towards the interior) will be at least 180.

but again, i don't know exactly the type or complexity of the polygons you have. so don't turn the conditional statements above into equivalences. the angle you want could be less than 180, yet not all points on S will necessarily lie inside the polygonal region.

DMG

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.