如何计算 SCNPlane 的法向量?

How to calculate normal vector of a SCNPlane?

目标:定义由 SCNPlane 表示的平面的法向量。

正如Apple文档中提到的Working with Vectors - Calculate the Normal of a Triangle,我可以根据平面的3个点来计算法向量。这里的问题是我不知道如何得到 3 点,它们构成了正确的三角形。我注意到 SCNPlane 有一个 属性 boundingBox 并且它可以代表 2 个三角形顶点(minmax)。如何找到 SCNPlane 上的第三个顶点?我不能使用 SCNPlane 的中心,因为它会创建一条线以及来自 boundingBox.

minmax

有没有其他方法可以帮助我检索 SCNPlane 的法向量?

documentation我们得知

The surface is one-sided. Its surface normal vectors point in the positive z-axis direction of its local coordinate space, so it is only visible from that direction by default.

SCNPlane 的法线在局部 space 中始终是 (0, 0, 1),并且无法更改。

当它附加到一个节点时,该节点的方向决定了另一个坐标系中的法线。您可以使用 simdConvertVector:toNode: 在坐标 spaces:

之间进行转换
// normal expressed in world space
let normal = simd_normalize(node.simdConvertVector(simd_float3(0, 0, 1), to: nil))

要添加到已接受的答案中,为了检索定义平面的点,您可以查询平面的几何来源。

let plane = SCNPlane(width: 100, height: 20)
print("Sources for normal: \(vertices(sources: plane.sources(for: .normal)))")
print("Sources for vertex: \(vertices(sources: plane.sources(for: .vertex)))")

extension UnsafeRawPointer {
    func loadUnaligned<T>(as: T.Type, count: Int) -> [T] {
        assert(_isPOD(T.self)) // relies on the type being POD (no refcounting or other management)
        let buffer = UnsafeMutablePointer<T>.allocate(capacity: count)
        defer { buffer.deallocate() }
        memcpy(buffer, self, MemoryLayout<T>.size * count)
        return (0..<count).map({ index in buffer.advanced(by: index).pointee })
    }
}

func vertices(sources: [SCNGeometrySource]) -> [[SCNVector3]] {

    var result = [[SCNVector3]]()
    result.reserveCapacity(sources.count)
    for source in sources {
        precondition(source.usesFloatComponents == true, "SCNVector3 can handle only three-component vectors whose components are floating-point values, i.e., floats or doubles")
        precondition(source.componentsPerVector == 3, "SCNVector3 can only be used for three components per vector")
        
        let shouldUseFloatNotDouble: Bool
        if source.bytesPerComponent == 4 {
            shouldUseFloatNotDouble = true
        }
        else if source.bytesPerComponent == 8 {
            shouldUseFloatNotDouble = false
        }
        else {
            assert(false, "The SCNGeometrySource has reported an unexpected byte size for its vector components, not 4 bytes (float) or 8 bytes (double) but \(source.bytesPerComponent). I am not equipped for this so I am going to use floats and hope for the best. This will probably not work. Sorry.")
            shouldUseFloatNotDouble = true
        }
        
        let vectors = source.data.withUnsafeBytes {
            (p: UnsafeRawBufferPointer) -> [SCNVector3] in
            if (shouldUseFloatNotDouble) {
                let simdArray = (p.baseAddress! + source.dataOffset).loadUnaligned(as: SIMD3<Float>.self, count: source.vectorCount)
                return simdArray.map { simd in SCNVector3(simd)}
            } else {
                let simdArray = (p.baseAddress! + source.dataOffset).loadUnaligned(as: SIMD3<Double>.self, count: source.vectorCount)
                return simdArray.map { simd in SCNVector3(simd)}
            }
        }
        result.append(vectors)
    }
    return result
}

输出:

Sources for normal: [[__C.SCNVector3(x: 0.0, y: 0.0, z: 1.0), __C.SCNVector3(x: 1.0, y: 0.5, z: -0.5), __C.SCNVector3(x: 0.0, y: 0.0, z: 1.0), __C.SCNVector3(x: 1.0, y: -0.5, z: 0.5)]]
Sources for vertex: [[__C.SCNVector3(x: -0.5, y: -0.5, z: 0.0), __C.SCNVector3(x: 0.0, y: 1.0, z: 0.0), __C.SCNVector3(x: 0.5, y: -0.5, z: 0.0), __C.SCNVector3(x: 0.0, y: 1.0, z: 1.0)]]