说明
圆角矩形就是普通的带有圆角的矩形。因为圆角边与矩形边圆角在位置上是连续的,但是斜率(导数)并不连续,没有统一的公式来确定每个点的位置,所以只能分段处理。
几何
我们对圆角矩形的边进行分段处理,如下图:先排列好最内圈红色的点,然后是黄色的圆角 0,接着是洋红色的点……直到排满一圈为止;然后就开始排列下一圈。
中心的大圆点,代表着其实有多个点组成,可以看做是半径为 0 的圆角矩形。这样一来,进行三角形连线时,就可以像原来一样当做扭曲的矩形处理了。
同样的,贴图 UV 坐标也有两种:环绕式,平铺式。需要注意的是:环绕式需要对 X 轴水平正方向进行特殊处理,以使 UV 坐标开始的点和最后一个点不相同。也就是需要在 X 轴正方向上,每一圈多插入两处点,这两个点位置坐标相同,但 UV 坐标不同。这样贴图出来才不会导致此处三角形出现错误的逆向插值。
代码
private static func angle2(a:simd_float2) -> Float {
var theta = atan2f(a.y, a.x);
if (theta < 0) { theta += .pi * 2.0; }
return theta;
}
public static func generateRoundedRectPlane(width: Float, depth: Float, radius: Float, angularResolution: Int = 24, edgeXResolution: Int = 2, edgeYResolution: Int = 2, radialResolution: Int = 2, circleUV: Bool = true) throws -> MeshResource {
var descr = MeshDescriptor()
var meshPositions: [SIMD3<Float>] = []
var indices: [UInt32] = []
var textureMap: [SIMD2<Float>] = []
let twoPi = Float.pi * 2.0
let halfPi = Float.pi * 0.5
let angular = angularResolution > 2 ? angularResolution : 3
let angularMinusOne = angular - 1
let angularMinusOnef = Float(angularMinusOne)
let radial = radialResolution > 1 ? radialResolution : 2
let radialf = Float(radial)
let radialMinusOnef = radialf - 1.0
let edgeX = edgeXResolution > 1 ? edgeXResolution : 2
let edgeXMinusOne = edgeX - 1
let edgeXMinusOnef = Float(edgeXMinusOne)
let edgeY = edgeYResolution > 1 ? edgeYResolution : 2
let edgeYMinusOne = edgeY - 1
let edgeYMinusOnef = Float(edgeYMinusOne)
let perLoop = (angular - 2) * 4 + (edgeX * 2) + (edgeY * 2) + (circleUV ? 2 : 0)
let widthHalf = width * 0.5
let depthHalf = depth * 0.5
let minDim = (widthHalf < depthHalf ? widthHalf : depthHalf)
let radius = radius > minDim ? minDim : radius
for j in 0..<radial {
let n = Float(j) / radialMinusOnef
// +X, -Y -> +Y
var start = SIMD2<Float>(widthHalf, -depthHalf + radius)
var end = SIMD2<Float>(widthHalf, depthHalf - radius)
for i in 0..<edgeY {
let t = Float(i) / edgeYMinusOnef
let pos = simd_mix(start, end, SIMD2<Float>(t, t))
meshPositions.append(SIMD3<Float>(pos.x, 0, pos.y) * n)
if circleUV {
let angle = angle2(a: pos)
let uvx = angle / twoPi
let uvy = n
textureMap.append(SIMD2<Float>(uvx,uvy))
if i == edgeY/2 - 1 {//start and end UVs are different, so add more points
let newPos = SIMD2<Float>(pos.x * n, 0)
meshPositions.append(SIMD3<Float>(newPos.x, 0, newPos.y))
textureMap.append(SIMD2<Float>(1,uvy))
meshPositions.append(SIMD3<Float>(newPos.x, 0, newPos.y))
textureMap.append(SIMD2<Float>(0,uvy))
}
} else {
textureMap.append(SIMD2<Float>(pos.x * n / width + 0.5, -pos.y * n / depth + 0.5))
}
}
// corner 0
for i in 1..<angularMinusOne {
let t = Float(i) / angularMinusOnef
let theta = t * halfPi
let x = radius * cos(theta)
let y = radius * sin(theta)
let pos = SIMD2<Float>(widthHalf - radius + x, depthHalf - radius + y)
meshPositions.append(SIMD3<Float>(pos.x, 0, pos.y) * n)
if circleUV {
let angle = angle2(a: pos)
let uvx = angle / twoPi
let uvy = n
textureMap.append(SIMD2<Float>(uvx,uvy))
} else {
textureMap.append(SIMD2<Float>(pos.x * n / width + 0.5, -pos.y * n / depth + 0.5))
}
}
// +Y, +X -> -X
start = SIMD2<Float>(widthHalf - radius, depthHalf)
end = SIMD2<Float>(-widthHalf + radius, depthHalf)
for i in 0..<edgeX {
let t = Float(i) / edgeXMinusOnef
let pos = simd_mix(start, end, SIMD2<Float>(t, t))
meshPositions.append(SIMD3<Float>(pos.x, 0, pos.y) * n)
if circleUV {
let angle = angle2(a: pos)
let uvx = angle / twoPi
let uvy = n
textureMap.append(SIMD2<Float>(uvx,uvy))
} else {
textureMap.append(SIMD2<Float>(pos.x * n / width + 0.5, -pos.y * n / depth + 0.5))
}
}
// corner 1
for i in 1..<angularMinusOne {
let t = Float(i) / angularMinusOnef
let theta = t * halfPi + halfPi
let x = radius * cos(theta)
let y = radius * sin(theta)
let pos = SIMD2<Float>(-widthHalf + radius + x, depthHalf - radius + y)
meshPositions.append(SIMD3<Float>(pos.x, 0, pos.y) * n)
if circleUV {
let angle = angle2(a: pos)
let uvx = angle / twoPi
let uvy = n
textureMap.append(SIMD2<Float>(uvx,uvy))
} else {
textureMap.append(SIMD2<Float>(pos.x * n / width + 0.5, -pos.y * n / depth + 0.5))
}
}
// -X, +Y -> -Y
start = SIMD2<Float>(-widthHalf, depthHalf - radius)
end = SIMD2<Float>(-widthHalf, -depthHalf + radius)
for i in 0..<edgeY {
let t = Float(i) / edgeYMinusOnef
let pos = simd_mix(start, end, SIMD2<Float>(t, t))
meshPositions.append(SIMD3<Float>(pos.x, 0, pos.y) * n)
if circleUV {
let angle = angle2(a: pos)
let uvx = angle / twoPi
let uvy = n
textureMap.append(SIMD2<Float>(uvx,uvy))
} else {
textureMap.append(SIMD2<Float>(pos.x * n / width + 0.5, -pos.y * n / depth + 0.5))
}
}
// corner 2
for i in 1..<angularMinusOne {
let t = Float(i) / angularMinusOnef
let theta = t * halfPi + .pi
let x = radius * cos(theta)
let y = radius * sin(theta)
let pos = SIMD2<Float>(-widthHalf + radius + x, -depthHalf + radius + y)
meshPositions.append(SIMD3<Float>(pos.x, 0, pos.y) * n)
if circleUV {
let angle = angle2(a: pos)
let uvx = angle / twoPi
let uvy = n
textureMap.append(SIMD2<Float>(uvx,uvy))
} else {
textureMap.append(SIMD2<Float>(pos.x * n / width + 0.5, -pos.y * n / depth + 0.5))
}
}
// -Y, -X -> +X
start = SIMD2<Float>(-widthHalf + radius, -depthHalf)
end = SIMD2<Float>(widthHalf - radius, -depthHalf)
for i in 0..<edgeX {
let t = Float(i) / edgeXMinusOnef
let pos = simd_mix(start, end, SIMD2<Float>(t, t))
meshPositions.append(SIMD3<Float>(pos.x, 0, pos.y) * n)
if circleUV {
let angle = angle2(a: pos)
let uvx = angle / twoPi
let uvy = n
textureMap.append(SIMD2<Float>(uvx,uvy))
} else {
textureMap.append(SIMD2<Float>(pos.x * n / width + 0.5, -pos.y * n / depth + 0.5))
}
}
// corner 3
for i in 1..<angularMinusOne {
let t = Float(i) / angularMinusOnef
let theta = t * halfPi + 1.5 * .pi
let x = radius * cos(theta)
let y = radius * sin(theta)
let pos = SIMD2<Float>(widthHalf - radius + x, -depthHalf + radius + y)
meshPositions.append(SIMD3<Float>(pos.x, 0, pos.y) * n)
if circleUV {
let angle = angle2(a: pos)
let uvx = angle / twoPi
let uvy = n
textureMap.append(SIMD2<Float>(uvx,uvy))
} else {
textureMap.append(SIMD2<Float>(pos.x * n / width + 0.5, -pos.y * n / depth + 0.5))
}
}
for i in 0..<perLoop {
if j + 1 != radial {
let currLoop = j * perLoop
let nextLoop = (j + 1) * perLoop
let next = (i + 1) % perLoop
let i0 = UInt32(currLoop + i)
let i1 = UInt32(currLoop + next)
let i2 = UInt32(nextLoop + i)
let i3 = UInt32(nextLoop + next)
indices.append(contentsOf: [i3,i2,i0,
i1,i3,i0
])
}
}
}
descr.primitives = .triangles(indices)
descr.positions = MeshBuffer(meshPositions)
descr.textureCoordinates = MeshBuffers.TextureCoordinates(textureMap)
return try .generate(from: [descr])
}