说明
超椭圆(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;
}