-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDioramaView.swift
More file actions
146 lines (106 loc) · 6.36 KB
/
DioramaView.swift
File metadata and controls
146 lines (106 loc) · 6.36 KB
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
/*
See the LICENSE.txt file for this sample’s licensing information.
Abstract:
A view that holds the app's immersive content.
*/
import SwiftUI
import RealityKit
import RealityKitContent
struct DioramaView: View {
@Environment(\.dismiss) private var dismiss
var viewModel: ViewModel
static let markersQuery = EntityQuery(where: .has(PointOfInterestComponent.self))
static let runtimeQuery = EntityQuery(where: .has(PointOfInterestRuntimeComponent.self))
@State private var subscriptions = [EventSubscription]()
@State private var attachmentsProvider = AttachmentsProvider()
var body: some View {
@Bindable var viewModel = viewModel
RealityView { content, _ in
do {
let entity = try await Entity(named: "DioramaAssembled", in: RealityKitContent.RealityKitContentBundle)
viewModel.rootEntity = entity
content.add(entity)
viewModel.updateScale()
// Offset the scene so it doesn't appear underneath the user or conflict with the main window.
entity.position = SIMD3<Float>(0, 0, -2)
setupBirds(rootEntity: entity)
subscriptions.append(content.subscribe(to: ComponentEvents.DidAdd.self, componentType: PointOfInterestComponent.self, { event in
createLearnMoreView(for: event.entity)
}))
subscriptions.append(content.subscribe(to: ComponentEvents.DidAdd.self, componentType: TrailComponent.self, { event in
let trail = event.entity
if let parentRegion = trail.parent?.components[PointOfInterestComponent.self] {
trail.components.set(RegionSpecificComponent(region: parentRegion.region))
}
// Trail entities need a TrailOpacityComponents so they can fade in and out.
trail.components.set(ControlledOpacityComponent(shouldShow: false))
trail.components.set(OpacityComponent(opacity: 0.0))
viewModel.updateRegionSpecificOpacity()
}))
} catch {
print("Error in RealityView's make: \(error)")
}
} update: { content, attachments in
viewModel.setupAudio()
// Add attachment entities to marked entities. First, find all entities that have the
// PointOfInterestRuntimeComponent, which means they've created an attachment.
viewModel.rootEntity?.scene?.performQuery(Self.runtimeQuery).forEach { entity in
guard let component = entity.components[PointOfInterestRuntimeComponent.self] else { return }
// Get the entity from the collection of attachments keyed by tag.
guard let attachmentEntity = attachments.entity(for: component.attachmentTag) else { return }
guard attachmentEntity.parent == nil else { return }
// Attachments are region-specific. They react when the slider changes from one map to the other.
// Take the region configured in Reality Composer Pro and give it to the corresponding attachment
// entity. These entities also need OpacityComponents so they can fade in and out as the map changes
if let pointOfInterestComponent = entity.components[PointOfInterestComponent.self] {
attachmentEntity.components.set(RegionSpecificComponent(region: pointOfInterestComponent.region))
attachmentEntity.components.set(OpacityComponent(opacity: 0))
}
// Have all the Point Of Interest attachments always face the camera by giving them a BillboardComponent.
attachmentEntity.components.set(BillboardComponent())
// SwiftUI calculates an attachment view's expanded size using the top center as the pivot point. This
// raises the views so they aren't sunk into the terrain in their initial collapsed state.
entity.addChild(attachmentEntity)
attachmentEntity.setPosition([0.0, 0.4, 0.0], relativeTo: entity)
}
viewModel.updateRegionSpecificOpacity()
viewModel.updateTerrainMaterial()
} attachments: {
ForEach(attachmentsProvider.sortedTagViewPairs, id: \.tag) { pair in
Attachment(id: pair.tag) {
pair.view
}
}
}
}
private func createLearnMoreView(for entity: Entity) {
// If this entity already has a RuntimeComponent, don't add another one.
guard entity.components[PointOfInterestRuntimeComponent.self] == nil else { return }
// Get this entity's PointOfInterestComponent, which is in the Reality Composer Pro project.
guard let pointOfInterest = entity.components[PointOfInterestComponent.self] else { return }
// Highlight the trail entity associated with this location marker entity.
let trailEntity: Entity? = entity.children.first(where: { $0.hasMaterialParameter(named: TrailAnimationSystem.materialParameterName) })
let tag: ObjectIdentifier = entity.id
let view = LearnMoreView(name: pointOfInterest.name,
description: pointOfInterest.description ?? "",
imageNames: pointOfInterest.imageNames,
trail: trailEntity,
viewModel: viewModel)
.tag(tag)
entity.components[PointOfInterestRuntimeComponent.self] = PointOfInterestRuntimeComponent(attachmentTag: tag)
attachmentsProvider.attachments[tag] = AnyView(view)
}
private func setupBirds(rootEntity entity: Entity) {
guard let birds = entity.findEntity(named: "Birds") else { return }
for bird in birds.children {
bird.components[FlockingComponent.self] = FlockingComponent()
guard let animationResource = bird.availableAnimations.first else { continue }
let controller = bird.playAnimation(animationResource.repeat())
controller.speed = Float.random(in: 1..<2.5)
}
}
}
#Preview {
DioramaView(viewModel: ViewModel())
.previewLayout(.sizeThatFits)
}