-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDioramaViewModel.swift
148 lines (115 loc) · 5.42 KB
/
DioramaViewModel.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
See the LICENSE.txt file for this sample’s licensing information.
Abstract:
The app's data model.
*/
import Foundation
import RealityKit
import RealityKitContent
import Observation
@Observable
final class ViewModel {
let materialParameterName = "Progress"
var rootEntity: Entity? = nil
var showImmersiveContent: Bool = false
var sliderValue: Float = 0.0
var contentScaleSliderValue: Float = 0.5
init(rootEntity: Entity? = nil, showImmersiveContent: Bool = false) {
self.rootEntity = rootEntity
self.showImmersiveContent = showImmersiveContent
updateRegionSpecificOpacity()
}
static let query = EntityQuery(where: .has(RegionSpecificComponent.self))
static let audioQuery = EntityQuery(where: .has(RegionSpecificComponent.self) && .has(AmbientAudioComponent.self))
func updateScale() {
let newScale = Float.lerp(a: 0.2, b: 1.0, t: contentScaleSliderValue)
rootEntity?.setScale(SIMD3<Float>(repeating: newScale), relativeTo: nil)
}
func updateRegionSpecificOpacity() {
// When the slider is at 0, the diorama shows Yosemite and displays
// Yosemite-specific item. When it's at 1, it shows Catalina Island,
// and displays the Catalina Island items.
rootEntity?.scene?.performQuery(Self.query).forEach({ regionSpecificEntity in
guard let component = regionSpecificEntity.components[RegionSpecificComponent.self] else { return }
let opacity: Float = component.region.opacity(forSliderValue: sliderValue)
// An entity might have an opacity component that controls whether it is shown
// at all. If it does, that takes precedence over the opacity based on the
// current slider value.
if let controlledOpacityComponent = regionSpecificEntity.components[ControlledOpacityComponent.self] {
let newOpacity = controlledOpacityComponent.opacity(forSliderValue: opacity)
regionSpecificEntity.components.set(OpacityComponent(opacity: newOpacity))
} else {
regionSpecificEntity.components.set(OpacityComponent(opacity: opacity))
}
})
// Adjust the gain on the ambient audio components.
rootEntity?.scene?.performQuery(Self.audioQuery).forEach({ audioEmitter in
guard var audioComponent = audioEmitter.components[AmbientAudioComponent.self],
let regionComponent = audioEmitter.components[RegionSpecificComponent.self] else { return }
let gain = regionComponent.region.gain(forSliderValue: sliderValue)
audioComponent.gain = gain
audioEmitter.components.set(audioComponent)
})
}
private var audioControllers = [Region: AudioPlaybackController]()
private var hasSetUpAudio: Bool {
Region.allCases.allSatisfy { region in
audioControllers[region] != nil
}
}
@MainActor
func setupAudio() {
Task {
guard !hasSetUpAudio, let scene = rootEntity?.scene else { return }
// Find audio emitters and prepare them to play audio.
scene.performQuery(Self.audioQuery).forEach({ audioEmitter in
guard let regionComponent = audioEmitter.components[RegionSpecificComponent.self] else { return }
let primPath: String
switch regionComponent.region {
case .catalina:
primPath = "/Root/Ocean_Sounds_wav"
case .yosemite:
primPath = "/Root/Forest_Sounds_wav"
}
guard let resource = try? AudioFileResource.load(named: primPath,
from: "DioramaAssembled.usda",
in: RealityKitContent.RealityKitContentBundle) else { return }
let audioPlaybackController = audioEmitter.prepareAudio(resource)
audioPlaybackController.play()
audioControllers[regionComponent.region] = audioPlaybackController
})
}
}
var terrainMaterialValue: Float? {
guard case .float(let value) = terrainMaterial?.getParameter(name: materialParameterName) else { return nil }
return value
}
private var terrainMaterial: ShaderGraphMaterial? {
rootEntity?.terrain?.shaderGraphMaterial
}
func resetAudio() {
audioControllers = [Region: AudioPlaybackController]()
}
func updateTerrainMaterial() {
guard let terrain = rootEntity?.terrain,
let terrainMaterial = terrainMaterial else { return }
do {
var material = terrainMaterial
try material.setParameter(name: materialParameterName, value: .float(sliderValue))
if var component = terrain.modelComponent {
component.materials = [material]
terrain.components.set(component)
}
try terrain.update(shaderGraphMaterial: terrainMaterial, { material in
try material.setParameter(name: materialParameterName, value: .float(sliderValue))
})
} catch {
print("problem: \(error)")
}
}
}
fileprivate extension Entity {
var terrain: Entity? {
findEntity(named: "FlatTerrain")
}
}