Skip to content

Instantly share code, notes, and snippets.

@Matt54
Last active August 9, 2024 00:26
Show Gist options
  • Save Matt54/e56254b73b61356b0cac869a1b7f2a9b to your computer and use it in GitHub Desktop.
Save Matt54/e56254b73b61356b0cac869a1b7f2a9b to your computer and use it in GitHub Desktop.
RealityView LowLevelMesh Cylinder with a wiggling animation
import SwiftUI
import RealityKit
struct CylinderWiggleMeshView: View {
let radialSegments = 16
let heightSegments = 5
let animationStepAmount: Float = 0.05
let radius: Float = 0.005
let height: Float = 0.15
let displacementRange: Float = 0.005
@State var mesh: LowLevelMesh?
@State var currentPositions: [SIMD3<Float>] = []
@State var targetPositions: [SIMD3<Float>] = []
@State var animationProgress: Float = 0
@State var timer: Timer?
var body: some View {
RealityView { content in
let mesh = try! getMesh()
let meshResource = try! MeshResource(from: mesh)
var material = PhysicallyBasedMaterial()
material.faceCulling = .front
material.baseColor.tint = .init(red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0)
material.metallic = 1.0
material.roughness = 0.0
let entity = ModelEntity(mesh: meshResource, materials: [material])
content.add(entity)
currentPositions = generatePositions()
targetPositions = generatePositions()
updateMesh(mesh)
self.mesh = mesh
}
.onAppear { startTimer() }
.onDisappear { stopTimer() }
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1/120.0, repeats: true) { _ in
animationProgress += animationStepAmount
if animationProgress >= 1 {
currentPositions = targetPositions
targetPositions = generatePositions()
animationProgress = 0
}
if let mesh = mesh {
updateMesh(mesh)
}
}
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
func getMesh() throws -> LowLevelMesh {
let vertexCount = (radialSegments + 1) * (heightSegments + 1)
let indexCount = radialSegments * heightSegments * 6
var desc = VertexData.descriptor
desc.vertexCapacity = vertexCount
desc.indexCapacity = indexCount
return try LowLevelMesh(descriptor: desc)
}
func updateMesh(_ mesh: LowLevelMesh) {
mesh.withUnsafeMutableBytes(bufferIndex: 0) { rawBytes in
let vertices = rawBytes.bindMemory(to: VertexData.self)
for y in 0...heightSegments {
let currentPosition = currentPositions[y]
let targetPosition = targetPositions[y]
let interpolatedPosition = mix(currentPosition, targetPosition, t: animationProgress)
for x in 0...radialSegments {
let angle = Float(x) / Float(radialSegments) * 2 * Float.pi
let xOffset = cos(angle) * radius
let zOffset = sin(angle) * radius
let position = SIMD3<Float>(interpolatedPosition.x + xOffset, interpolatedPosition.y, interpolatedPosition.z + zOffset)
let normal = normalize(SIMD3<Float>(-xOffset, 0, -zOffset))
let index = y * (radialSegments + 1) + x
vertices[index] = VertexData(position: position, normal: normal)
}
}
}
mesh.withUnsafeMutableIndices { rawIndices in
let indices = rawIndices.bindMemory(to: UInt32.self)
var index = 0
for y in 0..<heightSegments {
for x in 0..<radialSegments {
let a = y * (radialSegments + 1) + x
let b = a + 1
let c = a + (radialSegments + 1)
let d = c + 1
indices[index] = UInt32(a)
indices[index + 1] = UInt32(b)
indices[index + 2] = UInt32(d)
indices[index + 3] = UInt32(a)
indices[index + 4] = UInt32(d)
indices[index + 5] = UInt32(c)
index += 6
}
}
}
let meshBounds = BoundingBox(min: [-radius*2, -height/2, -radius*2], max: [radius*2, height/2, radius*2])
mesh.parts.replaceAll([
LowLevelMesh.Part(
indexCount: radialSegments * heightSegments * 6,
topology: .triangle,
bounds: meshBounds
)
])
}
func generatePositions() -> [SIMD3<Float>] {
var positions: [SIMD3<Float>] = []
let startPosition = SIMD3<Float>(0, -height/2, 0)
let endPosition = SIMD3<Float>(0, height/2, 0)
for y in 0...heightSegments {
let progress = Float(y) / Float(heightSegments)
var centerPosition = mix(startPosition, endPosition, t: progress)
if y != 0 && y != heightSegments {
centerPosition.x += Float.random(in: -displacementRange...displacementRange)
centerPosition.z += Float.random(in: -displacementRange...displacementRange)
}
positions.append(centerPosition)
}
return positions
}
struct VertexData {
var position: SIMD3<Float> = .zero
var normal: SIMD3<Float> = .zero
static var vertexAttributes: [LowLevelMesh.Attribute] = [
.init(semantic: .position, format: .float3, offset: MemoryLayout<Self>.offset(of: \.position)!),
.init(semantic: .normal, format: .float3, offset: MemoryLayout<Self>.offset(of: \.normal)!),
]
static var vertexLayouts: [LowLevelMesh.Layout] = [
.init(bufferIndex: 0, bufferStride: MemoryLayout<Self>.stride)
]
static var descriptor: LowLevelMesh.Descriptor {
var desc = LowLevelMesh.Descriptor()
desc.vertexAttributes = VertexData.vertexAttributes
desc.vertexLayouts = VertexData.vertexLayouts
desc.indexType = .uint32
return desc
}
}
}
#Preview {
CylinderWiggleMeshView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment