【问题标题】:Get points from a UIBezierPath从 UIBezierPath 获取点
【发布时间】:2014-11-11 05:03:53
【问题描述】:

我通过以下方式绘制了上面的 BezierPath: //位置是用户触摸屏幕的位置。 //位置将是图形的最大值 CGPoint 原点 = CGPointMake(xStart, 620.0); CGPoint endpt = CGPointMake(xEnd, 620.0); CGPoint midpt1 = midPointForPoints(原点,位置); 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 的参考资料,但我仍然不确定。 更新:基本上,我想做这样的事情。

更新: 听从@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() 时使用了两个控制点。

在 Fang 的密集咨询后更新:

- (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);
}

最后一个问题: 对于第二个 Bezeir 路径,y 值应随着 t 值的增加而减小。现在,y 值不断增加。我应该如何解决这个问题?经过大量调试后,我还没有找到为什么会发生这种情况,因为一切都符合文档。

【问题讨论】:

  • 很好的视频在这里展示了贝塞尔路径的实际绘制机制:vimeo.com/106757336
  • 这是一个很棒的视频。任何使用贝塞尔路径的人都必须注意这一点。谢谢@matt
  • 你确定你在这里正确地解决了t吗?感觉不太对劲,尽管它可能只是一个不同的因式分解。 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
  • 我不确定如何检查我是否解决了正确的问题。这是我的 python 版本:gist.github.com/woniesong92/c7f79c812f1ae8be21f5
  • 我会在这里验证 location.y > endpt.y (我知道你打算这样做;只要确保它确实在代码中的这一点上)和 0

标签: ios uibezierpath bezier


【解决方案1】:

应该可以沿 Bezier 路径获取点,因为 addQuadCurveToPoint 是将二次 Bezier 线段添加到路径中。所以,你的第一条二次贝塞尔曲线的三个控制点是(参考原帖中的代码)

P(0) = 原点
P(1) = (midpt1.x, midpt1.y+50)
P(2) = 位置

您可以通过将参数 t 从 0 更改为 1 的任意小的增量值来计算此二次贝塞尔曲线上任意数量的点

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 值。

【讨论】:

  • 嘿@fang,我还在努力理解你的答案。如果我卡住了,让我问你问题。只是为了确认,这是一种获取图表上点的方法,对吗?我刚刚更新了我的问题以更好地解释我的需求。
  • 是的。评估二次贝塞尔曲线是在曲线上获取点的方法,除非 addQuadCurveToPoint() 不像它声称的那样创建二次贝塞尔曲线。
  • 这简直太完美了。让我算一下,看看它是否适合我!
  • 如果 X 值在 [X0, X2] 范围内,您至少应该得到一个在 [0, 1] 范围内的根。在您的示例中,X=358,非常接近 X2 (364),因此 t 值应该非常接近 1。实际计算表明,t 的两个根是 t=0.9837796 和 t=-60.650446,您选择t=0.9837796,非常接近 1。
  • 对于您的最后一个问题,第二个 Bezier 段的 x0Val、x1Val 和 x2Val 的实际值是多少? location.x、ctrlpt2.x 和 endpt.x 是否会像第一个 Bezier 段那样使 t^2 系数为零?
【解决方案2】:

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 对应物的情况下,您无法像需要的那样获得该图上的每个坐标。

【讨论】:

  • 谢谢彼得,但获得这样的分数并不是我想要的。我想找到一组位于路径上的点,这样当我有 x 坐标时,我可以得到 y 坐标。
  • 是的,我刚刚添加了那个解释。基本上,你不能,即使 NSPath,它的 Cocoa 对应物,也不提供这种能力
  • 另一种方法是,如果您使用使用数学公式生成的多个样本点创建 CGPath(定义每个可能的 x,y),那么您可以访问这些点。
猜你喜欢
  • 2011-03-04
  • 2016-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多