element-ios/RiotSwiftUI/Modules/Common/EffectsScene/EffectsScene.swift

70 lines
2.7 KiB
Swift

//
// Copyright 2022-2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only
// Please see LICENSE in the repository root for full details.
//
import SceneKit
import SwiftUI
class EffectsScene: SCNScene {
// MARK: - Constants
private enum Constants {
static let confettiSceneName = "ConfettiScene.scn"
static let particlesNodeName = "particles"
}
// MARK: - Public
static func confetti(with theme: ThemeSwiftUI) -> EffectsScene? {
guard let scene = EffectsScene(named: Constants.confettiSceneName) else { return nil }
let colors: [[Float]] = theme.colors.namesAndAvatars.compactMap(\.floatComponents)
if let particles = scene.rootNode.childNode(withName: Constants.particlesNodeName, recursively: false)?.particleSystems?.first {
// The particles need a non-zero color variation for the handler to affect the color
particles.particleColorVariation = SCNVector4(x: 0, y: 0, z: 0, w: 0.1)
// Add a handler to customize the color of the particles.
particles.handle(.birth, forProperties: [.color]) { data, dataStride, _, count in
for index in 0..<count {
// Pick a random color to apply to the particle.
guard let color = colors.randomElement() else { continue }
// Get the particle's color pointer.
let colorPointer = data[0] + dataStride[0] * index
let rgbaPointer = colorPointer.bindMemory(to: Float.self, capacity: dataStride[0])
// Update the color for the particle.
rgbaPointer[0] = color[0]
rgbaPointer[1] = color[1]
rgbaPointer[2] = color[2]
rgbaPointer[3] = 1
}
}
}
return scene
}
}
private extension Color {
/// The color's components as an array of floats in the extended linear sRGB colorspace.
///
/// SceneKit works in a colorspace with a linear gamma, which is why this conversion is necessary.
var floatComponents: [Float]? {
// Get the CGColor from a UIColor as it is nil on Color when loaded from an asset catalog.
let cgColor = UIColor(self).cgColor
guard
let colorSpace = CGColorSpace(name: CGColorSpace.extendedLinearSRGB),
let linearColor = cgColor.converted(to: colorSpace, intent: .defaultIntent, options: nil),
let components = linearColor.components
else { return nil }
return components.map { Float($0) }
}
}