从UIBezierPath获取点

10

enter image description here

我通过以下步骤绘制了上面的BezierPath: // location是用户触摸屏幕的位置。 // location将是图形的最大值 CGPoint origin = CGPointMake(xStart, 620.0); CGPoint endpt = CGPointMake(xEnd, 620.0); CGPoint midpt1 = midPointForPoints(origin, location); CGPoint midpt2 = midPointForPoints(location, endpt);

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:origin];
[path addQuadCurveToPoint:location controlPoint:CGPointMake(midpt1.x, midpt1.y+50)];
[path addQuadCurveToPoint:endpt controlPoint:CGPointMake(midpt2.x, midpt2.y+50)];

[shapeLayer setPath:path.CGPath];

现在,我想检索某些位于路径上的x坐标的y坐标。例如,给定x = 0.0,我想获得y = 0.0,或者给定x = 300.0,y = 50.0。

我查看了一些参考资料,如questionsample code,但仍然不确定。 更新:基本上,我想做这样的事情。 enter image description here

更新: 根据@Fang的建议:

给定方程式

X = (1-t)^2*X0 + 2*t*(1-t)*X1 + t^2 *X2

我解决t。
t = ((2.0 * x0 - x1) + sqrt(((-2.0 * x0 + x1) ** 2.0) 
- ((4 * (x0 - 2.0 * x1 + x2)) * (x0 - x)))) / (2.0 * (x0 - 2.0 * x1 + x2))

或者
t = ((2.0 * x0 - x1) - sqrt(((-2.0 * x0 + x1) ** 2.0) 
- ((4 * (x0 - 2.0 * x1 + x2)) * (x0 - x)))) / (2.0 * (x0 - 2.0 * x1 + x2))

使用此值,找到与X对应的Y(我们使用X来找到上述t值)
Y = (1-t)^2*Y0 + 2*t*(1-t)*Y1 + t^2 *Y2

根据上述公式,我应该得到贝塞尔曲线上的点的y值,但我得到的点距正确的点很远。非常感谢任何进一步的帮助。
问题:我认为可能的一个问题是我用两个控制点调用addQuadCurveToPoint()两次,而不是一次用两个控制点。这是否意味着我绘制了两条贝塞尔路径并将它们组合在一起?我还在查看this以查看我的计算有什么问题,唯一的区别似乎是他在调用addQuadCurveToPoint()时使用了两个控制点。
经过方的深入咨询后更新:
- (float)getYFromBezierPath:(float)x location:(CGPoint)location ctrlpt1:(CGPoint)ctrlpt1 ctrlpt2:(CGPoint)ctrlpt2 endpt:(CGPoint)endpt {
    float yVal;
    float tVal;
    if (x <= location.x) {
        tVal = [self getTvalFromBezierPath:x x0Val:0.0 x1Val:ctrlpt1.x x2Val:location.x];
        yVal = [self getCoordFromBezierPath:tVal origin:0.0 p1Val:ctrlpt1.y p2Val:location.y];
    } else {
        // THIS PART IS THE PROBLEM //
        tVal = [self getTvalFromBezierPath:x x0Val:location.x x1Val:ctrlpt2.x x2Val:endpt.x];
        yVal = [self getCoordFromBezierPath:tVal origin:location.y p1Val:ctrlpt2.y p2Val:endpt.y];
    }
    return yVal;
}

- (float)getTvalFromBezierPath:(float)x x0Val:(float)x0 x1Val:(float)x1 x2Val:(float)x2 {
    float tVal = (x-x0)/(2*(x1-x0));
    return tVal;
}

- (float)getCoordFromBezierPath:(float)t origin: (float)origin p1Val: (float)p1 p2Val: (float)p2 {
// tVal = (sqrt((-2.0 * x * x1) + (x * x0) + (x * x2) + pow(x1, 2) - (x0 * x2)) + x0 - x1) / (x0 - (2.0 * x1) + x2);
    return (pow((1-t),2) * origin) + (2 * t * (1-t) * p1) + (pow(t,2) * p2);
}

最后一个问题: 对于第二个贝塞尔曲线路径,随着t值的增加,y值应该减小。现在,y值却不断增加。我该如何解决这个问题?经过密集的调试,我还没有找到原因,因为一切都符合文档要求。


2
这里有一个不错的视频,展示了贝塞尔路径是如何绘制出来的机制:http://vimeo.com/106757336 - matt
你确定你在这里正确地解出了t吗?虽然可能只是不同的因数分解,但感觉不太对。http://www.wolframalpha.com/input/?i=x+%3D+%281-t%29%5E2*X_0+%2B+2*t*%281-t%29*X_1+%2B+t%5E2+*X_2+solve+for+t - Rob Napier
另外,当你说你没有得到正确的点时,它有多远偏离?它是否曾经工作过(对于直线或更简单的曲线)?贝塞尔曲线对曲线上不同点处的t值的小误差非常敏感。 - Rob Napier
首先,感谢@RobNapier的帮助。我认识到的第一个问题是当X大于屏幕中点时,t值不在[0,1]的范围内。如果可能的话,我也很想在聊天中得到您的帮助。 - Maximus S
1
我会在这里验证位置y > 结束点y (我知道你打算这样做; 只是确保它确实是在代码的这一点上),并且0 < tVal < 1。 - Rob Napier
显示剩余6条评论
2个回答

5
可以通过addQuadCurveToPoint将二次贝塞尔曲线段添加到路径中,就像从一条Bezier路径中获取点一样。因此,您第一个二次Bezier曲线的三个控制点是(请参阅原始帖子中的代码片段):
P(0)= origin P(1)=(midpt1.x,midpt1.y + 50) P(2)= location
您可以通过将参数t从0变化为1,并采用任何小增量值来计算此二次Bezier曲线上的许多点:
C(t)=(1-t)^2 * P(0)+ 2 * t *(1-t)* P(1)+ t ^ 2 * P(2)
要从给定的X值获取Y值,您需要从t的此二次多项式中解决给定X值的t值:
X =(1-t)^2 * X0 + 2 * t *(1-t)* X1 + t ^ 2 * X2
其中X0,X1和X2是P(0),P(1)和P(2)的X坐标,这意味着X0 = origin.x,X1 = midpt1.x并且X2 = location.x。
由此可以得到一个二次方程:
(X0-2 * X1 + X2)* t ^ 2 + 2 *(X1-X0)* t +(X0-X)= 0
您可以使用二次公式解决t。如果X0,X1和X2的值恰好使t ^ 2项的系数为零,您可以直接解决t为t =(X-X0)/(2 *(X1-X0))。
一旦有了t值,就可以轻松评估相应的Y值。

是的。评估二次贝塞尔曲线是获取曲线上的点的方法,除非addQuadCurveToPoint()并未像它所声称的那样创建二次贝塞尔曲线。 - fang
那太完美了。让我算一下,看看它是否适合我! - Maximus S
我刚刚计算了一堆t值,有时候这个值不在[0,1]之间。例如,如果X = 358.0,X0 = 0.0,X1 = 179.0,X2 = 364.0,t = 1.88或-31.71。在这种情况下我该怎么办?我已经更新了问题。 - Maximus S
1
如果X值在[X0,X2]范围内,您应该至少得到一个在[0,1]范围内的根。在您的示例中,X = 358,非常接近X2(364),因此t值应该非常接近于1。实际计算显示,t的两个根是t = 0.9837796和t = -60.650446,并且您选择了t = 0.9837796,这非常接近于1。 - fang
1
针对您的最后一个问题,第二个贝塞尔曲线段的x0Val、x1Val和x2Val实际值是多少?location.x、ctrlpt2.x和endpt.x是否像第一个贝塞尔曲线段一样使t^2系数为零? - fang
显示剩余8条评论

1

CGPath是不透明的数据类型,也就是说,在这种情况下,我们只能获得在创建时定义的点,例如您创建的图形,只有三个点可以获得。

像示例代码一样,您可以使用CGPathApply获取这些点。如果在您的代码后面附加以下代码,它将输出3个点。

    ...
    [shapeLayer setPath:path.CGPath];
    NSMutableArray *keyPoints = [NSMutableArray array];
    CGPathApply(path.CGPath, (__bridge void *)keyPoints, getPointsFromBezier);

    NSLog(@"Points = %@", points);
}

// copied from the sample code.
void getPointsFromBezier(void *info, const CGPathElement *element)
{
    NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;
    CGPathElementType type = element->type;
    CGPoint *points = element->points;
    if (type != kCGPathElementCloseSubpath)
    {
        if ((type == kCGPathElementAddLineToPoint) ||
            (type == kCGPathElementMoveToPoint))
            [bezierPoints addObject:VALUE(0)];
        else if (type == kCGPathElementAddQuadCurveToPoint)
            [bezierPoints addObject:VALUE(1)];
        else if (type == kCGPathElementAddCurveToPoint)
            [bezierPoints addObject:VALUE(2)];
    }
}

因此,简而言之,您无法像您要求的那样获取该图表上的每个单独坐标,只给定其x/y对应项。

谢谢Peter,但我不想得到这样的点。我想找到一组在路径上的点,这样当我有一个x坐标时,就可以得到y坐标。 - Maximus S
是的,我刚刚添加了那个解释。基本上,你不能这样做,即使是 NSPath,它的 Cocoa 对应项也不提供此功能。 - Peter
另一种方法是,如果您使用生成的多个示例点使用数学公式创建CGPath(定义每个可能的x,y),则可以访问这些点。 - Peter

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