-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
uploaded different working components; composing view to be fixed
- Loading branch information
Lily Yu
committed
Dec 10, 2023
1 parent
b7c47fd
commit 05792e1
Showing
116 changed files
with
5,694 additions
and
0 deletions.
There are no files selected for viewing
593 changes: 593 additions & 0 deletions
593
CustomizableMessagePlane/CustomizableMessagePlane.xcodeproj/project.pbxproj
Large diffs are not rendered by default.
Oops, something went wrong.
7 changes: 7 additions & 0 deletions
7
...sagePlane/CustomizableMessagePlane.xcodeproj/project.xcworkspace/contents.xcworkspacedata
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
8 changes: 8 additions & 0 deletions
8
...tomizableMessagePlane.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
Binary file added
BIN
+36.9 KB
...odeproj/project.xcworkspace/xcuserdata/lily_yu.xcuserdatad/UserInterfaceState.xcuserstate
Binary file not shown.
14 changes: 14 additions & 0 deletions
14
...eMessagePlane.xcodeproj/xcuserdata/lily_yu.xcuserdatad/xcschemes/xcschememanagement.plist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>SchemeUserState</key> | ||
<dict> | ||
<key>CustomizableMessagePlane.xcscheme_^#shared#^_</key> | ||
<dict> | ||
<key>orderHint</key> | ||
<integer>0</integer> | ||
</dict> | ||
</dict> | ||
</dict> | ||
</plist> |
309 changes: 309 additions & 0 deletions
309
CustomizableMessagePlane/CustomizableMessagePlane/ARView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,309 @@ | ||
// | ||
// ARView.swift | ||
// CustomizableMessagePlane | ||
// | ||
// Created by Lily Yu on 11/9/23. | ||
// | ||
|
||
// Code example for delegation functions: https://github.com/augmentedhacking/ar-plane-classification | ||
|
||
import Foundation | ||
import SwiftUI | ||
import ARKit | ||
import RealityKit | ||
import Combine | ||
|
||
struct ARViewContainer: UIViewRepresentable { | ||
let viewModel: ViewModel | ||
|
||
func makeUIView(context: Context) -> ARView { | ||
let arView = SimpleARView(frame: .zero, viewModel: viewModel) | ||
return arView | ||
} | ||
|
||
func updateUIView(_ uiView: ARView, context: Context) {} | ||
} | ||
|
||
class SimpleARView: ARView { | ||
let viewModel: ViewModel | ||
private var subscriptions = Set<AnyCancellable>() | ||
|
||
// var planeAnchor: AnchorEntity? | ||
var messagePlaneEntity: MessagePlaneEntity? | ||
|
||
// Dictionary for storing ARPlaneAnchor(s) with AnchorEntity(s) | ||
var anchorEntityMap: [ARPlaneAnchor: AnchorEntity] = [:] | ||
|
||
init(frame: CGRect, viewModel: ViewModel) { | ||
self.viewModel = viewModel | ||
super.init(frame: frame) | ||
} | ||
|
||
required init?(coder decoder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
required init(frame frameRect: CGRect) { | ||
fatalError("init(frame:) has not been implemented") | ||
} | ||
|
||
override func didMoveToSuperview() { | ||
super.didMoveToSuperview() | ||
|
||
UIApplication.shared.isIdleTimerDisabled = true | ||
|
||
self.setupARSession() | ||
// self.setupScene() | ||
self.setupSubscriptions() | ||
} | ||
|
||
private func setupARSession() { | ||
let configuration = ARWorldTrackingConfiguration() | ||
configuration.planeDetection = [.vertical, .horizontal] | ||
|
||
let options: ARSession.RunOptions = [.removeExistingAnchors] | ||
|
||
self.session.delegate = self | ||
|
||
session.run(configuration, options: options) | ||
} | ||
|
||
private func setupScene() { | ||
// let planeAnchor = AnchorEntity(plane: .vertical) | ||
// scene.addAnchor(planeAnchor) | ||
|
||
// let messagePlaneEntity = MessagePlaneEntity(message: viewModel.message, planeAnchor: <#ARPlaneAnchor#>) | ||
// planeAnchor.addChild(messagePlaneEntity) | ||
// self.messagePlaneEntity = messagePlaneEntity | ||
|
||
} | ||
|
||
private func resetScene() { | ||
// messagePlaneEntity?.removeFromParent() | ||
// messagePlaneEntity = nil | ||
|
||
// planeAnchor?.removeFromParent() | ||
// planeAnchor = nil | ||
|
||
// TODO: clear the anchor entity array | ||
|
||
for (_, anchorEntity) in anchorEntityMap { | ||
// Remove all child entities of the anchor entity | ||
anchorEntity.children.removeAll() | ||
|
||
// Remove the anchor entity itself from the scene | ||
anchorEntity.removeFromParent() | ||
|
||
// Remove the ARPlaneAnchor | ||
if let planeAnchor = anchorEntity.anchor as? ARPlaneAnchor { | ||
// Remove the anchor from the session | ||
self.session.remove(anchor: planeAnchor) | ||
} | ||
} | ||
|
||
// Clear the anchorEntityMap | ||
anchorEntityMap.removeAll() | ||
|
||
setupARSession() | ||
|
||
// setupScene() | ||
} | ||
|
||
private func setupSubscriptions() { | ||
// Subscribe to message changes and regenerate texture | ||
// Drop first message so this doesn't run when app first loads | ||
self.viewModel.$message.dropFirst().sink { message in | ||
DispatchQueue.main.async { [weak self] in | ||
guard let self = self else { return } | ||
|
||
self.anchorEntityMap.values.forEach { anchorEntity in | ||
// let messageEntity = anchorEntity.findEntity(named: "message-plane") as? MessagePlaneEntity | ||
let messageEntities: [MessagePlaneEntity] = anchorEntity.children.compactMap { entity in | ||
return entity as? MessagePlaneEntity | ||
} | ||
|
||
messageEntities.forEach { entity in | ||
entity.messageEntity.updateMessageTexture(message: message) | ||
} | ||
} | ||
|
||
|
||
// This only updates the last reference | ||
// self.messagePlaneEntity?.messageEntity.updateMessageTexture(message: message) | ||
} | ||
}.store(in: &subscriptions) | ||
|
||
|
||
// Subscribe to reset signal | ||
self.viewModel.resetSignal.sink { [weak self] in | ||
guard let self = self else { return } | ||
self.resetScene() | ||
}.store(in: &subscriptions) | ||
} | ||
} | ||
|
||
// MARK: - Implement ARSessionDelegate protocol | ||
extension SimpleARView: ARSessionDelegate { | ||
// Tells the delegate that one or more anchors have been added to the session. | ||
func session(_ session: ARSession, didAdd anchors: [ARAnchor]) { | ||
// Filter added anchors for plane anchors | ||
let planeAnchors = anchors.compactMap { $0 as? ARPlaneAnchor } | ||
|
||
planeAnchors.forEach { | ||
// Create a RealityKit anchor at plane anchor's position | ||
let anchorEntity = AnchorEntity() | ||
|
||
/* --------- Plane Visualization Stuff --------- */ | ||
|
||
// Estimated size of detected plane | ||
// let extent = $0.planeExtent | ||
|
||
// Generate a rough plane mesh based on anchor extent | ||
// Later we will update this plane based on more detailed geometry as ARKit learns more about our environment | ||
// let planeMesh: MeshResource = .generatePlane(width: extent.width, | ||
// depth: extent.height) | ||
|
||
// Set color based on plane classification using our extension defined at bottom of this file | ||
let planeClassification = $0.classification | ||
|
||
// let modelEntity = ModelEntity(mesh: planeMesh, | ||
// materials: [planeClassification.debugMaterial]) | ||
|
||
// Add plane model entity to anchor entity | ||
// anchorEntity.addChild(modelEntity) | ||
|
||
/* -------------------------------------------- */ | ||
|
||
// Create a message plane entity based on the plane anchor | ||
self.messagePlaneEntity = | ||
MessagePlaneEntity(message: viewModel.message, planeAnchor: $0) | ||
|
||
// Add message plane entity to anchor entity | ||
anchorEntity.addChild(self.messagePlaneEntity!) | ||
|
||
// Assign AR plane anchor's transform to our anchor Entity | ||
anchorEntity.transform.matrix = $0.transform | ||
|
||
// Add anchor entity to our scene | ||
self.scene.addAnchor(anchorEntity) | ||
|
||
// Store ARKit's ARPlaneAnchor along with our associated RealityKit Anchor Entity | ||
self.anchorEntityMap[$0] = anchorEntity | ||
} | ||
} | ||
|
||
//Tells the delegate that the session has adjusted the properties of one or more anchors. | ||
func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) { | ||
// Filter updated anchors for plane anchors | ||
let planeAnchors = anchors.compactMap { $0 as? ARPlaneAnchor } | ||
|
||
planeAnchors.forEach { planeAnchor in | ||
// Look for an associated AnchorEntity in our dictionary, otherwise do nothing | ||
guard let anchorEntity = self.anchorEntityMap[planeAnchor] else { | ||
return | ||
} | ||
|
||
|
||
/* --------- Plane Visualization Stuff --------- */ | ||
|
||
// Look for AnchorEntity's first child that is a model entity | ||
// A better way to do this would use a custom entity, but this works for the tutorial | ||
guard let messagePlaneEntity = (anchorEntity | ||
.children | ||
.compactMap { $0 as? MessagePlaneEntity } | ||
.first) else { | ||
return | ||
} | ||
|
||
// Get detailed plane geometry | ||
var meshDescriptor = MeshDescriptor(name: "plane") | ||
meshDescriptor.positions = MeshBuffers.Positions(planeAnchor.geometry.vertices) | ||
meshDescriptor.primitives = .triangles(planeAnchor.geometry.triangleIndices.map { UInt32($0)}) | ||
|
||
DispatchQueue.main.async { | ||
// Try creating mesh from detailed ARPlaneGeometry | ||
if let mesh = try? MeshResource.generate(from: [meshDescriptor]) { | ||
messagePlaneEntity.modelEntity.model?.mesh = mesh | ||
} | ||
|
||
|
||
let bounds = messagePlaneEntity.modelEntity.visualBounds(relativeTo: messagePlaneEntity.modelEntity) | ||
|
||
let center = bounds.center | ||
messagePlaneEntity.messageEntity.position = bounds.center | ||
messagePlaneEntity.messageEntity.position.y = center.y + 0.001 | ||
} | ||
// | ||
// Get detailed plane geometry | ||
// var meshDescriptor = MeshDescriptor(name: "plane") | ||
// meshDescriptor.positions = MeshBuffers.Positions(planeAnchor.geometry.vertices) | ||
// meshDescriptor.primitives = .triangles(planeAnchor.geometry.triangleIndices.map { UInt32($0)}) | ||
// | ||
// DispatchQueue.main.async { | ||
// Try creating mesh from detailed ARPlaneGeometry | ||
// if let mesh = try? MeshResource.generate(from: [meshDescriptor]) { | ||
// modelEntity?.model?.mesh = mesh | ||
// } | ||
// | ||
// let planeClassification = planeAnchor.classification | ||
// modelEntity?.model?.materials = [planeClassification.debugMaterial] | ||
// } | ||
|
||
/* -------------------------------------------- */ | ||
|
||
|
||
// Update message plane entity's transform | ||
// messagePlaneEntity?.transform = Transform(matrix: planeAnchor.transform) | ||
|
||
// print(messagePlaneEntity) | ||
|
||
|
||
} | ||
} | ||
|
||
// Tells the delegate that one or more anchors have been removed from the session. | ||
func session(_ session: ARSession, didRemove anchors: [ARAnchor]) { | ||
// Filter removed anchors for plane anchors | ||
let planeAnchors = anchors.compactMap { $0 as? ARPlaneAnchor } | ||
|
||
planeAnchors.forEach { | ||
// Look for an associated AnchorEntity in our dictionary, otherwise do nothing | ||
guard let anchorEntity = self.anchorEntityMap[$0] else { | ||
return | ||
} | ||
|
||
// Remove anchor entity from scene and dictionary | ||
anchorEntity.removeFromParent() | ||
self.anchorEntityMap.removeValue(forKey: $0) | ||
} | ||
} | ||
} | ||
|
||
// Extension for coloring planes by classification | ||
extension ARPlaneAnchor.Classification { | ||
var debugMaterial: SimpleMaterial { | ||
return SimpleMaterial(color: self.debugColor.withAlphaComponent(0.9), | ||
isMetallic: false) | ||
} | ||
|
||
var debugColor: UIColor { | ||
switch self { | ||
case .ceiling: | ||
return .blue | ||
case .door: | ||
return .magenta | ||
case .floor: | ||
return .red | ||
case .seat: | ||
return .green | ||
case .table: | ||
return .yellow | ||
case .wall: | ||
return .cyan | ||
case .window: | ||
return .white | ||
default: | ||
return .gray | ||
} | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
CustomizableMessagePlane/CustomizableMessagePlane/AppDelegate.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// | ||
// AppDelegate.swift | ||
// CustomizableMessagePlane | ||
// | ||
// Created by Lily Yu on 11/9/23. | ||
// | ||
|
||
import UIKit | ||
import SwiftUI | ||
|
||
@main | ||
class AppDelegate: UIResponder, UIApplicationDelegate { | ||
|
||
var window: UIWindow? | ||
|
||
|
||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { | ||
|
||
// Create the SwiftUI view that provides the window contents. | ||
let contentView = ContentView() | ||
|
||
// Use a UIHostingController as window root view controller. | ||
let window = UIWindow(frame: UIScreen.main.bounds) | ||
window.rootViewController = UIHostingController(rootView: contentView) | ||
self.window = window | ||
window.makeKeyAndVisible() | ||
return true | ||
} | ||
|
||
func applicationWillResignActive(_ application: UIApplication) { | ||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. | ||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. | ||
} | ||
|
||
func applicationDidEnterBackground(_ application: UIApplication) { | ||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. | ||
} | ||
|
||
func applicationWillEnterForeground(_ application: UIApplication) { | ||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. | ||
} | ||
|
||
func applicationDidBecomeActive(_ application: UIApplication) { | ||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. | ||
} | ||
|
||
|
||
} | ||
|
Oops, something went wrong.