// // Entity+Opacity.swift // // Created by Drew Olbrich on 10/25/23. // Copyright © 2023 Lunar Skydiving LLC. All rights reserved. // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // import Foundation import RealityKit import Combine private var playbackCompletedSubscriptions: Set<AnyCancellable> = .init() extension Entity { /// The opacity value applied to the entity and its descendants. /// /// `OpacityComponent` is assigned to the entity if it doesn't already exist. var opacity: Float { get { return components[OpacityComponent.self]?.opacity ?? 1 } set { if !components.has(OpacityComponent.self) { components[OpacityComponent.self] = OpacityComponent(opacity: newValue) } else { components[OpacityComponent.self]?.opacity = newValue } } } /// Sets the opacity value applied to the entity and its descendants with optional animation. /// /// `OpacityComponent` is assigned to the entity if it doesn't already exist. func setOpacity(_ opacity: Float, animated: Bool, duration: TimeInterval = 0.2, delay: TimeInterval = 0, completion: (() -> Void)? = nil) { guard animated else { self.opacity = opacity return } if !components.has(OpacityComponent.self) { components[OpacityComponent.self] = OpacityComponent(opacity: 1) } let animation = FromToByAnimation(name: "Entity/setOpacity", to: opacity, duration: duration, timing: .linear, isAdditive: false, bindTarget: .opacity, delay: delay) do { let animationResource: AnimationResource = try .generate(with: animation) let animationPlaybackController = playAnimation(animationResource) if completion != nil { scene?.publisher(for: AnimationEvents.PlaybackCompleted.self) .filter { $0.playbackController == animationPlaybackController } .sink(receiveValue: { event in completion?() }).store(in: &playbackCompletedSubscriptions) } } catch { assertionFailure("Could not generate animation: \(error.localizedDescription)") } } }