S04E14:正八面体网格生成

358 阅读1分钟

说明

正八面体,就类似于两个金字塔,底对底叠起来的样子。

几何

第一步,还是找几何公式; 第二步,利用公式计算顶点坐标;

let a: Float = radius * sqrtf(2)//棱长
let rm = a / 2 // 中交球半径(过棱中点)
let points: [SIMD3<Float>] = [
    SIMD3<Float>(0, radius, 0),
    SIMD3<Float>(rm, 0, rm),
    SIMD3<Float>(-rm, 0, rm),
    SIMD3<Float>(-rm, 0, -rm),
    SIMD3<Float>(rm, 0, -rm),
    SIMD3<Float>(0, -radius, 0)
]

惟一不同的是,正八面体每个顶点周围有四个面(四条棱),所以每个顶点需要重复 4 次。其余步骤与正四面体一样,代码也是原封不动。

代码

/// 正八面体,radius 为外接球半径,res 三角面剖分次数
public static func generateOctahedron(radius: Float, res: Int = 0) throws -> MeshResource {
    let pointCount = 6
    var triangles = 8
    var vertices = pointCount * 4
    
    var descr = MeshDescriptor()
    var meshPositions: [SIMD3<Float>] = []
    var indices: [UInt32] = []
    var normals: [SIMD3<Float>] = Array(repeating: .zero, count: vertices)
    var textureMap: [SIMD2<Float>] = []
    
    let a: Float = radius * sqrtf(2)//棱长
    let rm = a / 2 // 中交球半径(过棱中点)
    let points: [SIMD3<Float>] = [
        SIMD3<Float>(0, radius, 0),
        SIMD3<Float>(rm, 0, rm),
        SIMD3<Float>(-rm, 0, rm),
        SIMD3<Float>(-rm, 0, -rm),
        SIMD3<Float>(rm, 0, -rm),
        SIMD3<Float>(0, -radius, 0)
    ]
    meshPositions.append(contentsOf: points + points + points + points)
    
    let index: [UInt32] = [
        0, 2, 1,
        0, 3, 2,
        0, 4, 3,
        0, 1, 4,
        
        5, 1, 2,
        5, 2, 3,
        5, 3, 4,
        5, 4, 1
    ]
    var countDict: [UInt32:Int] = [:]
    for ind in index {
        let count = countDict[ind] ?? 0
        indices.append(ind + UInt32(pointCount * count))
        countDict[ind] = count + 1
    }
    
    for i in 0..<triangles {
        let ai = 3 * i
        let bi = 3 * i + 1
        let ci = 3 * i + 2
        
        let i0 = indices[ai]
        let i1 = indices[bi]
        let i2 = indices[ci]
        
        let v0 = meshPositions[Int(i0)]
        let v1 = meshPositions[Int(i1)]
        let v2 = meshPositions[Int(i2)]
        
        let faceNormal = simd_normalize((v0 + v1 + v2) / 3)
        normals[Int(i0)] = faceNormal
        normals[Int(i1)] = faceNormal
        normals[Int(i2)] = faceNormal
    }
    
    for _ in 0..<res {
        let newTriangles = triangles * 4
        let newVertices = vertices + triangles * 3
        
        var newIndices: [UInt32] = []
        var pos: SIMD3<Float>
        
        for i in 0..<triangles {
            let ai = 3 * i
            let bi = 3 * i + 1
            let ci = 3 * i + 2
            
            let i0 = indices[ai]
            let i1 = indices[bi]
            let i2 = indices[ci]
            
            let v0 = meshPositions[Int(i0)]
            let v1 = meshPositions[Int(i1)]
            let v2 = meshPositions[Int(i2)]
            
            let faceNormal = normals[Int(i0)]
            normals.append(contentsOf: [faceNormal, faceNormal, faceNormal])
            // a
            pos = (v0 + v1) * 0.5
            meshPositions.append(pos)

            // b
            pos = (v1 + v2) * 0.5
            meshPositions.append(pos)
            
            // c
            pos = (v2 + v0) * 0.5
            meshPositions.append(pos)
            
            
            let a = UInt32(ai + vertices)
            let b = UInt32(bi + vertices)
            let c = UInt32(ci + vertices)
            newIndices.append(contentsOf: [
                i0, a, c,
                a, i1, b,
                a, b, c,
                c, b, i2
            ])
        }
        
        indices = newIndices
        triangles = newTriangles
        vertices = newVertices
    }
    
    for i in 0..<meshPositions.count {
        let p = meshPositions[i]
        let n = p
      
        textureMap.append(SIMD2<Float>(abs(atan2(n.x, n.z)) / .pi, 1 - acos(n.y/radius) / .pi))
    }
    descr.positions = MeshBuffers.Positions(meshPositions)
    descr.normals = MeshBuffers.Normals(normals)
    descr.textureCoordinates = MeshBuffers.TextureCoordinates(textureMap)
    descr.primitives = .triangles(indices)
    return try MeshResource.generate(from: [descr])
}