说明
Meta公司(原Facebook)开发布会的时候,宣传视频里有这么一段三维动画:
这就是李萨如曲线 (Lissajous Curve)的一种,大家所说的 Meta Logo 就是李萨如曲线的一种形式:即有 2 个完整波动周期( 2*2pi)的李萨如曲线。
几何
从直观上来说,Meta Logo 其实就是对圆环体的扭曲变形,也就是对原来的圆环高度坐标,添加 2* 2pi 个周期的波动就可以了。
但是,如果直接对圆环上的所有点直接添加 y 坐标波动,会让圆管变扁。因此,我们还是需要利用切线,来计算新的法线,进而得到粗细均匀的圆环网格。
首先,我们修改圆环中心点坐标的计算,添加 y 坐标的计算 let centerY = sin(2 * slice) * height * 0.5
。然后在计算切线时,仍然是求导即可。希望大家还记得复合函数求导的公式,这里我们需要对复合函数进行求导得到 tangentY = cos(2 * slice) * 2 * height * 0.5
复合函数求导链式法则:f[(g(x))]' = f'[g(x)] * g'(x),也就是说:sin'(2x) = cos(2x) * 2;sin'(3x) = cos(3x) * 3;sin'(4x) = cos(4x) * 4
最后需要注意的是,由于求出的切线长度并不是 1,所以需要另外进行归一化处理。
当然,这个波动周期也可以是 0,1,2,3,4……等其他数字
代码
public static func generateLissajousCurveTorus(minorRadius: Float, majorRadius: Float, height: Float, cycleTimes: Int = 2, minorResolution :Int = 24, majorResolution: Int = 96) throws -> MeshResource {
var descr = MeshDescriptor()
var meshPositions: [SIMD3<Float>] = []
var indices: [UInt32] = []
var normals: [SIMD3<Float>] = []
var textureMap: [SIMD2<Float>] = []
let slices = majorResolution > 3 ? majorResolution : 4
let angular = minorResolution > 2 ? minorResolution : 3
let cycleTimesf = Float(cycleTimes)
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 centerY = sin(cycleTimesf * slice) * height * 0.5
let centerZ = sinSlice * majorRadius
let center = SIMD3<Float>(centerX, centerY, centerZ)
let tangentN = simd_normalize(SIMD3<Float>(-sinSlice, cos(cycleTimesf * slice) * cycleTimesf * height * 0.5 / majorRadius, 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])
}