515 lines
21 KiB
Swift
515 lines
21 KiB
Swift
// PrimitiveGenerator.swift
|
|
// Procedural mesh generation for all primitive types.
|
|
|
|
import simd
|
|
|
|
/// Output from a primitive generator — ready for GPU upload.
|
|
struct PrimitiveMeshData {
|
|
let vertices: [MeshVertex] // Solid triangles
|
|
let wireVertices: [MeshVertex] // Wireframe lines
|
|
let faceCount: Int
|
|
let edgeCount: Int
|
|
let boundingBoxMin: SIMD3<Float>
|
|
let boundingBoxMax: SIMD3<Float>
|
|
}
|
|
|
|
enum PrimitiveGenerator {
|
|
|
|
// MARK: - Cube
|
|
|
|
static func cube(
|
|
size: Float = 2.0,
|
|
color: SIMD4<Float> = SIMD4(0.63, 0.63, 0.63, 1)
|
|
) -> PrimitiveMeshData {
|
|
let h = size * 0.5
|
|
let positions: [SIMD3<Float>] = [
|
|
.init(-h, -h, h), .init( h, -h, h), .init( h, h, h), .init(-h, h, h),
|
|
.init( h, -h, -h), .init(-h, -h, -h), .init(-h, h, -h), .init( h, h, -h),
|
|
]
|
|
let faces: [(indices: [Int], normal: SIMD3<Float>)] = [
|
|
([0,1,2,3], .init( 0, 0, 1)), // front
|
|
([4,5,6,7], .init( 0, 0,-1)), // back
|
|
([3,2,7,6], .init( 0, 1, 0)), // top
|
|
([5,4,1,0], .init( 0,-1, 0)), // bottom
|
|
([1,4,7,2], .init( 1, 0, 0)), // right
|
|
([5,0,3,6], .init(-1, 0, 0)), // left
|
|
]
|
|
|
|
var verts: [MeshVertex] = []
|
|
for face in faces {
|
|
let (idx, n) = (face.indices, face.normal)
|
|
// Two triangles per quad
|
|
for ti in [(0,1,2), (0,2,3)] {
|
|
for i in [ti.0, ti.1, ti.2] {
|
|
verts.append(MeshVertex(position: positions[idx[i]], normal: n, color: color))
|
|
}
|
|
}
|
|
}
|
|
|
|
let edges: [(Int,Int)] = [
|
|
(0,1),(1,2),(2,3),(3,0),
|
|
(4,5),(5,6),(6,7),(7,4),
|
|
(0,5),(1,4),(2,7),(3,6),
|
|
]
|
|
let wireColor = SIMD4<Float>(0, 0, 0, 0.4)
|
|
var wireVerts: [MeshVertex] = []
|
|
for (a, b) in edges {
|
|
wireVerts.append(.init(position: positions[a], normal: .zero, color: wireColor))
|
|
wireVerts.append(.init(position: positions[b], normal: .zero, color: wireColor))
|
|
}
|
|
|
|
return PrimitiveMeshData(
|
|
vertices: verts,
|
|
wireVertices: wireVerts,
|
|
faceCount: 6,
|
|
edgeCount: 12,
|
|
boundingBoxMin: .init(repeating: -h),
|
|
boundingBoxMax: .init(repeating: h)
|
|
)
|
|
}
|
|
|
|
// MARK: - Plane
|
|
|
|
static func plane(
|
|
size: Float = 2.0,
|
|
color: SIMD4<Float> = SIMD4(0.63, 0.63, 0.63, 1)
|
|
) -> PrimitiveMeshData {
|
|
let h = size * 0.5
|
|
let n: SIMD3<Float> = .init(0, 1, 0)
|
|
let p: [SIMD3<Float>] = [
|
|
.init(-h, 0, -h), .init( h, 0, -h),
|
|
.init( h, 0, h), .init(-h, 0, h),
|
|
]
|
|
let verts: [MeshVertex] = [
|
|
.init(position: p[0], normal: n, color: color),
|
|
.init(position: p[1], normal: n, color: color),
|
|
.init(position: p[2], normal: n, color: color),
|
|
.init(position: p[0], normal: n, color: color),
|
|
.init(position: p[2], normal: n, color: color),
|
|
.init(position: p[3], normal: n, color: color),
|
|
]
|
|
let wc = SIMD4<Float>(0, 0, 0, 0.4)
|
|
let wireVerts: [MeshVertex] = [
|
|
.init(position: p[0], normal: .zero, color: wc),
|
|
.init(position: p[1], normal: .zero, color: wc),
|
|
.init(position: p[1], normal: .zero, color: wc),
|
|
.init(position: p[2], normal: .zero, color: wc),
|
|
.init(position: p[2], normal: .zero, color: wc),
|
|
.init(position: p[3], normal: .zero, color: wc),
|
|
.init(position: p[3], normal: .zero, color: wc),
|
|
.init(position: p[0], normal: .zero, color: wc),
|
|
]
|
|
return PrimitiveMeshData(
|
|
vertices: verts, wireVertices: wireVerts,
|
|
faceCount: 1, edgeCount: 4,
|
|
boundingBoxMin: .init(-h, 0, -h),
|
|
boundingBoxMax: .init(h, 0, h)
|
|
)
|
|
}
|
|
|
|
// MARK: - UV Sphere
|
|
|
|
static func uvSphere(
|
|
radius: Float = 1.0,
|
|
segments: Int = 32,
|
|
rings: Int = 16,
|
|
color: SIMD4<Float> = SIMD4(0.63, 0.63, 0.63, 1)
|
|
) -> PrimitiveMeshData {
|
|
var verts: [MeshVertex] = []
|
|
var wireVerts: [MeshVertex] = []
|
|
let wc = SIMD4<Float>(0, 0, 0, 0.3)
|
|
|
|
// Generate grid of positions
|
|
var grid: [[SIMD3<Float>]] = []
|
|
for ring in 0...rings {
|
|
var row: [SIMD3<Float>] = []
|
|
let phi = Float.pi * Float(ring) / Float(rings)
|
|
for seg in 0...segments {
|
|
let theta = 2.0 * Float.pi * Float(seg) / Float(segments)
|
|
let x = radius * sinf(phi) * cosf(theta)
|
|
let y = radius * cosf(phi)
|
|
let z = radius * sinf(phi) * sinf(theta)
|
|
row.append(.init(x, y, z))
|
|
}
|
|
grid.append(row)
|
|
}
|
|
|
|
// Triangulate
|
|
for ring in 0..<rings {
|
|
for seg in 0..<segments {
|
|
let p00 = grid[ring][seg]
|
|
let p10 = grid[ring + 1][seg]
|
|
let p01 = grid[ring][seg + 1]
|
|
let p11 = grid[ring + 1][seg + 1]
|
|
|
|
let n00 = normalize(p00)
|
|
let n10 = normalize(p10)
|
|
let n01 = normalize(p01)
|
|
let n11 = normalize(p11)
|
|
|
|
if ring != 0 {
|
|
verts.append(.init(position: p00, normal: n00, color: color))
|
|
verts.append(.init(position: p10, normal: n10, color: color))
|
|
verts.append(.init(position: p01, normal: n01, color: color))
|
|
}
|
|
if ring != rings - 1 {
|
|
verts.append(.init(position: p01, normal: n01, color: color))
|
|
verts.append(.init(position: p10, normal: n10, color: color))
|
|
verts.append(.init(position: p11, normal: n11, color: color))
|
|
}
|
|
|
|
// Wireframe: horizontal + vertical lines
|
|
wireVerts.append(.init(position: p00, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: p01, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: p00, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: p10, normal: .zero, color: wc))
|
|
}
|
|
}
|
|
|
|
return PrimitiveMeshData(
|
|
vertices: verts, wireVertices: wireVerts,
|
|
faceCount: segments * rings,
|
|
edgeCount: segments * rings * 2,
|
|
boundingBoxMin: .init(repeating: -radius),
|
|
boundingBoxMax: .init(repeating: radius)
|
|
)
|
|
}
|
|
|
|
// MARK: - Cylinder
|
|
|
|
static func cylinder(
|
|
radius: Float = 1.0,
|
|
height: Float = 2.0,
|
|
segments: Int = 32,
|
|
color: SIMD4<Float> = SIMD4(0.63, 0.63, 0.63, 1)
|
|
) -> PrimitiveMeshData {
|
|
let halfH = height * 0.5
|
|
var verts: [MeshVertex] = []
|
|
var wireVerts: [MeshVertex] = []
|
|
let wc = SIMD4<Float>(0, 0, 0, 0.3)
|
|
|
|
for i in 0..<segments {
|
|
let a0 = 2.0 * Float.pi * Float(i) / Float(segments)
|
|
let a1 = 2.0 * Float.pi * Float(i + 1) / Float(segments)
|
|
let c0 = cosf(a0), s0 = sinf(a0)
|
|
let c1 = cosf(a1), s1 = sinf(a1)
|
|
|
|
let bl = SIMD3<Float>(radius * c0, -halfH, radius * s0)
|
|
let br = SIMD3<Float>(radius * c1, -halfH, radius * s1)
|
|
let tl = SIMD3<Float>(radius * c0, halfH, radius * s0)
|
|
let tr = SIMD3<Float>(radius * c1, halfH, radius * s1)
|
|
|
|
let n0 = normalize(SIMD3<Float>(c0, 0, s0))
|
|
let n1 = normalize(SIMD3<Float>(c1, 0, s1))
|
|
|
|
// Side quad (2 tris)
|
|
verts.append(.init(position: bl, normal: n0, color: color))
|
|
verts.append(.init(position: br, normal: n1, color: color))
|
|
verts.append(.init(position: tl, normal: n0, color: color))
|
|
verts.append(.init(position: tl, normal: n0, color: color))
|
|
verts.append(.init(position: br, normal: n1, color: color))
|
|
verts.append(.init(position: tr, normal: n1, color: color))
|
|
|
|
// Top cap tri
|
|
let topN: SIMD3<Float> = .init(0, 1, 0)
|
|
verts.append(.init(position: .init(0, halfH, 0), normal: topN, color: color))
|
|
verts.append(.init(position: tl, normal: topN, color: color))
|
|
verts.append(.init(position: tr, normal: topN, color: color))
|
|
|
|
// Bottom cap tri
|
|
let botN: SIMD3<Float> = .init(0, -1, 0)
|
|
verts.append(.init(position: .init(0, -halfH, 0), normal: botN, color: color))
|
|
verts.append(.init(position: br, normal: botN, color: color))
|
|
verts.append(.init(position: bl, normal: botN, color: color))
|
|
|
|
// Wireframe
|
|
wireVerts.append(.init(position: tl, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: tr, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: bl, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: br, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: bl, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: tl, normal: .zero, color: wc))
|
|
}
|
|
|
|
return PrimitiveMeshData(
|
|
vertices: verts, wireVertices: wireVerts,
|
|
faceCount: segments * 3,
|
|
edgeCount: segments * 3,
|
|
boundingBoxMin: .init(-radius, -halfH, -radius),
|
|
boundingBoxMax: .init(radius, halfH, radius)
|
|
)
|
|
}
|
|
|
|
// MARK: - Torus
|
|
|
|
static func torus(
|
|
majorRadius: Float = 1.0,
|
|
minorRadius: Float = 0.3,
|
|
majorSegments: Int = 48,
|
|
minorSegments: Int = 12,
|
|
color: SIMD4<Float> = SIMD4(0.63, 0.63, 0.63, 1)
|
|
) -> PrimitiveMeshData {
|
|
var verts: [MeshVertex] = []
|
|
var wireVerts: [MeshVertex] = []
|
|
let wc = SIMD4<Float>(0, 0, 0, 0.3)
|
|
|
|
func torusPoint(_ u: Float, _ v: Float) -> SIMD3<Float> {
|
|
let cu = cosf(u), su = sinf(u)
|
|
let cv = cosf(v), sv = sinf(v)
|
|
return SIMD3<Float>(
|
|
(majorRadius + minorRadius * cv) * cu,
|
|
minorRadius * sv,
|
|
(majorRadius + minorRadius * cv) * su
|
|
)
|
|
}
|
|
|
|
func torusNormal(_ u: Float, _ v: Float) -> SIMD3<Float> {
|
|
let cu = cosf(u), su = sinf(u)
|
|
let cv = cosf(v), sv = sinf(v)
|
|
return normalize(SIMD3<Float>(cv * cu, sv, cv * su))
|
|
}
|
|
|
|
for i in 0..<majorSegments {
|
|
let u0 = 2.0 * Float.pi * Float(i) / Float(majorSegments)
|
|
let u1 = 2.0 * Float.pi * Float(i + 1) / Float(majorSegments)
|
|
|
|
for j in 0..<minorSegments {
|
|
let v0 = 2.0 * Float.pi * Float(j) / Float(minorSegments)
|
|
let v1 = 2.0 * Float.pi * Float(j + 1) / Float(minorSegments)
|
|
|
|
let p00 = torusPoint(u0, v0); let n00 = torusNormal(u0, v0)
|
|
let p10 = torusPoint(u1, v0); let n10 = torusNormal(u1, v0)
|
|
let p01 = torusPoint(u0, v1); let n01 = torusNormal(u0, v1)
|
|
let p11 = torusPoint(u1, v1); let n11 = torusNormal(u1, v1)
|
|
|
|
verts.append(.init(position: p00, normal: n00, color: color))
|
|
verts.append(.init(position: p10, normal: n10, color: color))
|
|
verts.append(.init(position: p01, normal: n01, color: color))
|
|
verts.append(.init(position: p01, normal: n01, color: color))
|
|
verts.append(.init(position: p10, normal: n10, color: color))
|
|
verts.append(.init(position: p11, normal: n11, color: color))
|
|
|
|
// Wireframe (major + minor rings)
|
|
wireVerts.append(.init(position: p00, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: p10, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: p00, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: p01, normal: .zero, color: wc))
|
|
}
|
|
}
|
|
|
|
let ext = majorRadius + minorRadius
|
|
return PrimitiveMeshData(
|
|
vertices: verts, wireVertices: wireVerts,
|
|
faceCount: majorSegments * minorSegments,
|
|
edgeCount: majorSegments * minorSegments * 2,
|
|
boundingBoxMin: .init(-ext, -minorRadius, -ext),
|
|
boundingBoxMax: .init(ext, minorRadius, ext)
|
|
)
|
|
}
|
|
|
|
// MARK: - Cone
|
|
|
|
static func cone(
|
|
radius: Float = 1.0,
|
|
height: Float = 2.0,
|
|
segments: Int = 32,
|
|
color: SIMD4<Float> = SIMD4(0.63, 0.63, 0.63, 1)
|
|
) -> PrimitiveMeshData {
|
|
let halfH = height * 0.5
|
|
let apex = SIMD3<Float>(0, halfH, 0)
|
|
var verts: [MeshVertex] = []
|
|
var wireVerts: [MeshVertex] = []
|
|
let wc = SIMD4<Float>(0, 0, 0, 0.3)
|
|
|
|
for i in 0..<segments {
|
|
let a0 = 2.0 * Float.pi * Float(i) / Float(segments)
|
|
let a1 = 2.0 * Float.pi * Float(i + 1) / Float(segments)
|
|
let b0 = SIMD3<Float>(radius * cosf(a0), -halfH, radius * sinf(a0))
|
|
let b1 = SIMD3<Float>(radius * cosf(a1), -halfH, radius * sinf(a1))
|
|
|
|
// Side normal (approximation)
|
|
let mid = normalize((b0 + b1) * 0.5 - SIMD3<Float>(0, -halfH, 0))
|
|
let sideN = normalize(SIMD3<Float>(mid.x, radius / height, mid.z))
|
|
|
|
// Side tri
|
|
verts.append(.init(position: apex, normal: sideN, color: color))
|
|
verts.append(.init(position: b0, normal: sideN, color: color))
|
|
verts.append(.init(position: b1, normal: sideN, color: color))
|
|
|
|
// Bottom cap
|
|
let botN = SIMD3<Float>(0, -1, 0)
|
|
verts.append(.init(position: .init(0, -halfH, 0), normal: botN, color: color))
|
|
verts.append(.init(position: b1, normal: botN, color: color))
|
|
verts.append(.init(position: b0, normal: botN, color: color))
|
|
|
|
// Wire
|
|
wireVerts.append(.init(position: b0, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: b1, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: apex, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: b0, normal: .zero, color: wc))
|
|
}
|
|
|
|
return PrimitiveMeshData(
|
|
vertices: verts, wireVertices: wireVerts,
|
|
faceCount: segments * 2,
|
|
edgeCount: segments * 2,
|
|
boundingBoxMin: .init(-radius, -halfH, -radius),
|
|
boundingBoxMax: .init(radius, halfH, radius)
|
|
)
|
|
}
|
|
|
|
// MARK: - Icosphere
|
|
|
|
static func icosphere(
|
|
radius: Float = 1.0,
|
|
subdivisions: Int = 2,
|
|
color: SIMD4<Float> = SIMD4(0.63, 0.63, 0.63, 1)
|
|
) -> PrimitiveMeshData {
|
|
// Start with icosahedron
|
|
let t = (1.0 + sqrtf(5.0)) * 0.5
|
|
var positions: [SIMD3<Float>] = [
|
|
normalize(.init(-1, t, 0)), normalize(.init(1, t, 0)),
|
|
normalize(.init(-1,-t, 0)), normalize(.init(1,-t, 0)),
|
|
normalize(.init(0,-1, t)), normalize(.init(0, 1, t)),
|
|
normalize(.init(0,-1,-t)), normalize(.init(0, 1,-t)),
|
|
normalize(.init(t, 0,-1)), normalize(.init(t, 0, 1)),
|
|
normalize(.init(-t,0,-1)), normalize(.init(-t,0, 1)),
|
|
].map { $0 * radius }
|
|
|
|
var triangles: [(Int,Int,Int)] = [
|
|
(0,11,5),(0,5,1),(0,1,7),(0,7,10),(0,10,11),
|
|
(1,5,9),(5,11,4),(11,10,2),(10,7,6),(7,1,8),
|
|
(3,9,4),(3,4,2),(3,2,6),(3,6,8),(3,8,9),
|
|
(4,9,5),(2,4,11),(6,2,10),(8,6,7),(9,8,1),
|
|
]
|
|
|
|
// Subdivide
|
|
var midpointCache: [UInt64: Int] = [:]
|
|
func midpoint(_ a: Int, _ b: Int) -> Int {
|
|
let key = UInt64(min(a,b)) << 32 | UInt64(max(a,b))
|
|
if let cached = midpointCache[key] { return cached }
|
|
let mid = normalize((positions[a] + positions[b]) * 0.5) * radius
|
|
positions.append(mid)
|
|
let idx = positions.count - 1
|
|
midpointCache[key] = idx
|
|
return idx
|
|
}
|
|
|
|
for _ in 0..<subdivisions {
|
|
var newTris: [(Int,Int,Int)] = []
|
|
for (a,b,c) in triangles {
|
|
let ab = midpoint(a,b)
|
|
let bc = midpoint(b,c)
|
|
let ca = midpoint(c,a)
|
|
newTris.append((a, ab, ca))
|
|
newTris.append((b, bc, ab))
|
|
newTris.append((c, ca, bc))
|
|
newTris.append((ab, bc, ca))
|
|
}
|
|
triangles = newTris
|
|
midpointCache.removeAll()
|
|
}
|
|
|
|
var verts: [MeshVertex] = []
|
|
var wireVerts: [MeshVertex] = []
|
|
let wc = SIMD4<Float>(0, 0, 0, 0.3)
|
|
|
|
for (a,b,c) in triangles {
|
|
let pa = positions[a], pb = positions[b], pc = positions[c]
|
|
let na = normalize(pa), nb = normalize(pb), nc = normalize(pc)
|
|
verts.append(.init(position: pa, normal: na, color: color))
|
|
verts.append(.init(position: pb, normal: nb, color: color))
|
|
verts.append(.init(position: pc, normal: nc, color: color))
|
|
|
|
wireVerts.append(.init(position: pa, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: pb, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: pb, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: pc, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: pc, normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: pa, normal: .zero, color: wc))
|
|
}
|
|
|
|
return PrimitiveMeshData(
|
|
vertices: verts, wireVertices: wireVerts,
|
|
faceCount: triangles.count,
|
|
edgeCount: triangles.count * 3 / 2,
|
|
boundingBoxMin: .init(repeating: -radius),
|
|
boundingBoxMax: .init(repeating: radius)
|
|
)
|
|
}
|
|
|
|
// MARK: - Camera wireframe icon
|
|
|
|
static func cameraWireframe() -> PrimitiveMeshData {
|
|
let wc = SIMD4<Float>(0.4, 0.4, 0.4, 1.0)
|
|
// Frustum shape
|
|
let body: [SIMD3<Float>] = [
|
|
.init(-0.5, -0.3, 0.5), .init(0.5, -0.3, 0.5),
|
|
.init(0.5, 0.3, 0.5), .init(-0.5, 0.3, 0.5),
|
|
.init(-0.3, -0.2, -0.5), .init(0.3, -0.2, -0.5),
|
|
.init(0.3, 0.2, -0.5), .init(-0.3, 0.2, -0.5),
|
|
]
|
|
let edges: [(Int,Int)] = [
|
|
(0,1),(1,2),(2,3),(3,0),
|
|
(4,5),(5,6),(6,7),(7,4),
|
|
(0,4),(1,5),(2,6),(3,7),
|
|
]
|
|
var wireVerts: [MeshVertex] = []
|
|
for (a,b) in edges {
|
|
wireVerts.append(.init(position: body[a], normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: body[b], normal: .zero, color: wc))
|
|
}
|
|
// Up arrow
|
|
wireVerts.append(.init(position: .init(-0.3, 0.3, 0.5), normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: .init(0, 0.55, 0.5), normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: .init(0, 0.55, 0.5), normal: .zero, color: wc))
|
|
wireVerts.append(.init(position: .init(0.3, 0.3, 0.5), normal: .zero, color: wc))
|
|
|
|
return PrimitiveMeshData(
|
|
vertices: [], wireVertices: wireVerts,
|
|
faceCount: 0, edgeCount: edges.count + 2,
|
|
boundingBoxMin: .init(-0.5, -0.3, -0.5),
|
|
boundingBoxMax: .init(0.5, 0.55, 0.5)
|
|
)
|
|
}
|
|
|
|
// MARK: - Light point icon
|
|
|
|
static func lightIcon() -> PrimitiveMeshData {
|
|
let lc = SIMD4<Float>(1.0, 0.85, 0.4, 1.0)
|
|
var wireVerts: [MeshVertex] = []
|
|
let r: Float = 0.3
|
|
let segments = 16
|
|
|
|
// Circle on XY
|
|
for i in 0..<segments {
|
|
let a0 = 2.0 * Float.pi * Float(i) / Float(segments)
|
|
let a1 = 2.0 * Float.pi * Float(i + 1) / Float(segments)
|
|
wireVerts.append(.init(position: .init(r * cosf(a0), r * sinf(a0), 0), normal: .zero, color: lc))
|
|
wireVerts.append(.init(position: .init(r * cosf(a1), r * sinf(a1), 0), normal: .zero, color: lc))
|
|
}
|
|
// Circle on XZ
|
|
for i in 0..<segments {
|
|
let a0 = 2.0 * Float.pi * Float(i) / Float(segments)
|
|
let a1 = 2.0 * Float.pi * Float(i + 1) / Float(segments)
|
|
wireVerts.append(.init(position: .init(r * cosf(a0), 0, r * sinf(a0)), normal: .zero, color: lc))
|
|
wireVerts.append(.init(position: .init(r * cosf(a1), 0, r * sinf(a1)), normal: .zero, color: lc))
|
|
}
|
|
// Rays
|
|
let rayLen: Float = 0.15
|
|
for i in 0..<8 {
|
|
let a = 2.0 * Float.pi * Float(i) / 8.0
|
|
let inner = SIMD3<Float>(r * cosf(a), r * sinf(a), 0)
|
|
let outer = SIMD3<Float>((r + rayLen) * cosf(a), (r + rayLen) * sinf(a), 0)
|
|
wireVerts.append(.init(position: inner, normal: .zero, color: lc))
|
|
wireVerts.append(.init(position: outer, normal: .zero, color: lc))
|
|
}
|
|
|
|
return PrimitiveMeshData(
|
|
vertices: [], wireVertices: wireVerts,
|
|
faceCount: 0, edgeCount: segments * 2 + 8,
|
|
boundingBoxMin: .init(repeating: -(r + rayLen)),
|
|
boundingBoxMax: .init(repeating: r + rayLen)
|
|
)
|
|
}
|
|
}
|