Skip to content

Instantly share code, notes, and snippets.

@Matt54
Created August 4, 2024 14:48
Show Gist options
  • Save Matt54/b593dd62ff161e5ecb17cfe5bc1c49af to your computer and use it in GitHub Desktop.
Save Matt54/b593dd62ff161e5ecb17cfe5bc1c49af to your computer and use it in GitHub Desktop.
RealityView comparing LowLevelMesh spheres with and without surface normal data
import SwiftUI
import RealityKit
struct LowLevelMeshNormalsComparisonView: View {
var body: some View {
RealityView { content in
let sphereWithoutNormals = try! sphereEntity(includeNormals: false)
sphereWithoutNormals.position.x = -0.11
let sphereWithNormals = try! sphereEntity(includeNormals: true)
sphereWithNormals.position.x = 0.11
content.add(sphereWithNormals)
content.add(sphereWithoutNormals)
}
}
func sphereEntity(includeNormals: Bool = true) throws -> Entity {
var mesh: LowLevelMesh
if includeNormals {
mesh = try getMeshWithNormals(radius: 0.1)
} else {
mesh = try getMeshWithoutNormals(radius: 0.1)
}
let resource = try MeshResource(from: mesh)
var material = PhysicallyBasedMaterial()
material.baseColor.tint = .init(red: 1.0, green: 0.0, blue: 1.0, alpha: 1.0)
material.faceCulling = .none
material.metallic = 1.0
material.roughness = 0.0
let modelComponent = ModelComponent(mesh: resource, materials: [material])
let entity = Entity()
entity.components.set(modelComponent)
return entity
}
func getMeshWithNormals(radius: Float,
latitudeBands: Int = 40,
longitudeBands: Int = 20) throws -> LowLevelMesh {
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
}
}
let vertexCount = (latitudeBands + 1) * (longitudeBands + 1)
let indexCount = latitudeBands * longitudeBands * 6
var desc = MyVertexWithNormal.descriptor
desc.vertexCapacity = vertexCount
desc.indexCapacity = indexCount
let mesh = try LowLevelMesh(descriptor: desc)
mesh.withUnsafeMutableBytes(bufferIndex: 0) { rawBytes in
let vertices = rawBytes.bindMemory(to: MyVertexWithNormal.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)
let x = cosPhi * sinTheta
let y = cosTheta
let z = sinPhi * sinTheta
let position = SIMD3<Float>(x, y, z) * radius
let normal = -SIMD3<Float>(x, y, z).normalized()
vertices[vertexIndex] = MyVertexWithNormal(position: position, normal: normal)
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 {
let first = (latNumber * (longitudeBands + 1)) + longNumber
let second = first + longitudeBands + 1
indices[index] = UInt32(first)
indices[index + 1] = UInt32(second)
indices[index + 2] = UInt32(first + 1)
indices[index + 3] = UInt32(second)
indices[index + 4] = UInt32(second + 1)
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 getMeshWithoutNormals(radius: Float,
latitudeBands: Int = 40,
longitudeBands: Int = 20) throws -> LowLevelMesh {
struct MyVertexWithoutNormal {
var position: SIMD3<Float> = .zero
static var vertexAttributes: [LowLevelMesh.Attribute] = [
.init(semantic: .position, format: .float3, offset: MemoryLayout<Self>.offset(of: \.position)!),
]
static var vertexLayouts: [LowLevelMesh.Layout] = [
.init(bufferIndex: 0, bufferStride: MemoryLayout<Self>.stride)
]
static var descriptor: LowLevelMesh.Descriptor {
var desc = LowLevelMesh.Descriptor()
desc.vertexAttributes = MyVertexWithoutNormal.vertexAttributes
desc.vertexLayouts = MyVertexWithoutNormal.vertexLayouts
desc.indexType = .uint32
return desc
}
}
let vertexCount = (latitudeBands + 1) * (longitudeBands + 1)
let indexCount = latitudeBands * longitudeBands * 6
var desc = MyVertexWithoutNormal.descriptor
desc.vertexCapacity = vertexCount
desc.indexCapacity = indexCount
let mesh = try LowLevelMesh(descriptor: desc)
mesh.withUnsafeMutableBytes(bufferIndex: 0) { rawBytes in
let vertices = rawBytes.bindMemory(to: MyVertexWithoutNormal.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)
let x = cosPhi * sinTheta
let y = cosTheta
let z = sinPhi * sinTheta
let position = SIMD3<Float>(x, y, z) * radius
vertices[vertexIndex] = MyVertexWithoutNormal(position: position)
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 {
let first = (latNumber * (longitudeBands + 1)) + longNumber
let second = first + longitudeBands + 1
indices[index] = UInt32(first)
indices[index + 1] = UInt32(second)
indices[index + 2] = UInt32(first + 1)
indices[index + 3] = UInt32(second)
indices[index + 4] = UInt32(second + 1)
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
}
}
#Preview {
LowLevelMeshNormalsComparisonView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment