|
| 1 | +// |
| 2 | +// LayerTree.Builder.Path.Arc.swift |
| 3 | +// SwiftDraw |
| 4 | +// |
| 5 | +// Created by Simon Whitty on 10/2/19. |
| 6 | +// Copyright © 2019 WhileLoop Pty Ltd. All rights reserved. |
| 7 | +// |
| 8 | +// Distributed under the permissive zlib license |
| 9 | +// Get the latest version from here: |
| 10 | +// |
| 11 | +// https://github.com/swhitty/SwiftDraw |
| 12 | +// |
| 13 | +// This software is provided 'as-is', without any express or implied |
| 14 | +// warranty. In no event will the authors be held liable for any damages |
| 15 | +// arising from the use of this software. |
| 16 | +// |
| 17 | +// Permission is granted to anyone to use this software for any purpose, |
| 18 | +// including commercial applications, and to alter it and redistribute it |
| 19 | +// freely, subject to the following restrictions: |
| 20 | +// |
| 21 | +// 1. The origin of this software must not be misrepresented; you must not |
| 22 | +// claim that you wrote the original software. If you use this software |
| 23 | +// in a product, an acknowledgment in the product documentation would be |
| 24 | +// appreciated but is not required. |
| 25 | +// |
| 26 | +// 2. Altered source versions must be plainly marked as such, and must not be |
| 27 | +// misrepresented as being the original software. |
| 28 | +// |
| 29 | +// 3. This notice may not be removed or altered from any source distribution. |
| 30 | +// |
| 31 | + |
| 32 | +import Foundation |
| 33 | + |
| 34 | +//converts DOM.Path.Arc -> LayerTree.Path.Cubic |
| 35 | + |
| 36 | +private extension Comparable { |
| 37 | + func clamped(to limits: ClosedRange<Self>) -> Self { |
| 38 | + return min(max(self, limits.lowerBound), limits.upperBound) |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +private func almostEqual<T: FloatingPoint>(_ a: T, _ b: T) -> Bool { |
| 43 | + return a >= b.nextDown && a <= b.nextUp |
| 44 | +} |
| 45 | + |
| 46 | +private func vectorAngle(ux: LayerTree.Float, uy: LayerTree.Float, vx: LayerTree.Float, vy: LayerTree.Float) -> LayerTree.Float { |
| 47 | + let sign: LayerTree.Float = (ux * vy - uy * vx) < 0.0 ? -1.0 : 1.0 |
| 48 | + let dot = (ux * vx + uy * vy).clamped(to: -1.0...1.0) |
| 49 | + return sign * acos(dot) |
| 50 | +} |
| 51 | + |
| 52 | +private extension LayerTree.Float { |
| 53 | + |
| 54 | + static var tau: LayerTree.Float { |
| 55 | + return .pi * 2 |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +private func getArcCenter(from origin: LayerTree.Point, to destination: LayerTree.Point, |
| 60 | + fa: Bool, fs: Bool, |
| 61 | + rx: LayerTree.Float, ry: LayerTree.Float, |
| 62 | + sin_phi: LayerTree.Float, |
| 63 | + cos_phi: LayerTree.Float) -> (point: LayerTree.Point, theta: LayerTree.Float, deltaT: LayerTree.Float) { |
| 64 | + |
| 65 | + let x1p = cos_phi * (origin.x - destination.x) / 2 + sin_phi * (origin.y - destination.y) / 2 |
| 66 | + let y1p = -sin_phi * (origin.x - destination.x) / 2 + cos_phi * (origin.y - destination.y) / 2 |
| 67 | + |
| 68 | + let rx_sq = rx * rx |
| 69 | + let ry_sq = ry * ry |
| 70 | + let x1p_sq = x1p * x1p |
| 71 | + let y1p_sq = y1p * y1p |
| 72 | + |
| 73 | + var radicant = max((rx_sq * ry_sq) - (rx_sq * y1p_sq) - (ry_sq * x1p_sq), 0.0) |
| 74 | + let radicantSign: LayerTree.Float = fa == fs ? -1.0 : 1.0 |
| 75 | + |
| 76 | + radicant /= (rx_sq * y1p_sq) + (ry_sq * x1p_sq) |
| 77 | + radicant = radicant.squareRoot() * radicantSign |
| 78 | + |
| 79 | + let cxp = radicant * rx / ry * y1p |
| 80 | + let cyp = radicant * -ry / rx * x1p |
| 81 | + |
| 82 | + let cx = cos_phi * cxp - sin_phi * cyp + (origin.x + destination.x) / 2 |
| 83 | + let cy = sin_phi * cxp + cos_phi * cyp + (origin.y + destination.y) / 2 |
| 84 | + |
| 85 | + let v1x = (x1p - cxp) / rx |
| 86 | + let v1y = (y1p - cyp) / ry |
| 87 | + let v2x = (-x1p - cxp) / rx |
| 88 | + let v2y = (-y1p - cyp) / ry |
| 89 | + |
| 90 | + let theta1 = vectorAngle(ux: 1, uy: 0, vx: v1x, vy: v1y); |
| 91 | + var delta_theta = vectorAngle(ux: v1x, uy: v1y, vx: v2x, vy: v2y); |
| 92 | + |
| 93 | + if (fs == false && delta_theta > 0) { |
| 94 | + delta_theta -= .tau; |
| 95 | + } |
| 96 | + if (fs == true && delta_theta < 0) { |
| 97 | + delta_theta += .tau; |
| 98 | + } |
| 99 | + |
| 100 | + return (point: LayerTree.Point(cx, cy), theta: theta1, deltaT: delta_theta) |
| 101 | +} |
| 102 | + |
| 103 | +private func approximateUnitArc(theta: LayerTree.Float, deltaT: LayerTree.Float) -> [LayerTree.Float] { |
| 104 | + let alpha = (4.0 / 3.0) * tan(deltaT / 4.0) |
| 105 | + |
| 106 | + let x1 = cos(theta) |
| 107 | + let y1 = sin(theta) |
| 108 | + let x2 = cos(theta + deltaT) |
| 109 | + let y2 = sin(theta + deltaT) |
| 110 | + |
| 111 | + return [x1, |
| 112 | + y1, |
| 113 | + x1 - y1 * alpha, |
| 114 | + y1 + x1 * alpha, |
| 115 | + x2 + y2 * alpha, |
| 116 | + y2 - x2 * alpha, |
| 117 | + x2, |
| 118 | + y2] |
| 119 | +} |
| 120 | + |
| 121 | + |
| 122 | +private func makePoint(x: LayerTree.Float, y: LayerTree.Float, rx: LayerTree.Float, ry: LayerTree.Float, cos_phi: LayerTree.Float, sin_phi: LayerTree.Float, center: LayerTree.Point) -> LayerTree.Point { |
| 123 | + |
| 124 | + let x1 = x * rx |
| 125 | + let y1 = y * ry |
| 126 | + |
| 127 | + let xp = cos_phi * x1 - sin_phi * y1 |
| 128 | + let yp = sin_phi * x1 + cos_phi * y1 |
| 129 | + |
| 130 | + return LayerTree.Point(xp + center.x, yp + center.y) |
| 131 | +} |
| 132 | + |
| 133 | +func makeCubic(from origin: LayerTree.Point, to destination: LayerTree.Point, |
| 134 | + large: Bool, sweep: Bool, |
| 135 | + rx: LayerTree.Float, ry: LayerTree.Float, |
| 136 | + rotation: LayerTree.Float) -> [(cp1: LayerTree.Point, cp2: LayerTree.Point, p: LayerTree.Point)] { |
| 137 | + |
| 138 | + let sin_phi = sin(rotation * .tau / 360.0) |
| 139 | + let cos_phi = cos(rotation * .tau / 360.0) |
| 140 | + |
| 141 | + let x1p = cos_phi * (origin.x - destination.x) / 2.0 + sin_phi * (origin.y - destination.y) / 2.0 |
| 142 | + let y1p = -sin_phi * (origin.x - destination.x) / 2.0 + cos_phi * (origin.y - destination.y) / 2.0 |
| 143 | + |
| 144 | + if almostEqual(x1p, 0.0) && almostEqual(y1p, 0.0) { |
| 145 | + return []; |
| 146 | + } |
| 147 | + |
| 148 | + if almostEqual(rx, 0.0) || almostEqual(ry, 0.0) { |
| 149 | + return []; |
| 150 | + } |
| 151 | + |
| 152 | + var rx1 = abs(rx) |
| 153 | + var ry1 = abs(ry) |
| 154 | + |
| 155 | + let lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry) |
| 156 | + if (lambda > 1.0) { |
| 157 | + let lambdaSquareRoot = lambda.squareRoot() |
| 158 | + rx1 *= lambdaSquareRoot |
| 159 | + ry1 *= lambdaSquareRoot |
| 160 | + } |
| 161 | + |
| 162 | + let cc = getArcCenter(from: origin, to: destination, fa: large, fs: sweep, rx: rx, ry: ry, sin_phi: sin_phi, cos_phi: cos_phi) |
| 163 | + |
| 164 | + var result = [[LayerTree.Float]]() |
| 165 | + |
| 166 | + let segments = max(Int(ceil(abs(cc.deltaT) / (LayerTree.Float.tau / 4.0))), 1) |
| 167 | + let deltaT = cc.deltaT / LayerTree.Float(segments) |
| 168 | + |
| 169 | + var theta1 = cc.theta |
| 170 | + |
| 171 | + for _ in 0..<segments { |
| 172 | + let unit = approximateUnitArc(theta: theta1, deltaT: deltaT) |
| 173 | + result.append(unit) |
| 174 | + theta1 += deltaT |
| 175 | + } |
| 176 | + |
| 177 | + return result.map { |
| 178 | + let cp1 = makePoint(x: $0[2], y: $0[3], rx: rx1, ry: ry1, cos_phi: cos_phi, sin_phi: sin_phi, center: cc.point) |
| 179 | + let cp2 = makePoint(x: $0[4], y: $0[5], rx: rx1, ry: ry1, cos_phi: cos_phi, sin_phi: sin_phi, center: cc.point) |
| 180 | + let p = makePoint(x: $0[6], y: $0[7], rx: rx1, ry: ry1, cos_phi: cos_phi, sin_phi: sin_phi, center: cc.point) |
| 181 | + |
| 182 | + return (cp1: cp1, cp2: cp2, p: p) |
| 183 | + } |
| 184 | +} |
0 commit comments