Skip to content

Instantly share code, notes, and snippets.

@Matt54
Created July 22, 2024 04:00
Show Gist options
  • Save Matt54/f92b7af21ef090d3e71c3fad446bbcb3 to your computer and use it in GitHub Desktop.
Save Matt54/f92b7af21ef090d3e71c3fad446bbcb3 to your computer and use it in GitHub Desktop.
RealityView playing an animation sequence which blends spotlights inside of an extruded box
import RealityKit
import SwiftUI
struct SpotLightsColorBlendBoxView: View {
var body: some View {
GeometryReader3D { proxy in
RealityView { content in
let size = content.convert(proxy.frame(in: .local), from: .local, to: .scene)
let entity = try! getRootEntity(boundingBox: size)
content.add(entity)
}
}
}
func getRootEntity(boundingBox: BoundingBox) throws -> Entity {
let boxEntity = try getBoxEntity(boundingBox: boundingBox)
let coneEntities = getConeEntities(boundingBox: boundingBox)
coneEntities.forEach( { boxEntity.addChild($0) })
boxEntity.scale *= scalePreviewFactor
return boxEntity
}
func getBoxEntity(boundingBox: BoundingBox) throws -> Entity {
let boxEntity = Entity()
let minDimension = CGFloat.maximum(CGFloat(boundingBox.minX), CGFloat(boundingBox.minY))
let maxDimension = CGFloat.minimum(CGFloat(boundingBox.maxX), CGFloat(boundingBox.maxY))
let widthIncreaseFactor: CGFloat = 1.0
let graphic = SwiftUI.Path { path in
path.move(to: CGPoint(x: minDimension*widthIncreaseFactor, y: minDimension))
path.addLine(to: CGPoint(x: minDimension*widthIncreaseFactor, y: maxDimension))
path.addLine(to: CGPoint(x: maxDimension*widthIncreaseFactor, y: maxDimension))
path.addLine(to: CGPoint(x: maxDimension*widthIncreaseFactor, y: minDimension))
path.addLine(to: CGPoint(x: minDimension*widthIncreaseFactor, y: minDimension))
path.closeSubpath()
}
var extrusionOptions = MeshResource.ShapeExtrusionOptions()
extrusionOptions.extrusionMethod = .linear(depth: boundingBox.boundingRadius)
extrusionOptions.materialAssignment = .init(front: 0, back: 1, extrusion: 1, frontChamfer: 1, backChamfer: 1)
extrusionOptions.chamferRadius = boundingBox.boundingRadius * 0.1
let meshResource = try MeshResource(extruding: graphic, extrusionOptions: extrusionOptions)
let modelComponent = ModelComponent(mesh: meshResource, materials: getMaterialArray())
boxEntity.components.set(modelComponent)
return boxEntity
}
func getConeEntities(boundingBox: BoundingBox) -> [Entity] {
let coneOffsetDimension = Float.maximum(boundingBox.minX, boundingBox.minY) * 0.5
let colors: [UIColor] = [.green, .blue, .yellow, .red]
let positions: [SIMD3<Float>] = [
SIMD3<Float>(coneOffsetDimension, coneOffsetDimension, -coneOffsetDimension*2.0),
SIMD3<Float>(coneOffsetDimension, -coneOffsetDimension, -coneOffsetDimension*2.0),
SIMD3<Float>(-coneOffsetDimension, coneOffsetDimension, -coneOffsetDimension*2.0),
SIMD3<Float>(-coneOffsetDimension, -coneOffsetDimension, -coneOffsetDimension*2.0)
]
var entities: [Entity] = []
for (index, color) in colors.enumerated() {
let coneAndLightEntity = createConeAndLightEntity(color: color)
coneAndLightEntity.position = positions[index]
entities.append(coneAndLightEntity)
}
let centerWallPoint = SIMD3<Float>(0, 0, -boundingBox.extents.z + boundingBox.boundingRadius * 0.1)
let wallPositions: [SIMD3<Float>] = [
SIMD3<Float>(coneOffsetDimension, coneOffsetDimension, coneOffsetDimension*2.0),
SIMD3<Float>(coneOffsetDimension, -coneOffsetDimension, coneOffsetDimension*2.0),
SIMD3<Float>(-coneOffsetDimension, coneOffsetDimension, coneOffsetDimension*2.0),
SIMD3<Float>(-coneOffsetDimension, -coneOffsetDimension, coneOffsetDimension*2.0)
]
for modelEntity in entities {
animateEntity(modelEntity, centerWallPoint: centerWallPoint, wallPositions: wallPositions)
}
return entities
}
func createConeAndLightEntity(color: UIColor) -> Entity {
let coneAndLightEntity = Entity()
let coneEntity = Entity()
let coneMeshResource = MeshResource.generateCone(height: 0.1, radius: 0.05)
coneEntity.transform.rotation = simd_quatf(angle: .pi * 0.5, axis: [1, 0, 0])
let coneModelComponent = ModelComponent(mesh: coneMeshResource, materials: [SimpleMaterial(color: color, isMetallic: true)])
coneEntity.components.set(coneModelComponent)
let spotlightComponent = SpotLightComponent(color: color, intensity: 5000, innerAngleInDegrees: 5, outerAngleInDegrees: 28, attenuationRadius: 0.5, attenuationFalloffExponent: 2.0)
coneAndLightEntity.components.set(spotlightComponent)
coneAndLightEntity.addChild(coneEntity)
return coneAndLightEntity
}
func animateEntity(_ entity: Entity,
centerWallPoint: SIMD3<Float>,
wallPositions: [SIMD3<Float>]) {
var animationResources: [AnimationResource] = []
// Initial rotation to face backWallPoint
let centerWallDirection = normalize(centerWallPoint - entity.position)
let initialRotation = simd_quatf(from: [0, 0, -1], to: centerWallDirection)
var initialTransform = entity.transform
initialTransform.rotation = initialRotation
animationResources.append(createAnimationResource(from: entity.transform, to: initialTransform, delay: 0.25, speed: 0.75))
// Hold facing the backWallPoint
animationResources.append(createAnimationResource(from: initialTransform, to: initialTransform, delay: 0.0, speed: 3.0))
// Rotations to face each wall position
var previousTransform = initialTransform
for position in wallPositions {
let nextDirection = normalize(position - entity.position)
let nextRotation = simd_quatf(from: [0, 0, -1], to: nextDirection)
var nextTransform = previousTransform
nextTransform.rotation = nextRotation
animationResources.append(createAnimationResource(from: previousTransform, to: nextTransform, delay: 0.0, speed: 1.5))
previousTransform = nextTransform
}
// Rotate back to face the backWallPoint
animationResources.append(createAnimationResource(from: previousTransform, to: initialTransform, delay: 0.0, speed: 1.5))
// Final rotation back to the initial position
animationResources.append(createAnimationResource(from: initialTransform, to: entity.transform, delay: 0.0, speed: 0.75))
let sequence = try! AnimationResource.sequence(with: animationResources).repeat()
entity.playAnimation(sequence)
}
func createAnimationResource(from: Transform, to: Transform, delay: Double, speed: Float) -> AnimationResource {
let animationDefinition = FromToByAnimation(from: from, to: to, bindTarget: .transform)
let animationViewDefinition = AnimationView(source: animationDefinition, delay: delay, speed: speed)
return try! AnimationResource.generate(with: animationViewDefinition)
}
func getMaterialArray() -> [RealityFoundation.Material] {
var transparentMaterial = UnlitMaterial()
transparentMaterial.color.tint = .init(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
transparentMaterial.blending = .transparent(opacity: 0.0)
transparentMaterial.faceCulling = .none
var reflectiveMaterial = PhysicallyBasedMaterial()
reflectiveMaterial.baseColor.tint = .init(red: 0.25, green: 0.25, blue: 0.25, alpha: 1.0)
reflectiveMaterial.metallic = 1.0
reflectiveMaterial.roughness = 0.7
reflectiveMaterial.faceCulling = .none
return [transparentMaterial, reflectiveMaterial]
}
}
#Preview {
SpotLightsColorBlendBoxView()
}
var isPreview: Bool {
return ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
}
var scalePreviewFactor: Float = isPreview ? 0.3 : 1.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment