S04E08: 圆柱体网格生成

627 阅读2分钟

说明

圆柱体由上下两个圆,以及中间的管状柱体组成。

几何

圆柱体网格与圆锥体是类似的,我们先构造一个圆柱面,再分别添加上下两个圆,就成了一个圆柱体。因为圆锥表面的法线是倾斜的,需要进行计算,而圆柱的法线要简单的多。

同前面一样,我们可以设置圆柱的上下面与中间部分是否共用一个贴图:

需要注意的是,上下两个面,法线方向不同,上面的法线朝上,下面的法线朝下。

除了法线,uv 坐标也必须反转一下,不然贴图会出现镜像;三角形的索引也需要逆向排列,不然会出现从外面看不到,而从圆柱内部却能看到底面的情况。

代码

public static func generateCylinder(radius: Float, height: Float, angularResolution: Int = 24, radialResolution: Int = 1, verticalResolution: Int = 1, splitFaces: Bool = false, circleUV: Bool = true) throws -> MeshResource {
    var descr = MeshDescriptor()
    var meshPositions: [SIMD3<Float>] = []
    var indices: [UInt32] = []
    var normals: [SIMD3<Float>] = []
    var textureMap: [SIMD2<Float>] = []
    var materials: [UInt32] = []
    
    
    let radial = radialResolution > 0 ? radialResolution : 1
    let angular = angularResolution > 2 ? angularResolution : 3
    let vertical = verticalResolution > 0 ? verticalResolution : 1

    let radialf = Float(radial)
    let angularf = Float(angular)
    let verticalf = Float(vertical)

    let radialInc = radius / radialf
    let angularInc = (2.0 * .pi) / angularf
    let verticalInc = height / verticalf

    let perLoop = angular + 1
    let verticesPerCircle = perLoop * (radial + 1)
    let yOffset = -0.5 * height
    
    for v in 0...vertical {
        let vf = Float(v)
        let y = yOffset + vf * verticalInc
        for a in 0...angular {
            let af = Float(a)
            let angle = af * angularInc
            let x = cos(angle)
            let z = sin(angle)
            
            meshPositions.append(SIMD3<Float>(radius * x, y, radius * z))
            normals.append(SIMD3<Float>(x, 0.0, z))
            textureMap.append(SIMD2<Float>(1.0 - af / angularf, vf / verticalf))
            
            if (v != vertical && a != angular) {
                let index = a + v * perLoop

                let tl = UInt32(index)
                let tr = tl + 1
                let bl = UInt32(index + perLoop)
                let br = bl + 1

                indices.append(contentsOf: [
                    tl, bl, tr,
                    tr, bl, br
                ])
            }
        }
    }
    if splitFaces {
        materials.append(contentsOf: Array(repeating: 0, count: angular * vertical * 2))
    }
    
    var flip = true
    var direction: Float = 1.0
    var vertexOffset = meshPositions.count
    for _ in 0..<2 {
        for r in 0...radial {
            let rf = Float(r)
            let rad = rf * radialInc
            for a in 0...angular {
                let af = Float(a)
                let angle = af * angularInc
                let x = rad * cos(angle)
                let y = rad * sin(angle)
                
                meshPositions.append(SIMD3<Float>(x, direction * height * 0.5, y))
                normals.append(SIMD3<Float>(0.0, direction, 0.0))
                if circleUV {
                    textureMap.append(SIMD2<Float>(flip ? af / angularf : 1.0 - af / angularf, rf / radialf))
                } else {
                    textureMap.append(SIMD2<Float>(-direction * x/radius/2+0.5, y/radius/2+0.5))
                }
                if (r != radial && a != angular) {
                    let index = vertexOffset + a + r * perLoop;

                    let tl = UInt32(index)
                    let tr = tl + 1
                    let bl = UInt32(index + perLoop)
                    let br = bl + 1

                    if (flip) {
                        indices.append(contentsOf: [
                            tl, tr, bl,
                            tr, br, bl
                        ])
                    } else {
                        indices.append(contentsOf: [
                            tl, bl, tr,
                            tr, bl, br
                        ])
                    }
                }
            }
        }
        vertexOffset += verticesPerCircle
        direction *= -1.0
        flip = !flip
    }
    if splitFaces {
        materials.append(contentsOf: Array(repeating: 1, count: angular * radial * 4))
    }
    
    descr.positions = MeshBuffers.Positions(meshPositions)
    descr.normals = MeshBuffers.Normals(normals)
    descr.textureCoordinates = MeshBuffers.TextureCoordinates(textureMap)
    descr.primitives = .triangles(indices)
    if !materials.isEmpty {
        descr.materials = MeshDescriptor.Materials.perFace(materials)
    }
    return try MeshResource.generate(from: [descr])
}