Skip to content

Instantly share code, notes, and snippets.

@Matt54
Created July 29, 2024 04:05
Show Gist options
  • Save Matt54/fa80d45b8bf7a422a5a935a4d3c4b566 to your computer and use it in GitHub Desktop.
Save Matt54/fa80d45b8bf7a422a5a935a4d3c4b566 to your computer and use it in GitHub Desktop.
RealityView with a plane (modulated by a sine wave) and a point light
import SwiftUI
import RealityKit
struct WavyPlaneView: View {
@State private var phase: Float = 0.0
@State private var mesh: LowLevelMesh?
@State private var timer: Timer?
let resolution = 60
var body: some View {
RealityView { content in
let planeEntity = try! getPlaneEntity()
let lightEntity = try! getLightEntity()
planeEntity.addChild(lightEntity)
content.add(planeEntity)
}
.onAppear { startTimer() }
.onDisappear { stopTimer() }
}
private func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1/120.0, repeats: true) { _ in
phase += 0.1
updateMesh(phase: phase)
}
}
private func stopTimer() {
timer?.invalidate()
timer = nil
}
func getLightEntity() throws -> Entity {
let entity = Entity()
let pointLightComponent = PointLightComponent( cgColor: .init(red: 1, green: 1, blue: 1, alpha: 1), intensity: 5000, attenuationRadius: 0.25 )
entity.components.set(pointLightComponent)
entity.position = .init(x: 0, y: 0, z: 0.125)
return entity
}
func getPlaneEntity() throws -> Entity {
let mesh = try createPlaneMesh()
let resource = try! MeshResource(from: mesh)
var material = PhysicallyBasedMaterial()
material.baseColor.tint = .init(red: 0.25, green: 0.5, blue: 1.0, alpha: 1.0)
material.faceCulling = .none
material.metallic = 0.0
material.roughness = 1.0
let modelComponent = ModelComponent(mesh: resource, materials: [material])
let entity = Entity()
entity.components.set(modelComponent)
return entity
}
func createPlaneMesh() throws -> LowLevelMesh {
let vertexCount = resolution * resolution
let indexCount = (resolution - 1) * (resolution - 1) * 6
var desc = MyVertexWithNormal.descriptor
desc.vertexCapacity = vertexCount
desc.indexCapacity = indexCount
let mesh = try LowLevelMesh(descriptor: desc)
self.mesh = mesh
return mesh
}
func updateMesh(amplitude: Float = 0.009, frequency: Float = 60.0, phase: Float = 0.0) {
guard let mesh = mesh else { return }
let size: Float = 0.4
let indexCount = (resolution - 1) * (resolution - 1) * 6
mesh.withUnsafeMutableBytes(bufferIndex: 0) { rawBytes in
let vertices = rawBytes.bindMemory(to: MyVertexWithNormal.self)
for y in 0..<resolution {
for x in 0..<resolution {
let index = y * resolution + x
let xPos = Float(x) / Float(resolution - 1) * size - size / 2
let yPos = Float(y) / Float(resolution - 1) * size - size / 2
// Calculate z using a sine wave
let z = amplitude * sin(frequency * (xPos + yPos) + phase)
let position = SIMD3<Float>(xPos, yPos, z)
// Calculate normal for the wavy surface
let dx = amplitude * frequency * cos(frequency * (xPos + yPos) + phase)
let dy = amplitude * frequency * cos(frequency * (xPos + yPos) + phase)
let normal = simd_normalize(SIMD3<Float>(-dx, -dy, -1))
vertices[index] = MyVertexWithNormal(position: position, normal: normal)
}
}
}
mesh.withUnsafeMutableIndices { rawIndices in
let indices = rawIndices.bindMemory(to: UInt32.self)
var index = 0
for y in 0..<(resolution - 1) {
for x in 0..<(resolution - 1) {
let topLeft = UInt32(y * resolution + x)
let topRight = topLeft + 1
let bottomLeft = UInt32((y + 1) * resolution + x)
let bottomRight = bottomLeft + 1
indices[index] = topLeft
indices[index + 1] = bottomLeft
indices[index + 2] = topRight
indices[index + 3] = topRight
indices[index + 4] = bottomLeft
indices[index + 5] = bottomRight
index += 6
}
}
}
let maxZ = amplitude
mesh.parts.replaceAll([
LowLevelMesh.Part(
indexCount: indexCount,
topology: .triangle,
bounds: BoundingBox(min: [-size/2, -size/2, -maxZ], max: [size/2, size/2, maxZ])
)
])
}
}
struct MyVertexWithNormal {
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 = MyVertexWithNormal.vertexAttributes
desc.vertexLayouts = MyVertexWithNormal.vertexLayouts
desc.indexType = .uint32
return desc
}
}
#Preview {
WavyPlaneView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment