在iPhone上查找简单三次贝塞尔曲线上给定距离的点。

16
4个回答

31

计算位置背后有一些简单的数学方法,你可以在讨论Bézier曲线的每篇论文上甚至是维基百科上阅读到相关内容。无论如何,我能理解所有实际上在代码中实现它时遇到困难的人,所以我编写了这个示例UIView,因为这可能是最简单的入门方式。

#import "MBBezierView.h"

CGFloat bezierInterpolation(CGFloat t, CGFloat a, CGFloat b, CGFloat c, CGFloat d) {
    CGFloat t2 = t * t;
    CGFloat t3 = t2 * t;
    return a + (-a * 3 + t * (3 * a - a * t)) * t
    + (3 * b + t * (-6 * b + b * 3 * t)) * t
    + (c * 3 - c * 3 * t) * t2
    + d * t3;
}

@implementation MBBezierView

- (void)drawRect:(CGRect)rect {
    CGPoint p1, p2, p3, p4;
    p1 = CGPointMake(30, rect.size.height * 0.33);
    p2 = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
    p3 = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
    p4 = CGPointMake(-30 + CGRectGetMaxX(rect), rect.size.height * 0.66);

    [[UIColor blackColor] set];
    [[UIBezierPath bezierPathWithRect:rect] fill];

    [[UIColor redColor] setStroke];

    UIBezierPath *bezierPath = [[[UIBezierPath alloc] init] autorelease];   
    [bezierPath moveToPoint:p1];
    [bezierPath addCurveToPoint:p4 controlPoint1:p2 controlPoint2:p3];
    [bezierPath stroke];

    [[UIColor brownColor] setStroke];
    for (CGFloat t = 0.0; t <= 1.00001; t += 0.05) {
        CGPoint point = CGPointMake(bezierInterpolation(t, p1.x, p2.x, p3.x, p4.x), bezierInterpolation(t, p1.y, p2.y, p3.y, p4.y));
        UIBezierPath *pointPath = [UIBezierPath bezierPathWithArcCenter:point radius:5 startAngle:0 endAngle:2*M_PI clockwise:YES];
        [pointPath stroke];
    }   
}

@end

这是我得到的内容:

alt text

这是我收到的信息:

3
@Joe Blow:这真的是你想要的吗?你说你想要一点,它沿着曲线有一个距离D。Michal,如果我错了,请纠正我,但你的代码只是在不同的参数值处评估曲线,这与长度不同。正如你所看到的,在曲线的峰值处点之间更接近,在曲线中间则更远。 - CromTheDestroyer
嗨Crom - 你说得很对,它们只是大致沿着曲线等间距。显然,数学上没有真正简单的方法来获得精确的间距。这种常见的近似在游戏引擎等方面运行良好。请参见4089443。 - Fattie
你好,Michel。我已经实现了这段代码,但它显示的是白屏,而不是绘图。我在viewDidLoad中调用了这个方法 [self drawRect:bgImage.frame];,但是出了什么问题呢? - Sandeep Singh
嗨@crom-再次强调,它们仅在曲线上近似等距。要做到等距间距非常困难,我是说极其困难。有多难?这里是一个关于该问题的典型数学论文!http://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf特别注意下面丹尼尔提供的非常有用的链接。(丹尼尔的无价链接,再次感谢。) - Fattie
嗨,@Michael,你能看一下我的问题吗?链接是https://dev59.com/Yl8d5IYBdhLWcg3waxkp。 - Maximus S

3
Michal提出的沿曲线移动距离t的近似方法在一些曲线上和某些情况下可能存在问题。不幸的是,我已经搜索了相当长时间,但没有找到正确解决方案的Obj-C实现。
然而,Mike "Pomax" Kamermans在他惊人的Bezier曲线入门中以非常奇妙的方式描述了该解决方案。它甚至拥有所有的代码,使用processing编写并且是公共领域的。我很惊讶为什么还没有人将其转换为Obj-C。我非常心动。

非常有用的链接@Daniel - 谢谢。太棒了。这是那种页面之一...http://pomax.github.io/bezierinfo...太有用了!哈哈。再次感谢,这个链接非常有用,我会奖励你的!太棒了。 - Fattie
2
哈哈哈,很高兴Primer找到了用处。如果您发现有任何遗漏或不清楚的地方,请告诉我 =P - Mike 'Pomax' Kamermans

0
任何贝塞尔曲线都可以简单地视为具有矢量或复数系数的多项式函数。例如,您截图中的三次贝塞尔曲线将由3阶多项式函数生成,曲线上的每个点描述了曲线多项式的结果B(t),该值取特定输入值t的结果。如果我没记错的话,一旦您知道用于创建曲线的多项式,您只需解出B(t)=a+bi,其中a+bi描述了您要查找t值的复平面上的点。像这样在多项式中找根是一个众所周知的问题,对于2阶或更低阶的曲线可以通过代数方法求解,对于高阶的多项式则可以使用一些方法,例如前向牛顿法进行求解。如果您知道生成多项式,当然也应该很容易找到导数。贝塞尔通常从“模板多项式”中绘制,在绘制不同曲线时仅更改系数,因此您可能可以在文档中查找这些多项式。

0

基于Michal的答案,我在我的项目中用Swift编写了这个。也许会有帮助,只是在这里留下了原始Fattie截图的解释:

let targetPoint = CGPoint(x: bezierInterpolation(t: distance, p1: sP1.point.x, cp1: sP1.nextMarker.x, cp2: sP2.previousMarker.x, p2: sP2.point.x),
                          y: bezierInterpolation(t: distance, p1: sP1.point.y, cp1: sP1.nextMarker.y, cp2: sP2.previousMarker.y, p2: sP2.point.y))

func bezierInterpolation(t: CGFloat, p1: CGFloat, cp1: CGFloat, cp2: CGFloat, p2: CGFloat) -> CGFloat {
    let t2: CGFloat = t * t;
    let t3: CGFloat = t2 * t;
    return p1 + (-p1 * 3 + t * (3 * p1 - p1 * t)) * t
    + (3 * cp1 + t * (-6 * cp1 + cp1 * 3 * t)) * t
    + (cp2 * 3 - cp2 * 3 * t) * t2
    + p2 * t3;
}

enter image description here


没错,使用“长而清晰”的方法编写算术运算确实有其优点(显然结果是相同的)... https://dev59.com/jW855IYBdhLWcg3w5IrZ#31317254 - Fattie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接