【问题标题】:SKPhysicsBody.area of a polygon not correct?多边形的 SKPhysicsBody.area 不正确?
【发布时间】:2020-01-16 16:39:37
【问题描述】:

我正在尝试在 Swift 中计算多边形的面积,但它似乎无法正确计算它的面积。 所以我用这段代码试了一下,向你展示它计算的是相同的区域——即使这两个区域之一应该只有另一个区域的 75%:

让 tmp_size = 100 var path = CGMutablePath() path.move(to: CGPoint(x: 0, y: 0)) path.addLine(to: CGPoint(x: 0, y: tmp_size / 2)) path.addLine(to: CGPoint(x: tmp_size / 2, y: tmp_size / 2)) path.addLine(to: CGPoint(x: tmp_size / 2, y: tmp_size )) path.addLine(to: CGPoint(x: tmp_size, y: tmp_size)) path.addLine(to: CGPoint(x: tmp_size, y: 0)) path.closeSubpath() var pb = SKPhysicsBody(polygonFrom: 路径) print("面积:\(pb.area * pow(150, 2))") 路径 = CGMutablePath() path.move(to: CGPoint(x: 0, y: 0)) path.addLine(to: CGPoint(x: 0, y: tmp_size)) path.addLine(to: CGPoint(x: tmp_size, y: tmp_size)) path.addLine(to: CGPoint(x: tmp_size, y: 0)) path.closeSubpath() pb = SKPhysicsBody(polygonFrom: 路径) print("AREA2: \(pb.area * pow(150, 2))")

输出:

区域:10000.000409781933
区域2:10000.00074505806

这是一个已知的错误吗?

无论如何,我需要一个如何计算多边形面积的函数(最好是快速的,但欢迎所有其他语言)。 任何帮助将不胜感激!

如果这让事情变得更容易: 我的多边形只有 90 度角,所以我可以把它分成矩形(如果我知道怎么做的话)

非常感谢您的帮助!

【问题讨论】:

  • 您的 Area1 多边形搞砸了。您有一条线与另一条线相交,这将产生不正确的结果
  • 谢谢,我在第二个“addline”处交换了 x 和 y 值 - 但它仍然为两个区域输出 1000。我认为它只会从多边形的 BoundingBox 计算,它总是一个 CGRectangle。
  • 用三角形来测试
  • 我现在找到了一个对我有用的解决方案 - 请参阅下面的答案 - 感谢您的宝贵时间!

标签: swift path sprite-kit polygon area


【解决方案1】:

iOS12.4 / XCode 10.3 / Swift 5解决方案:

首先,将此扩展添加到您的代码中,以将路径的 CGPoints 提取到数组中:

extension CGPath {
    func forEach( body: @escaping @convention(block) (CGPathElement) -> Void) {
        typealias Body = @convention(block) (CGPathElement) -> Void
        let callback: @convention(c) (UnsafeMutableRawPointer, UnsafePointer<CGPathElement>) -> Void = { (info, element) in
            let body = unsafeBitCast(info, to: Body.self)
            body(element.pointee)
        }
        //print(MemoryLayout.size(ofValue: body))
        let unsafeBody = unsafeBitCast(body, to: UnsafeMutableRawPointer.self)
        self.apply(info: unsafeBody, function: unsafeBitCast(callback, to: CGPathApplierFunction.self))
    }
    func getPathElementsPoints() -> [CGPoint] {
        var arrayPoints : [CGPoint]! = [CGPoint]()
        self.forEach { element in
            switch (element.type) {
            case CGPathElementType.moveToPoint:
                arrayPoints.append(element.points[0])
            case .addLineToPoint:
                arrayPoints.append(element.points[0])
            case .addQuadCurveToPoint:
                arrayPoints.append(element.points[0])
                arrayPoints.append(element.points[1])
            case .addCurveToPoint:
                arrayPoints.append(element.points[0])
                arrayPoints.append(element.points[1])
                arrayPoints.append(element.points[2])
            default: break
            }
        }
        return arrayPoints
    }
    func getPathElementsPointsAndTypes() -> ([CGPoint],[CGPathElementType]) {
        var arrayPoints : [CGPoint]! = [CGPoint]()
        var arrayTypes : [CGPathElementType]! = [CGPathElementType]()
        self.forEach { element in
            switch (element.type) {
            case CGPathElementType.moveToPoint:
                arrayPoints.append(element.points[0])
                arrayTypes.append(element.type)
            case .addLineToPoint:
                arrayPoints.append(element.points[0])
                arrayTypes.append(element.type)
            case .addQuadCurveToPoint:
                arrayPoints.append(element.points[0])
                arrayPoints.append(element.points[1])
                arrayTypes.append(element.type)
                arrayTypes.append(element.type)
            case .addCurveToPoint:
                arrayPoints.append(element.points[0])
                arrayPoints.append(element.points[1])
                arrayPoints.append(element.points[2])
                arrayTypes.append(element.type)
                arrayTypes.append(element.type)
                arrayTypes.append(element.type)
            default: break
            }
        }
        return (arrayPoints,arrayTypes)
    }
}

来源:How to get the CGPoint(s) of a CGPath(来自用户 Alessandro Ornano 的回答;swift 5.x 版本)

其次,我的示例代码是:

let tmp_size = 100

    var path = CGMutablePath()
    path.move(to: CGPoint(x: 0, y: 0))
    path.addLine(to: CGPoint(x: 0, y: tmp_size / 2  ))
    path.addLine(to: CGPoint(x: tmp_size / 2, y: tmp_size / 2))
    path.addLine(to: CGPoint(x: tmp_size / 2, y: tmp_size   ))
    path.addLine(to: CGPoint(x: tmp_size    , y: tmp_size   ))
    path.addLine(to: CGPoint(x: tmp_size    , y: 0      ))
    path.closeSubpath()
    var pb = SKPhysicsBody(polygonFrom: path)

    print("area1: \(getAreaFromPolygon(path: path))")


    path = CGMutablePath()
    path.move(to: CGPoint(x: 0, y: 0))
    path.addLine(to: CGPoint(x: 0, y: tmp_size))
    path.addLine(to: CGPoint(x: tmp_size, y: tmp_size))
    path.addLine(to: CGPoint(x: tmp_size, y: 0))
    path.closeSubpath()
    pb = SKPhysicsBody(polygonFrom: path)

    print("area2: \(getAreaFromPolygon(path: path))")

最后,我计算多边形面积的函数(以 CGPoints 为单位):

func getAreaFromPolygon (path: CGMutablePath) -> Double {
    // get an array of the CGPoints of this path
    let PathArray = path.getPathElementsPoints()

    // store all CGPoint.x into an array        
    var xArray = [Int]()
    for PosX in PathArray {
        xArray.append(Int(PosX.x))
    }

    // store all CGPoints.y into an array
    var yArray = [Int]()
    for PosY in PathArray {
        yArray.append(Int(PosY.y))
    }

    // calculate the area
    var area = 0.0
    for i in 0..<PathArray.count {
        let firstPart = Double((yArray[i] + yArray[(i+1) % PathArray.count]))
        let secondPart = (xArray[i] - xArray[(i+1) % PathArray.count])
        area += Double(firstPart) * Double(secondPart)
    }
    area = abs(area) / 2.0
    return area
}

来源:https://de.wikipedia.org/wiki/Gaußsche_Trapezformel#Programmcode

(以前是 Java,但我把它翻译成 Swift 5)

注意:我将公式分为两部分(第一部分和第二部分),因为它太复杂,Xcode 无法检查

顺便说一句,我的输出是:

area1: 7500.0
area2: 10000.0

这是正确的,因为 area1 与 area2 相差 75%。

P.S.:我只有矩形的多边形,但它也应该适用于任何多边形(可能有相交线的多边形除外)

【讨论】:

    【解决方案2】:

    这是对 ma2412 对效率的回答的补充。

    他正在使用所谓的鞋带公式。

    已修改代码以删除不必要的循环和模数

    func getAreaFromPolygon (path: CGMutablePath) -> Double {
        // get an array of the CGPoints of this path
        var Array = path.getPathElementsPoints()
        Array.append(Array[0])
        // calculate the area
        var area = 0.0
        for i in 0..<Array.count - 1 {
            let firstPart = Double(Array[i].y + Array[i+1].y)
            let secondPart = Double(Array[i].x - Array[i+1].x)
            area += firstPart * secondPart
        }
        return (abs(area) / 2.0)
    }
    

    【讨论】:

    • 不应该是“for i in 0..
    • 不,如果你注意到数组,我将元素零注入到链的末尾以避免 mod
    • Mod 通常是“慢”的,因为它必须进行除法才能得到余数(常量有例外)。如果您在循环时遇到这种情况,尝试避免它通常是一个好主意。
    • 避免“慢”总是好的! func getAreaFromPolygon (path: CGMutablePath) -> Double { // 得到这个路径的 CGPoints 数组 var Array = path.getPathElementsPoints() Array.append(Array[0]) // 计算面积 var area = 0.0 for i在 0..
    • 我会将 firstPart 和 secondPart 作为变量。优化器应该足够聪明以消除不必要的分配。不要为了微不足道的优化而牺牲代码的可读性
    猜你喜欢
    • 1970-01-01
    • 2020-02-09
    • 2015-03-21
    • 2014-06-09
    • 2011-03-03
    • 2012-12-09
    • 2021-12-28
    • 2012-12-30
    • 1970-01-01
    相关资源
    最近更新 更多