在给定角度的情况下,找到UIView矩形上被一条直线相交的CGPoint,该直线从中心点出发。

11
在iOS中,我试图确定矩形上从中心点到矩形周长在预定角度处相交的点。
假设我知道中心点、矩形大小和角度(从0度开始为东,逆时针穿过90度为北,180度为西,270度为南,360度为东),我需要知道相交点的坐标。
Finding points on a rectangle at a given angle中给出了某种令人困惑但可能准确的数学答案。该答案引导我尝试以下代码,但它无法正常工作。这个问题与那个问题类似,但我正在寻找一个经过更正的Objective-C/iOS方法,而不是一般的数学响应。
我认为代码问题的一部分与使用单个0到360度角度(以弧度表示,没有负数可能性)有关,但可能会存在其他问题。下面的代码主要使用在belisarius的回答中定义的符号,包括我的尝试计算定义在那里的每个区域的相交点。
这个代码在我的UIImageView子类中:
- (CGPoint) startingPointGivenAngleInDegrees:(double)angle {
    double angleInRads = angle/180.0*M_PI;
    float height = self.frame.size.height;
    float width = self.frame.size.width;
    float x0 = self.center.x;
    float y0 = self.center.y;
    // region 1 
    if (angleInRads >= -atan2(height, width) && angleInRads <= atan2(height, width)) {
        return CGPointMake(x0 + width/2, y0 + width/2 * tan(angleInRads));
    }
    // region 2
    if (angleInRads >= atan2(height, width) && angleInRads <= M_PI - atan2(height, width)) {
        return CGPointMake(x0 + height / (2*tan(angleInRads)),y0+height/2);
    }
    // region 3
    if (angleInRads >= M_PI - atan2(height, width) && angleInRads <= M_PI + atan2(height, width)) {
        return CGPointMake(x0 - width/2, y0 + width/2 * tan(angleInRads));
    }
    // region 4
    return CGPointMake(x0 + height / (2*tan(angleInRads)),y0-height/2);    
}
2个回答

27

我不会调试你的代码,但是我可以解释一下我的做法。

首先,我们定义一些术语。让我们把xRadius定义为框架宽度的一半,把yRadius定义为框架高度的一半。

现在考虑框架的四条边,并将它们延伸为无限线。在这四条线上方,以您指定的角度通过框架中心的一条线:

diagram of rectangle with overlaid line

假设框架以原点为中心 - 框架中心的坐标为(0,0)。 我们可以轻松地计算出对角线线与框架右侧交点的位置:坐标为(xRadiusxRadius * tan(angle))。 我们还可以轻松地计算出对角线线与框架顶部交点的位置:坐标为(-yRadius / tan(angle)-yRadius)。

(为什么要对顶部交点的坐标取反?因为 UIView 坐标系统与正常的数学坐标系统相反。在数学中,y 坐标向页面顶部增加。在 UIView 中,y 坐标朝向视图底部增加。)

因此,我们可以简单地计算出线与框架右侧的交点。如果该交点在框架外部,则我们知道线必须在与右边相交之前与顶边相交。如何判断右侧交点是否超出边界?如果它的 y 坐标(xRadius * tan(angle))大于 yRadius(或小于 -yRadius),则超出边界。

因此,在一个方法中将所有这些组合在一起,我们首先计算出xRadiusyRadius

- (CGPoint)radialIntersectionWithConstrainedRadians:(CGFloat)radians {
    // This method requires 0 <= radians < 2 * π.

    CGRect frame = self.frame;
    CGFloat xRadius = frame.size.width / 2;
    CGFloat yRadius = frame.size.height / 2;

然后我们计算与右边缘相交的y坐标:

    CGPoint pointRelativeToCenter;
    CGFloat tangent = tanf(radians);
    CGFloat y = xRadius * tangent;

我们检查交集是否在框架内:

    if (fabsf(y) <= yRadius) {

一旦我们知道它在帧内,我们就必须确定我们是否想要与右边缘或左边缘相交。如果角度小于π/2(90°)或大于3π/2(270°),我们希望与右边缘相交。否则,我们希望与左边缘相交。

        if (radians < (CGFloat)M_PI_2 || radians > (CGFloat)(M_PI + M_PI_2)) {
            pointRelativeToCenter = CGPointMake(xRadius, y);
        } else {
            pointRelativeToCenter = CGPointMake(-xRadius, -y);
        }

如果右侧交点的y坐标越界,则计算与底部边缘的交点x坐标。

    } else {
        CGFloat x = yRadius / tangent;

接下来我们需要确定是想要上边缘还是下边缘。如果角度小于π (180°),我们想要的是下边缘。否则,我们想要的是上边缘。

        if (radians < (CGFloat)M_PI) {
            pointRelativeToCenter = CGPointMake(x, yRadius);
        } else {
            pointRelativeToCenter = CGPointMake(-x, -yRadius);
        }
    }

最后,我们通过实际框架的中心来偏移计算得出的点,并将其返回。

    return CGPointMake(pointRelativeToCenter.x + CGRectGetMidX(frame),
        pointRelativeToCenter.y + CGRectGetMidY(frame));
}

这里有一个测试项目: https://github.com/mayoff/stackoverflow-radial-intersection

看起来像这样:

edgepoint screen shot


1
太棒了!谢谢。我在这方面遇到了一些麻烦,因为我没有意识到在我的UIImageview子类中必须使用[self.superview convertPoint:thePoint toView:self](对于其他初学者来说)。 - Victor Van Hee
我正在学习你的代码,它非常优秀,想知道如果我想旋转图像并仍然保持边缘检测,该怎么做?有什么想法吗? - flooie

7
这是Rob的一份迅捷复制粘贴的回答。
func findEdgePoint(angle: CGFloat) -> CGPoint {

    let intersection: CGPoint

    let xRad = frame.width / 2
    let yRad = frame.height / 2

    let tangent = tan(angle)
    let y = xRad * CGFloat(tangent)

    if fabs(y) <= yRad {

        if angle < CGFloat.pi / 2 || angle > 3 * CGFloat.pi / 2 {
            intersection = CGPoint(x: xRad, y: y)
        } else {
            intersection = CGPoint(x: -xRad, y: -y)
        }
    } else {

        let x = yRad / CGFloat(tangent)

        if angle < CGFloat.pi {
            intersection = CGPoint(x: x, y: yRad)
        } else {
            intersection = CGPoint(x: -x, y: -yRad)
        }
    }

    return intersection
}

// heck yeah rob, he's the man
// if he can't solve it, no one can

虽然这是一个答案,但它是@rob之前的答案的“复制品”。SO没有保留两个具有相同解决方案的答案的附加价值。 - Grant Miller
5
我不同意。我在这里看到了足够多的“如何在Swift中做某事”的问题,因此我认为将旧的Objective-C答案提供Swift翻译会增加价值。 - rob mayoff
1
非常感谢你提供的这段代码和解释,Rob。我需要在Swift中使用它,所以我想在翻译后留下它。在答案中,我确实看到了很多“这是Swift版本”,所以我认为这样做没问题。 - beatTheSystem42
如果有人发现这段代码的运行结果与预期不符:所得到的“交点”是相对于框架中心点的一个点,而不是与框架在同一坐标系下的点。我直到看到Rob编写的原始代码才意识到这一点。 - Antony Ng

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