2016-08-02 6 views
2

Я пытаюсь нарисовать простую форму параболы, используя UIBezierPath. У меня есть maxPoint и boundingRect, из которых я основываю ширину и длину параболы.
Вот функция, которую я сделал, чтобы нарисовать параболу (рисую параболу в представлении контейнера, rect будет container.bounds):Неизбежный UIBezierPath изгибающий механизм, контрольная точка и точка кривой

func addParabolaWithMax(maxPoint: CGPoint, inRect boundingRect: CGRect) { 
    let path = UIBezierPath() 

    let p1 = CGPointMake(1, CGRectGetMaxY(boundingRect)-1) 
    let p3 = CGPointMake(CGRectGetMaxX(boundingRect)-1, CGRectGetMaxY(boundingRect)-1) 

    path.moveToPoint(p1) 
    path.addQuadCurveToPoint(p3, controlPoint: maxPoint) 

    // Drawing code 
    ... 
} 

Моя проблема в том, что я хочу maxPoint, что я отправляю в функции быть фактической крайней точкой в ​​самой параболе. Так, например, если я отправлю в (CGRectGetMidX(container.bounds), 0), максимальная точка должна быть в самом верхнем центре. Но при использовании этой функции с этой конкретной точкой, это то, что результат выглядит следующим образом:

enter image description here

Так что же путь делает здесь? Или, другими словами, как я могу получить от controlPoint до фактической максимальной точки, которая мне нужна? Я попытался добавить и вычесть разные значения из значения y, основанного на высоте boundingRect, но я не мог найти правильную комбинацию, так как в разных точках с разными значениями y она ведет себя по-разному. Кажется, есть какой-то мультипликатор, который добавляется, как я могу его решить?

ответ

4

Для майских приложений решение adam.wulf является хорошо, но это не создает параболу. Чтобы создать параболу, нам нужно вычислить контрольную точку, учитывая середину квадратичной кривой. Пути Безье - это просто математика; мы можем легко вычислить это. Нам просто нужно инвертировать функцию Безье и решить ее при t = 0,5.

Решение Безье на 0,5 (средняя точка) получается красиво на Draw a quadratic Bézier curve through three given points.

2*Pc - P0/2 - P2/2 

Где Pc это точка, мы хотим, чтобы пройти и P0 и P2 являются конечными точками.

(Вычисление Безье в других точках не очень интуитивно. Значение при t = 0,25 не является «четверть пути по пути». Но, к счастью для наших целей, t = 0,5 подходит для нашей интуиции довольно хорошо «средняя точка» по квадратичной величине.)

С учетом нашего решения мы можем написать наш код. Простите перевод в Swift 3; моя копия Xcode 7.3 не очень довольна игровыми площадками iOS, но ее легко преобразовать в 2.2.

func addParabolaWithMax(maxPoint: CGPoint, inRect boundingRect: CGRect) -> UIBezierPath { 

    func halfPoint1D(p0: CGFloat, p2: CGFloat, control: CGFloat) -> CGFloat { 
     return 2 * control - p0/2 - p2/2 
    } 

    let path = UIBezierPath() 

    let p0 = CGPoint(x: 0, y: boundingRect.maxY) 
    let p2 = CGPoint(x: boundingRect.maxX, y: boundingRect.maxY) 

    let p1 = CGPoint(x: halfPoint1D(p0: p0.x, p2: p2.x, control: maxPoint.x), 
        y: halfPoint1D(p0: p0.y, p2: p2.y, control: maxPoint.y)) 

    path.move(to: p0) 
    path.addQuadCurve(to: p2, controlPoint: p1) 
    return path 
} 

halfPoint1D функция является одномерной реализацией нашего решения.Для нашего двумерного CGPoint нам просто нужно позвонить ему дважды.

Если бы я мог порекомендовать только один ресурс для понимания кривых Безье, вероятно, это был раздел "Constructing Bézier curves" из Википедии. Изучая маленькие анимации, которые показывают, как возникают кривые, я нахожу очень интересным. Раздел «Конкретные случаи» также полезен. Для глубокого изучения темы (и той, которую я рекомендую всем разработчикам иметь знакомое знакомство), мне нравится A Primer on Bézier Curves. Это нормально, чтобы сфотографировать его и просто прочитать части, которые вас интересуют в данный момент. Но базовое понимание этой группы функций будет иметь большое значение для устранения магии от рисования в Core Graphics и сделать UIBezierPath инструментом, а не черным ящиком.

+0

Обратите внимание, что если ваша цель - создать плавную кривую, лежащую на ряде точек, сплайны Catmull-Rom могут быть лучшим выбором, чем Bezier кривые. Они вычислительно дороже, чем Безье, они создают кривую, проходящую через все контрольные точки. Однако, как и кривые Безье, вы можете получить «перегибы» в сплайнах Catmull-Rom, если вы попытаетесь слишком резко нарисовать кривую. Отличные книги Erica Sadun от Cookies от iOS Developer имеют рабочий код для сплайнов Catmull-Rom, если вам интересно. –

+0

Одна из неприятных особенностей сплайнов Catmull-Rom заключается в том, что они не включают в себя первую и последнюю точки, поэтому вам нужно добавить дополнительные точки расширения в «правильные места». Вы можете избежать изломов, если используете центростремительные сплайны C-R (которые являются моим обычным предпочтением именно по этой причине). –

0

пусть путь = UIBezierPath()

 let p1 = CGPointMake(0,self.view.frame.height/2) 
     let p3 = CGPointMake(self.view.frame.width,self.view.frame.height/2) 

     path.moveToPoint(p1) 
     path.addQuadCurveToPoint(p3, controlPoint: CGPoint(x: self.view.frame.width/2, y: -self.view.frame.height/2)) 

     let line = CAShapeLayer() 
     line.path = path.CGPath; 
     line.strokeColor = UIColor.blackColor().CGColor 
     line.fillColor = UIColor.redColor().CGColor 
     view.layer.addSublayer(line) 

это причина: https://cdn.tutsplus.com/mobile/authors/legacy/Akiel%20Khan/2012/10/15/bezier.png вы должны рассматривать понятие касательной

+0

Использование этого кода действительно приносит максимальную точку на вершину, но мне нужно, чтобы парабола начиналась в самом нижнем левом и концевом нижнем правом углу, почему вы начинаете и заканчиваете параболу на половине высоты? Мне удалось сделать это так, как я хочу с этой конкретной точки, имея '-container.bounds.height' в' controlPoint', но дело в том, что оно не работает со всеми типами точек. Таким образом, должно быть некоторое динамическое значение или множитель, добавляемый к значению y контрольной точки. Это то, что я пытаюсь найти – Eilon

1

Хитрость заключается в том, чтобы разбить кривую на две части, так что вы можете контролировать которая указывает, что кривая проходит. Как упоминалось в ответе Эдуардо, контрольные точки обрабатывают касательную, а конечные точки находятся на кривой. Это позволяет иметь кривую из левого нижнего угла в верхней части, а затем от верхнего центра в нижнем правом углу:

let p1 = CGPointMake(0,self.view.frame.height/2) 
let p3 = CGPointMake(self.view.frame.width,self.view.frame.height/2) 
let ctrlRight = CGPointMake(self.view.frame.width,0) 
let ctrlLeft = CGPointZero 

let bezierPath = UIBezierPath() 
bezierPath.moveToPoint(p1) 
bezierPath.addCurveToPoint(maxPoint, controlPoint1: p1, controlPoint2: ctrlLeft) 
bezierPath.addCurveToPoint(p3, controlPoint1: ctrlRight, controlPoint2: p3) 

UIColor.blackColor().setStroke() 
bezierPath.lineWidth = 1 
bezierPath.stroke() 
+0

Согласен с подходом, но я думаю, что кривая будет гораздо более точно соответствовать запросу, если вы удалите '/ 2' на' p1' и 'p3'. Но это не будет параболой, поскольку вы добавляете кубическую кривую, а не квадровую кривую. (Но вы можете сделать это «параболоподобным», что может быть реальной целью.) –

Смежные вопросы