Last active
July 7, 2024 20:07
-
-
Save Matt54/535dbf2f6c0149ed24204b9cbd65000e to your computer and use it in GitHub Desktop.
Sun with rays entity created from many low poly spheres at varying opacity and radius + different rotation animation along each axis
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import RealityKit | |
import SwiftUI | |
struct GlowingLowPolySphere: View { | |
@State private var opacity: Float = 1.0 | |
@State private var isForward: Bool = false | |
@State private var rotationAngles: SIMD3<Float> = [0, 0, 0] | |
@State private var modulationTimer: Timer? | |
@State private var time: Double = 0.0 | |
@State private var lastRotationUpdateTime = CACurrentMediaTime() | |
@State private var rootEntity: Entity? | |
var body: some View { | |
GeometryReader3D { proxy in | |
RealityView { content in | |
let size = content.convert(proxy.frame(in: .local), from: .local, to: .scene).extents | |
let radius = Float(0.5 * size.x) | |
let entity = glowingSphereEntity(radius: radius) | |
content.add(entity) | |
entity.components.set(OpacityComponent(opacity: opacity)) | |
rootEntity = entity | |
} update: { content in | |
if let entity = content.entities.first { | |
entity.components.set(OpacityComponent(opacity: opacity)) | |
} | |
} | |
} | |
.onAppear { | |
startOpacityTimer() | |
} | |
} | |
private func startOpacityTimer() { | |
Timer.scheduledTimer(withTimeInterval: 1/120.0, repeats: true) { _ in | |
if isForward { | |
opacity += 0.01 | |
if opacity >= 1.0 { | |
isForward = false | |
} | |
} else { | |
opacity -= 0.01 | |
if opacity <= 0.0 { | |
isForward = true | |
} | |
} | |
let currentTime = CACurrentMediaTime() | |
let frameDuration = currentTime - lastRotationUpdateTime | |
self.time += frameDuration | |
// Rotate along all axis at different rates for a wonky rotation effect | |
rotationAngles.x += Float(frameDuration * 1.5) | |
rotationAngles.y += Float(frameDuration * 0.7) | |
rotationAngles.z += Float(frameDuration * 0.45) | |
let rotationX = simd_quatf(angle: rotationAngles.x, axis: [1, 0, 0]) | |
let rotationY = simd_quatf(angle: rotationAngles.y, axis: [0, 1, 0]) | |
let rotationZ = simd_quatf(angle: rotationAngles.z, axis: [0, 0, 1]) | |
rootEntity?.transform.rotation = rotationX * rotationY * rotationZ | |
lastRotationUpdateTime = currentTime | |
} | |
} | |
func glowingSphereEntity(radius: Float) -> Entity { | |
let sphereEntity = Entity() | |
let numSpheres = 50 | |
for i in 0..<numSpheres { | |
let fraction = Float(i) / Float(numSpheres) | |
let sphereRadius = radius * (1.0 - fraction * 1.0) | |
let opacity = pow(fraction, 3) // Quadratic exaggerates effect | |
let sphere = Entity() | |
sphere.components.set(getModelComponent(radius: sphereRadius, opacity: opacity)) | |
sphereEntity.addChild(sphere) | |
} | |
sphereEntity.scale *= scalePreviewFactor | |
return sphereEntity | |
} | |
func getModelComponent(radius: Float, opacity: Float) -> ModelComponent { | |
var material = UnlitMaterial() | |
material.color.tint = .yellow | |
material.blending = .transparent(opacity: .init(floatLiteral: opacity)) | |
material.faceCulling = .back | |
let sphereMesh = try! MeshResource.generateSpecificSphere(radius: radius, latitudeBands: 8, longitudeBands: 8) | |
return ModelComponent(mesh: sphereMesh, materials: [material]) | |
} | |
} | |
#Preview { | |
GlowingLowPolySphere() | |
} | |
var isPreview: Bool { | |
return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" | |
} | |
var scalePreviewFactor: Float = isPreview ? 0.3 : 1.0 | |
struct MyVertex { | |
var position: SIMD3<Float> = .zero | |
var color: UInt32 = .zero | |
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 | |
} | |
} | |
extension MeshResource { | |
static func generateSpecificSphere(radius: Float, latitudeBands: Int = 10, longitudeBands: Int = 10) throws -> MeshResource { | |
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) | |
let x = cosPhi * sinTheta | |
let y = cosTheta | |
let z = sinPhi * sinTheta | |
let position = SIMD3<Float>(x, y, z) * radius | |
let color = 0xFFFFFFFF // White color for simplicity | |
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 { | |
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 | |
) | |
]) | |
// Print the number of triangles | |
let triangleCount = indexCount / 3 | |
print("Number of triangles: \(triangleCount)") | |
return try MeshResource(from: mesh) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment