Skip to content

Instantly share code, notes, and snippets.

@Matt54
Created June 20, 2024 23:49
Show Gist options
  • Select an option

  • Save Matt54/7086d9e061031ec45d2ee693442e355a to your computer and use it in GitHub Desktop.

Select an option

Save Matt54/7086d9e061031ec45d2ee693442e355a to your computer and use it in GitHub Desktop.
A RealityView using a LowLevelMesh to produce a morphing sphere
import RealityKit
import SwiftUI
import GameplayKit
struct MorphingSphereRealityView: View {
@State private var currentEntity: Entity?
@State private var morphFactor: Float = 0.0
@State private var frameDuration: TimeInterval = 0.0
@State private var lastUpdateTime = CACurrentMediaTime()
static let animationFrameDuration: TimeInterval = 1.0 / 120.0
private let timer = Timer.publish(every: animationFrameDuration, on: .main, in: .common).autoconnect()
var body: some View {
RealityView { content in
currentEntity = try! createSphereEntity(morphFactor: 0)
if let entity = currentEntity {
content.add(entity)
}
} update: { content in
if let modelComponent = try? getModelComponent(morphFactor: morphFactor) {
currentEntity?.components.set(modelComponent)
}
}
.onReceive(timer) { input in
let currentTime = CACurrentMediaTime()
frameDuration = currentTime - lastUpdateTime
lastUpdateTime = currentTime
let morphAmount = Float(frameDuration * 0.8)
morphFactor = morphFactor + morphAmount
}
}
func createSphereEntity(morphFactor: Float) throws -> Entity {
let modelComponent = try getModelComponent(morphFactor: morphFactor)
let entity = Entity()
entity.name = "Sphere"
entity.components.set(modelComponent)
entity.scale *= 0.1
return entity
}
func getModelComponent(morphFactor: Float) throws -> ModelComponent {
let lowLevelMesh = try sphereMesh(morphFactor: morphFactor)
let resource = try MeshResource(from: lowLevelMesh)
var material = PhysicallyBasedMaterial()
material.baseColor.tint = .init(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
material.roughness.scale = 0.0
material.metallic.scale = 1.0
material.faceCulling = .none
return ModelComponent(mesh: resource, materials: [material])
}
func sphereMesh(morphFactor: Float) throws -> LowLevelMesh {
let latitudeBands = 40
let longitudeBands = 40
let radius: Float = 0.5
let vertexCount = (latitudeBands + 1) * (longitudeBands + 1)
let indexCount = latitudeBands * longitudeBands * 6
var desc = MyVertex.descriptor
desc.vertexCapacity = vertexCount
desc.indexCapacity = indexCount
let mesh = try LowLevelMesh(descriptor: desc)
mesh.withUnsafeMutableBytes(bufferIndex: 0) { rawBytes in
let vertices = rawBytes.bindMemory(to: MyVertex.self)
var vertexIndex = 0
for latNumber in 0...latitudeBands {
let theta = Float(latNumber) * Float.pi / Float(latitudeBands)
let sinTheta = sin(theta)
let cosTheta = cos(theta)
for longNumber in 0...longitudeBands {
let phi = Float(longNumber) * 2 * Float.pi / Float(longitudeBands)
let sinPhi = sin(phi)
let cosPhi = cos(phi)
var x = cosPhi * sinTheta
var y = cosTheta
var z = sinPhi * sinTheta
let noiseScale: Float = 0.05
let noiseValueX = customNoise(x: morphFactor + theta, y: phi) * noiseScale
let noiseValueY = customNoise(x: morphFactor + phi, y: theta) * noiseScale
let noiseValueZ = customNoise(x: morphFactor + theta + phi, y: theta) * noiseScale
let noiseScale2: Float = 0.0125
let noiseValueX2 = perlinNoise(x: morphFactor + theta, y: phi) * noiseScale2
let noiseValueY2 = perlinNoise(x: morphFactor + phi, y: theta) * noiseScale2
let noiseValueZ2 = perlinNoise(x: morphFactor + theta + phi, y: theta) * noiseScale2
x += noiseValueX+noiseValueX2
y += noiseValueY+noiseValueY2
z += noiseValueZ+noiseValueZ2
let position = SIMD3<Float>(x, y, z) * radius
let color = 0xFFFFFFFF
vertices[vertexIndex] = MyVertex(position: position, color: UInt32(color))
vertexIndex += 1
}
}
}
mesh.withUnsafeMutableIndices { rawIndices in
let indices = rawIndices.bindMemory(to: UInt32.self)
var index = 0
for latNumber in 0..<latitudeBands {
for longNumber in 0..<longitudeBands {
// Each quad (rectangle) on the sphere’s surface is made up of two triangles.
/*
first first+1
* -------- *
| / |
| / |
| / |
| / |
* -------- *
second second+1
*/
// index of the first vertex of the quad.
let first = (latNumber * (longitudeBands + 1)) + longNumber
// index of the vertex directly below the first vertex.
let second = first + longitudeBands + 1
// first vertex of the first triangle.
indices[index] = UInt32(first)
// second vertex of the first triangle
indices[index + 1] = UInt32(second)
// third vertex of the first triangle
indices[index + 2] = UInt32(first + 1)
// first vertex of the second triangle
indices[index + 3] = UInt32(second)
// second vertex of the second triangle
indices[index + 4] = UInt32(second + 1)
// third vertex of the second triangle
indices[index + 5] = UInt32(first + 1)
index += 6
}
}
}
let meshBounds = BoundingBox(min: [-radius, -radius, -radius], max: [radius, radius, radius])
mesh.parts.replaceAll([
LowLevelMesh.Part(
indexCount: indexCount,
topology: .triangle,
bounds: meshBounds
)
])
return mesh
}
}
func customNoise(x: Float, y: Float) -> Float {
let value1 = (sin(x * 10.0) + cos(y * 10.0)) * 0.5
let value2 = (-sin(x * 10.0) + cos(y * 20.0)) * 0.1
let value3 = (sin(x * 20.0) - cos(y * 10.0)) * 0.05
return value1 + value2 + value3
}
func perlinNoise(x: Float, y: Float, seed: Int = 42) -> Float {
let noiseSource = GKPerlinNoiseSource(frequency: 1.0, octaveCount: 6, persistence: 0.5, lacunarity: 2.0, seed: Int32(seed))
let noise = GKNoise(noiseSource)
let noiseMap = GKNoiseMap(noise, size: vector_double2(1.0, 1.0), origin: vector_double2(Double(x), Double(y)), sampleCount: vector_int2(1, 1), seamless: false)
return Float(noiseMap.value(at: vector_int2(0, 0)))
}
struct MyVertex {
var position: SIMD3<Float> = .zero
var color: UInt32 = .zero
}
extension MyVertex {
static var vertexAttributes: [LowLevelMesh.Attribute] = [
.init(semantic: .position, format: .float3, offset: MemoryLayout<Self>.offset(of: \.position)!),
.init(semantic: .color, format: .uchar4Normalized_bgra, offset: MemoryLayout<Self>.offset(of: \.color)!)
]
static var vertexLayouts: [LowLevelMesh.Layout] = [
.init(bufferIndex: 0, bufferStride: MemoryLayout<Self>.stride)
]
static var descriptor: LowLevelMesh.Descriptor {
var desc = LowLevelMesh.Descriptor()
desc.vertexAttributes = MyVertex.vertexAttributes
desc.vertexLayouts = MyVertex.vertexLayouts
desc.indexType = .uint32
return desc
}
}
#Preview {
MorphingSphereRealityView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment