【问题标题】:Find the CGPoint on a UIView rectangle intersected by a straight line at a given angle from the center point在与中心点成给定角度的直线相交的 UIView 矩形上找到 CGPoint
【发布时间】:2012-03-02 01:19:50
【问题描述】:

在 iOS 中,我试图确定矩形上的点,该点与从中心点到矩形周边的假想线以预定角度相交。

假设我知道中心点、矩形的大小和角度(从东方的 0 度开始,逆时针经过北方的 90 度、西方的 180 度和南方的 270 度,再到东方的 360 度)。我需要知道相交点的坐标。

Finding points on a rectangle at a given angle 上的(对我而言)有点令人困惑的数学但大概是准确的答案让我尝试了以下代码,但它不能正常工作。这个问题与那个问题类似,但我正在寻找一种更正的 Objective-C / iOS 方法,而不是一般的数学答案。

我认为部分代码问题与使用单个 0 到 360 度角(以弧度表示,不可能为负数)输入有关,但可能还有其他问题。下面的代码主要使用the answer from 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);    
}

【问题讨论】:

    标签: iphone ios math graphics geometry


    【解决方案1】:

    我不会调试您的代码,而只是解释我将如何做到这一点。

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

    现在考虑框架的四个边缘,并将它们延伸为无限的线。在这四行之上,以您指定的角度放置一条穿过框架中心的线:

    假设框架以原点为中心 - 框架的中心位于坐标 (0,0) 处。我们可以很容易地计算出对角线与框架右边缘相交的位置:坐标为 (xRadius, xRadius * tan(angle))。我们可以很容易地计算出对角线与框架顶部边缘的交点:坐标为 (-yRadius / tan(angle), -yRadius)。

    (为什么我们否定顶部边缘交叉点的坐标?因为UIView 坐标系是从正常的数学坐标系翻转过来的。在数学中,y 坐标向页面顶部增加。在@987654331 @, 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

    看起来像这样:

    【讨论】:

    • 太棒了!谢谢。我对此有点麻烦,因为在我的情况下,我没有意识到我必须在 UIImageview 子类中使用 [self.superview convertPoint:thePoint toView:self] (对于其他来到这里的新手)。
    • 我正在研究你的代码非常好,试图了解如果我想旋转图像并仍然保持边缘检测该怎么办?有什么想法吗?
    【解决方案2】:

    这是 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 使用相同的解决方案保留两个答案没有额外的价值。
    • 我不同意。我已经在这里看到了足够多的“我如何在 Swift 中完成此操作”问题,我认为提供旧 Objective-C 答案的 Swift 翻译会增加价值。
    • 非常感谢这段代码和解释,Rob。在 Swift 中需要它,所以我想我会在翻译后将它抛在脑后。在回答之后肯定会看到很多“它在 Swift 中”,所以我认为它会没事的。
    • 对于任何发现此代码无法按预期工作的人:结果“交点”是相对于框架中心点的点,而不是与框架位于同一坐标。直到看到 rob 写的原始代码我才意识到这一点。
    猜你喜欢
    • 2014-12-06
    • 2016-11-15
    • 2020-12-17
    • 2012-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-07
    相关资源
    最近更新 更多