说明
圆柱体由上下两个圆,以及中间的管状柱体组成。
几何
圆柱体网格与圆锥体是类似的,我们先构造一个圆柱面,再分别添加上下两个圆,就成了一个圆柱体。因为圆锥表面的法线是倾斜的,需要进行计算,而圆柱的法线要简单的多。
同前面一样,我们可以设置圆柱的上下面与中间部分是否共用一个贴图:
需要注意的是,上下两个面,法线方向不同,上面的法线朝上,下面的法线朝下。
除了法线,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])
}