S04E05:方圆形 Squircle 平面网格生成

866 阅读2分钟

说明

超椭圆(superellipse)也称为Lamé曲线,是在笛卡儿坐标系下满足以下方程式的点的集合: 其中n、a及b为正数。

而当其中 a、b 均为 1 时,得到的就不再是椭圆,而是超圆形,如果此时再有 n = 4,那么就得到了方圆形 Squircle。

在几何学中,方圆形是一种性质界于圆形和正方形之间的几何形状。是超椭圆的一个特例,是一种 n 为4的超椭圆,亦可以视为曲边四边形的一种。 方圆形在英文中称为Squircle是由「square」(正方形) and 「circle」(圆形)的组合字,中文同样也是方形和圆形的组合词。

几何

整体逻辑与圆形的顶点分布没有什么大的区别。惟一不同的是,顶点位置的确定不再只是根据三角函数求出,而是要根据 n 的值(代码中为 p),先求出次方值,再开方才能得到。

// 求 p 次方
let den = pow(abs(cost), p) + pow(abs(sint), p)
// 开 p 次方,并求倒数
let phi = 1.0 / pow(den, 1.0 / p)
// 对比圆形,多乘了 phi
let x = radius * phi * cost
let z = radius * phi * sint

那么,当 p = 2 时,phi 其实就是 1,得到的就是一个正圆,p = 4 时,得到的就是方圆形。同样的,贴图模式也有两种。

代码

public static func generateSquirclePlane(size: Float, p: Float, angularResolution: Int, radialResolution: Int, circleUV: Bool = true) throws -> MeshResource {
    var descr = MeshDescriptor()
    var meshPositions: [SIMD3<Float>] = []
    var indices: [UInt32] = []
    var textureMap: [SIMD2<Float>] = []
    
    let rad = size * 0.5
    let angular = angularResolution > 2 ? angularResolution : 3
    let radial = radialResolution > 1 ? radialResolution : 1

    let perLoop = angular + 1
    for r in 0...radial {
        let k = Float(r) / Float(radial)
        let radius = map(input: Float(r), inMin: 0, inMax: Float(radial), outMin: 0, outMax: rad)
        for a in 0...angular {
            let t = Float(a) / Float(angular)
            let theta = 2.0 * .pi * t

            let cost = cos(theta)
            let sint = sin(theta)

            let den = pow(abs(cost), p) + pow(abs(sint), p)
            let phi = 1.0 / pow(den, 1.0 / p)

            let x = radius * phi * cost
            let z = radius * phi * sint
            meshPositions.append(SIMD3<Float>(x, 0, z))
            if circleUV {
                textureMap.append(SIMD2<Float>(t, k))
            } else {
                textureMap.append(SIMD2<Float>(x/size+0.5, -z/size+0.5))
            }
            
            if (r != radial && a != angular) {
                let index = UInt32(a + r * perLoop)

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

                indices.append(contentsOf: [tr,bl,tl,
                                            br,bl,tr])
            }
        }
    }
    
    descr.primitives = .triangles(indices)
    descr.positions = MeshBuffer(meshPositions)
    descr.textureCoordinates = MeshBuffers.TextureCoordinates(textureMap)
    return try .generate(from: [descr])
}
private static func map(input: Float, inMin: Float, inMax:Float, outMin:Float, outMax: Float) -> Float {
    return ((input - inMin) / (inMax - inMin) * (outMax - outMin)) + outMin;
}