S04E10: 圆环体网格生成

598 阅读2分钟

说明

所谓圆环体,就是类似手镯一样的环体,也就是类似于甜甜圈的物体🍩

几何

圆环体有两个参数:大半径和小半径,我们还是将两个半径分别进行旋转,构造出纵横网格,然后把这些点连接起来,组成网格体。 如上图所示,我们可以很容易根据大半径得到点 B 的坐标,但是如何计算点 CDFG 组成的小圆呢?我们这里选择以切线方式进行计算:对点 B 的坐标进行求导,就可以得到点 B 处的切线向量,然后再把向量 BC 绕切线进行不同角度的旋转,就能得到点 DFG 的坐标。

也就是,首先我们要求出圆环的中心点的坐标,这个坐标只和大半径及其旋转角度相关 let center = SIMD3<Float>(cos(slice) * majorRadius, 0, sin(slice) * majorRadius) ,我们对其中的slice变量进行求导得到 let tangent = SIMD3<Float>(-sin(slice) * majorRadius, 0, cos(slice) * majorRadius) 我们希望得到的切线向量是归一化的向量,所以直接忽略掉大半径,就得到了长度为 1 的切线向量 tangentN = SIMD3<Float>(-sin(slice), 0, cos(slice))

导数小知识:cos'(x) = -sin(x), sin'(x) = cos(x)

接下来我们将该点处的水平法线(水平法线可以直接由中心点的坐标得到)进行旋转,就得到了真正的法线,最后我们将大半径上的中心点沿法线进行移动,就得到了圆环上的点。

代码

public static func generateTorus(minorRadius: Float, majorRadius: Float, minorResolution :Int = 24, majorResolution: Int = 24) throws -> MeshResource {
    var descr = MeshDescriptor()
    var meshPositions: [SIMD3<Float>] = []
    var indices: [UInt32] = []
    var normals: [SIMD3<Float>] = []
    var textureMap: [SIMD2<Float>] = []
    
    let slices = majorResolution > 2 ? majorResolution : 3
    let angular = minorResolution > 2 ? minorResolution : 3

    let slicesf = Float(slices)
    let angularf = Float(angular)

    let limit = Float.pi * 2.0
    let sliceInc = limit / slicesf
    let angularInc = limit / angularf

    let perLoop = angular + 1
    
    for s in 0...slices {
        let sf = Float(s)
        let slice = sf * sliceInc
        let cosSlice = cos(slice)
        let sinSlice = sin(slice)
        
        let centerX = cosSlice * majorRadius
        let centerZ = sinSlice * majorRadius
        let center = SIMD3<Float>(centerX, 0, centerZ)
        
        let tangentN = SIMD3<Float>(-sinSlice, 0, cosSlice)
        let vectorN = SIMD3<Float>(cosSlice, 0, sinSlice)
        for a in 0...angular {
            let af = Float(a)
            let angle = af * angularInc
            
            let normal = simd_act(simd_quatf(angle: angle, axis: tangentN), vectorN)

            meshPositions.append(center + normal * minorRadius)
            normals.append(normal)
            textureMap.append(SIMD2<Float>(af / angularf, sf / slicesf))
            
            if (s != slices && a != angular) {
                let index = a + s * perLoop

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

                indices.append(contentsOf: [
                    tl, tr, bl,
                    tr, br, bl
                ])
            }
        }
    }
    
    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])
}