Как нормализовать строку переменной ширины UIBezierPath? - PullRequest
0 голосов
/ 06 мая 2020

Итак, у меня есть простой фрагмент кода, в котором я пытаюсь нарисовать линию переменной ширины с помощью UIBezierPath. Из коробки у нас нет такой возможности. Мы можем использовать только stati c ширину линии для каждого сегмента. Я нашел хороший учебник, в котором объясняется, как мы можем сделать это по-другому. Поэтому я изменил этот код и застрял в ситуации, когда моя изогнутая линия перекрывается.

enter image description here Я знаю, почему это произошло, потому что у нас нет дополнительных точек, потому что наш угол может быть более плавным.

Я добавил красные кружки, чтобы увидеть, где мы получили точки касания из осыпи.

И вопрос. Есть идеи, как я могу "нормализовать" эти углы, добавив дополнительные точки или убрав лишние точки?

Пример кода:

import UIKit

class ViewController: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .lightGray
  }

  var pts = Array(repeating: CGPoint.zero, count: 5)
  var ctr = 0
  var pointsBuffer = Array(repeating: CGPoint.zero, count: 100)
  var dispatchQue: DispatchQueue!
  var bufIdx = 0

  var isFirstTouchPoint: Bool = false
  var lastSegmentOfPrev: LineSegment!

  struct LineSegment {
    var firstPoint: CGPoint = .zero
    var secondPoint: CGPoint = .zero
  }

  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let point = touches.first?.location(in: view) else {return}
    ctr = 0
    bufIdx = 0;
    pts[0] = point
    isFirstTouchPoint = true
  }

  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let point = touches.first?.location(in: view) else {return}

    let circleLayer = CAShapeLayer()
    let radius: CGFloat = 2.0
    circleLayer.path = UIBezierPath(roundedRect: CGRect(x: point.x, y: point.y, width: 1.0 * radius, height: 1.0 * radius), cornerRadius: radius).cgPath

    circleLayer.fillColor = UIColor.red.cgColor
    self.view.layer.addSublayer(circleLayer)

    ctr += 1
    pts[ctr] = point
    if ctr == 4 {
      pts[3] = CGPoint(x: (pts[2].x + pts[4].x)/2.0,
                       y: (pts[2].y + pts[4].y)/2.0)

      for i in 0..<4 {
        pointsBuffer[bufIdx + i] = pts[i]
      }

      bufIdx += 4

      dispatchQue = DispatchQueue(label: "some.queue")
      dispatchQue.async {

        let offsetPath = UIBezierPath()
        if self.bufIdx == 0 {
          return
        }

        var ls = [LineSegment](repeating: LineSegment(), count: 4)

        for i in stride(from: 0, to: self.bufIdx, by: 4) {
          if self.isFirstTouchPoint {
            ls[0] = LineSegment(firstPoint: self.pointsBuffer[0], secondPoint: self.pointsBuffer[0])
            offsetPath.move(to: ls[0].firstPoint)
            self.isFirstTouchPoint = false
          } else {
            ls[0] = self.lastSegmentOfPrev
          }

          ls[1] = self.lineSegmentPerpendicular(to: LineSegment(firstPoint: self.pointsBuffer[i],
                                                                secondPoint: self.pointsBuffer[i + 1]), ofRelativeLength: 5)
          ls[2] = self.lineSegmentPerpendicular(to: LineSegment(firstPoint: self.pointsBuffer[i + 1],
                                                                secondPoint: self.pointsBuffer[i + 2]), ofRelativeLength: 5)
          ls[3] = self.lineSegmentPerpendicular(to: LineSegment(firstPoint: self.pointsBuffer[i + 2],
                                                                secondPoint: self.pointsBuffer[i + 3]), ofRelativeLength: 5)

          offsetPath.move(to: ls[0].firstPoint)
          offsetPath.addCurve(to: ls[3].firstPoint, controlPoint1: ls[1].firstPoint, controlPoint2: ls[2].firstPoint)
          offsetPath.addLine(to: ls[3].secondPoint)
          offsetPath.addCurve(to: ls[0].secondPoint, controlPoint1: ls[2].secondPoint, controlPoint2: ls[1].secondPoint)
          offsetPath.close()

          self.lastSegmentOfPrev = ls[3]
        }

        DispatchQueue.main.async {
          self.bufIdx = 0
          let newLayer = CAShapeLayer()
          newLayer.strokeColor = UIColor.black.cgColor
          newLayer.fillColor = UIColor.clear.cgColor
          newLayer.path = offsetPath.cgPath
          self.view.layer.addSublayer(newLayer)
        }
      }
      pts[0] = pts[3]
      pts[1] = pts[4]
      ctr = 1
    }
  }

  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

  }

  func lineSegmentPerpendicular(to pp: LineSegment, ofRelativeLength fraction: CGFloat) -> LineSegment {
    let x0 = pp.firstPoint.x
    let y0 = pp.firstPoint.y
    let x1 = pp.secondPoint.x
    let y1 = pp.secondPoint.y

    var xa: CGFloat
    var ya: CGFloat
    var xb: CGFloat
    var yb: CGFloat

    let x0x1 = x1 - x0
    let y0y1 = y1 - y0
    let ab = sqrt(x0x1 * x0x1 + y0y1 * y0y1)

    let la = fraction / ab
    let lb = fraction / ab

    xa = x1 + la * (y0 - y1)
    ya = y1 + la * (x1 - x0)

    xb = x1 - lb * (y0 - y1)
    yb = y1 - lb * (x1 - x0)

    return LineSegment(firstPoint: CGPoint(x: xa, y: ya), secondPoint: CGPoint(x: xb, y: yb))
  }
}
...