Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make clock ticks stuck to the clock border?

I try to make a clock, in swift, but now i want to make something strange. I want make border radius settable. This is the easy part (is easy because I already did that). I drew 60 ticks around the clock. The problem is that 60 ticks are a perfect circle. If I change the border radius I obtain this clock:

enter image description here

All ticks are made with NSBezierPath, and code for calculate position for every tick is :

tickPath.moveToPoint(CGPoint(
            x: center.x + cos(angle) *  point1 ,
            y: center.y + sin(angle) * point1
            ))

        tickPath.lineToPoint(CGPoint(
            x: center.x + cos(angle) * point2,
            y: center.y + sin(angle) * point2
            ))

point1 and point2 are points for 12 clock tick.

My clock background is made with bezier path:

 let bezierPath = NSBezierPath(roundedRect:self.bounds, xRadius:currentRadius, yRadius:currentRadius) 

currentRadius - is a settable var , so my background cam be, from a perfect circle (when corner radius = height / 2) to a square (when corner radius = 0 ).

Is any formula to calculate position for every tick so, for any border radius , in the end all ticks to be at same distance to border ?

like image 731
C-Viorel Avatar asked Jan 20 '26 16:01

C-Viorel


1 Answers

The maths is rather complicated to explain without recourse to graphics diagrams, but basically if you consider a polar coordinates approach with the origin at the clock centre then there are two cases:

  • where the spoke from the origin hits the straight side of the square - easy by trigonometry
  • where it hits the circle arc at the corner - we use the cosine rule to solve the triangle formed by the centre of the clock, the centre of the corner circle and the point where the spoke crosses the corner. The origin-wards angle of that triangle is 45º - angleOfSpoke, and two of the sides are of known length. Solve the cosine equation as a quadratic and you have it.

This function does it:

func radiusAtAngle(angleOfSpoke: Double, radius: Double, cornerRadius: Double) -> Double {
    // radius is the half-width of the square, = the full radius of the circle
    // cornerRadius is, of course, the corner radius. 
    // angleOfSpoke is the (maths convention) angle of the spoke
    // the function returns the radius of the spoke.

    let theta = atan((radius - cornerRadius) / radius) // This determines which case

    let modAngle = angleOfSpoke % M_PI_2 // By symmetry we need only consider the first quadrant

    if modAngle <= theta { // it's on the vertical flat
        return radius / cos(modAngle)
    } else if modAngle > M_PI_2 - theta { // it's on the horizontal flat
        return radius / cos(M_PI_2 - modAngle)
    } else { // it's on the corner arc
        // We are using the cosine rule to solve the triangle formed by
        // the clock centre, the curved corner's centre,
        // and the point of intersection of the spoke.
        // Then use quadratic solution to solve for the radius.
        let diagonal = hypot(radius - cornerRadius, radius - cornerRadius)
        let rcosa = diagonal * cos(M_PI_4 - modAngle)
        let sqrTerm = rcosa * rcosa - diagonal * diagonal + cornerRadius * cornerRadius
        if sqrTerm < 0.0 {
            println("Aaargh - Negative term") // Doesn't happen - use assert in production
            return 0.0
        } else {
            return rcosa + sqrt(sqrTerm) // larger of the two solutions
        }
    }
}

In the diagram OP = diagonal, OA = radius, PS = PB = cornerRadius, OS = function return, BÔX = theta, SÔX = angleOfSpoke

Diagram of solution

like image 178
Grimxn Avatar answered Jan 23 '26 13:01

Grimxn