diff --git a/Swift/FishSense Swift.xcodeproj/project.pbxproj b/Swift/FishSense Swift.xcodeproj/project.pbxproj index 38e1de1..aa49441 100644 --- a/Swift/FishSense Swift.xcodeproj/project.pbxproj +++ b/Swift/FishSense Swift.xcodeproj/project.pbxproj @@ -16,6 +16,9 @@ 7AA6771F1CFF765600B353FB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7AA6771D1CFF765600B353FB /* LaunchScreen.storyboard */; }; 7AA677271CFF774800B353FB /* PhotoCaptureDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA677261CFF774800B353FB /* PhotoCaptureDelegate.swift */; }; 7AA677291CFF7B5C00B353FB /* PreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA677281CFF7B5C00B353FB /* PreviewView.swift */; }; + 9DA317CD2C585DF400482765 /* Entitlements.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9DA317CC2C585DF400482765 /* Entitlements.plist */; }; + 9DA317D12C58602000482765 /* DBHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA317D02C58602000482765 /* DBHelper.swift */; }; + 9DA317D32C58603300482765 /* Objects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DA317D22C58603300482765 /* Objects.swift */; }; EF9079482BE892D300EDEF43 /* PhotoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF9079472BE892D300EDEF43 /* PhotoViewController.swift */; }; EFBFFD1B2C0790390061AE66 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFBFFD1A2C0790390061AE66 /* Extensions.swift */; }; EFBFFD1D2C0791D30061AE66 /* SphereNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EFBFFD1C2C0791D30061AE66 /* SphereNode.swift */; }; @@ -59,6 +62,9 @@ 7AA677201CFF765600B353FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7AA677261CFF774800B353FB /* PhotoCaptureDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCaptureDelegate.swift; sourceTree = ""; }; 7AA677281CFF7B5C00B353FB /* PreviewView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreviewView.swift; sourceTree = ""; }; + 9DA317CC2C585DF400482765 /* Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Entitlements.plist; sourceTree = ""; }; + 9DA317D02C58602000482765 /* DBHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBHelper.swift; sourceTree = ""; }; + 9DA317D22C58603300482765 /* Objects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Objects.swift; sourceTree = ""; }; EF9079472BE892D300EDEF43 /* PhotoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoViewController.swift; sourceTree = ""; }; EFBFFD1A2C0790390061AE66 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; EFBFFD1C2C0791D30061AE66 /* SphereNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SphereNode.swift; sourceTree = ""; }; @@ -123,11 +129,22 @@ 7AA6771B1CFF765600B353FB /* Assets.xcassets */, 7AA6771D1CFF765600B353FB /* LaunchScreen.storyboard */, 7AA677201CFF765600B353FB /* Info.plist */, + 9DA317CC2C585DF400482765 /* Entitlements.plist */, 42C6D5B12C3EFA94009711A1 /* Zipping.swift */, + 9DA317C72C585B0C00482765 /* Local Database */, ); path = FishSense; sourceTree = ""; }; + 9DA317C72C585B0C00482765 /* Local Database */ = { + isa = PBXGroup; + children = ( + 9DA317D02C58602000482765 /* DBHelper.swift */, + 9DA317D22C58603300482765 /* Objects.swift */, + ); + path = "Local Database"; + sourceTree = ""; + }; DF594A7680971C5690D94783 /* Configuration */ = { isa = PBXGroup; children = ( @@ -212,6 +229,7 @@ 7AA6771F1CFF765600B353FB /* LaunchScreen.storyboard in Resources */, 7AA6771C1CFF765600B353FB /* Assets.xcassets in Resources */, 7697F939217A94FB0011862B /* README.md in Resources */, + 9DA317CD2C585DF400482765 /* Entitlements.plist in Resources */, 7AA6771A1CFF765600B353FB /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -234,6 +252,8 @@ 7AA677151CFF765600B353FB /* AppDelegate.swift in Sources */, EF9079482BE892D300EDEF43 /* PhotoViewController.swift in Sources */, EFBFFD1B2C0790390061AE66 /* Extensions.swift in Sources */, + 9DA317D32C58603300482765 /* Objects.swift in Sources */, + 9DA317D12C58602000482765 /* DBHelper.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -388,7 +408,7 @@ CLANG_CXX_LIBRARY = "libc++"; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = J7APG66N2C; + DEVELOPMENT_TEAM = BV4K5476V9; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Frameworks", @@ -399,7 +419,7 @@ "@executable_path/Frameworks", ); OTHER_LDFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = org.ucsd.e4e.FishSense; + PRODUCT_BUNDLE_IDENTIFIER = org.mango.ucsd.e4e.FishSense; PRODUCT_NAME = FishSense; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; @@ -417,7 +437,7 @@ CLANG_CXX_LIBRARY = "libc++"; CODE_SIGN_IDENTITY = "iPhone Developer"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = J7APG66N2C; + DEVELOPMENT_TEAM = BV4K5476V9; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Frameworks", @@ -428,7 +448,7 @@ "@executable_path/Frameworks", ); OTHER_LDFLAGS = ""; - PRODUCT_BUNDLE_IDENTIFIER = org.ucsd.e4e.FishSense; + PRODUCT_BUNDLE_IDENTIFIER = org.mango.ucsd.e4e.FishSense; PRODUCT_NAME = FishSense; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; diff --git a/Swift/FishSense/Base.lproj/Main.storyboard b/Swift/FishSense/Base.lproj/Main.storyboard index c586f25..457a07c 100644 --- a/Swift/FishSense/Base.lproj/Main.storyboard +++ b/Swift/FishSense/Base.lproj/Main.storyboard @@ -258,21 +258,6 @@ - - - - - - - - - - - - - - - diff --git a/Swift/FishSense/Entitlements.plist b/Swift/FishSense/Entitlements.plist new file mode 100644 index 0000000..f7df460 --- /dev/null +++ b/Swift/FishSense/Entitlements.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + com.apple.security.files.documents + + + diff --git a/Swift/FishSense/ImageGallery.swift b/Swift/FishSense/ImageGallery.swift index dd02ac5..c34ddb5 100644 --- a/Swift/FishSense/ImageGallery.swift +++ b/Swift/FishSense/ImageGallery.swift @@ -10,6 +10,10 @@ struct ImageGallery: View { @State private var selectedItem: DataTemp? @State private var position = CGSize.zero + // To display the length of the fish + // var fish_length: Int64 + // + var body: some View { ZStack { ScrollView { @@ -31,7 +35,7 @@ struct ImageGallery: View { let fileAttribute: [FileAttributeKey : Any] = try FileManager.default.attributesOfItem(atPath: zipUrl.path) if let fileNumberSize: NSNumber = fileAttribute[FileAttributeKey.size] as? NSNumber { - fileSizeValue = UInt64(fileNumberSize) + fileSizeValue = UInt64(truncating: fileNumberSize) let byteCountFormatter: ByteCountFormatter = ByteCountFormatter() byteCountFormatter.countStyle = ByteCountFormatter.CountStyle.file @@ -57,7 +61,12 @@ struct ImageGallery: View { print("Error occurred: \(error)") } } - }) + } + // + //THIS IS WHERE I WOULD LIKE TO PUT A DELETE BUTTON NEXT TO THE IMPORT BUTTON + //I would like for it to display a message and ask, "Before proceeding, are you sure you want to delete all data?" ( YES | NO ) + // + ) ForEach(dataList) { data in HStack(spacing: 2) { // Image on the left @@ -84,6 +93,7 @@ struct ImageGallery: View { Text("Time: \n\(data.creationDate, formatter: dateFormatter)\n ") .foregroundColor(Color.primary) Text("Fish Length: \(data.fishLen.map { "\($0)" } ?? "Unavailable")") + //Text("Fish Length: \(fish_length)") .foregroundColor(Color.primary) } .padding(8) diff --git a/Swift/FishSense/Info.plist b/Swift/FishSense/Info.plist index 3c9ed95..4577a48 100644 --- a/Swift/FishSense/Info.plist +++ b/Swift/FishSense/Info.plist @@ -2,57 +2,68 @@ - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 5.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - LSApplicationCategoryType - - LSRequiresIPhoneOS - - LSSupportsOpeningDocumentsInPlace - - NSCameraUsageDescription - FishSense uses the camera to take fish length measurements - NSLocationWhenInUseUsageDescription - AVCam uses your location to tag where photos and videos are taken. - NSMicrophoneUsageDescription - FishSense - NSPhotoLibraryUsageDescription - AVCam saves captured photos and videos to your photo library. - UIFileSharingEnabled - - UILaunchStoryboardName - Launch Screen - UIMainStoryboardFile - Main - UIRequiredDeviceCapabilities - - armv7 - - UIRequiresFullScreen - - UIStatusBarHidden - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UIViewControllerBasedStatusBarAppearance - + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 5.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSApplicationCategoryType + + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + + NSCameraUsageDescription + FishSense uses the camera to take fish length measurements + NSLocationWhenInUseUsageDescription + AVCam uses your location to tag where photos and videos are taken. + NSMicrophoneUsageDescription + FishSense + NSPhotoLibraryUsageDescription + AVCam saves captured photos and videos to your photo library. + UIFileSharingEnabled + + UILaunchStoryboardName + Launch Screen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UIViewControllerBasedStatusBarAppearance + + + + UIApplicationStateRestorationEnabled + + + + NSDocumentsFolderUsageDescription + This app needs access to the Documents folder to store data. + NSFileProviderDomainUsageDescription + This app needs access to the FileProvider domain to manage files. + diff --git a/Swift/FishSense/Local Database/DBHelper.swift b/Swift/FishSense/Local Database/DBHelper.swift new file mode 100644 index 0000000..886dc9a --- /dev/null +++ b/Swift/FishSense/Local Database/DBHelper.swift @@ -0,0 +1,135 @@ +// +// DBHelper.swift +// FishSense +// +// Created by Allen Kan on 7/29/24. +// Copyright © 2024 E4E. All rights reserved. +// + +import Foundation +import SQLite3 + +class DBHelper { + var db: OpaquePointer? + var path: String = "FishSenseDB.sqlite" + + init() { + self.db = createDB() + self.createTable() + } + + func createDB() -> OpaquePointer? { + do { + let filePath = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(path) + + var db: OpaquePointer? = nil + if sqlite3_open(filePath.path, &db) != SQLITE_OK { + let errmsg = String(cString: sqlite3_errmsg(db)) + print("There is an error in creating the database: \(errmsg)") + return nil + } else { + print("Successfully opened a connection to the database at \(filePath.path)") + return db + } + } catch { + print("Error finding file path: \(error.localizedDescription)") + return nil + } + } + + func createTable() { + let query = "CREATE TABLE IF NOT EXISTS FishData(id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, fish_length INTEGER, headx INTEGER, heady INTEGER, tailx INTEGER, taily INTEGER, lat DOUBLE, long DOUBLE, rgb TEXT, depth TEXT, mask TEXT);" + var statement: OpaquePointer? = nil + + if sqlite3_prepare_v2(self.db, query, -1, &statement, nil) == SQLITE_OK { + if sqlite3_step(statement) == SQLITE_DONE { + print("Table creation success") + } else { + let errmsg = String(cString: sqlite3_errmsg(db)) + print("Table creation fail: \(errmsg)") + } + } else { + let errmsg = String(cString: sqlite3_errmsg(db)) + print("Preparation fail: \(errmsg)") + } + sqlite3_finalize(statement) + } + + //Assign default values to the objects in the database + func insert(timestamp: Int64 = 0, fish_length: Int64 = 0, headx: Int64 = 0, heady: Int64 = 0, tailx: Int64 = 0, taily: Int64 = 0, lat: Double = 0.0, long: Double = 0.0, rgb: String = "", depth: String = "", mask: String = "") { + let query = "INSERT INTO FishData(timestamp, fish_length, headx, heady, tailx, taily, lat, long, rgb, depth, mask) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" + var statement: OpaquePointer? = nil + + if sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK { + sqlite3_bind_int64(statement, 1, timestamp) + sqlite3_bind_int64(statement, 2, fish_length) + sqlite3_bind_int64(statement, 3, headx) + sqlite3_bind_int64(statement, 4, heady) + sqlite3_bind_int64(statement, 5, tailx) + sqlite3_bind_int64(statement, 6, taily) + + sqlite3_bind_double(statement, 7, (lat)) + sqlite3_bind_double(statement, 8, (long)) + + sqlite3_bind_text(statement, 9, (rgb as NSString).utf8String, -1, nil) + sqlite3_bind_text(statement, 10, (depth as NSString).utf8String, -1, nil) + sqlite3_bind_text(statement, 11, (mask as NSString).utf8String, -1, nil) + + if sqlite3_step(statement) == SQLITE_DONE { + print("Data inserted successfully") + printEntireTable() + + } else { + let errmsg = String(cString: sqlite3_errmsg(db)) + print("Data is not inserted in table. Error: \(errmsg)") + } + } else { + let errmsg = String(cString: sqlite3_errmsg(db)) + print("Query is not as per requirement. Error: \(errmsg)") + } + sqlite3_finalize(statement) + } + + func read(avg: Int) -> [FishData] { + var mainList = [FishData]() + let query = "SELECT * FROM FishData;" + var statement: OpaquePointer? = nil + + if sqlite3_prepare_v2(db, query, -1, &statement, nil) == SQLITE_OK { + while sqlite3_step(statement) == SQLITE_ROW { + let id = Int64(sqlite3_column_int(statement, 0)) + let timestamp = Int64(sqlite3_column_int(statement, 1)) + let fish_length = Int64(sqlite3_column_int(statement, 2)) + let headx = Int64(sqlite3_column_int(statement, 3)) + let heady = Int64(sqlite3_column_int(statement, 4)) + let tailx = Int64(sqlite3_column_int(statement, 5)) + let taily = Int64(sqlite3_column_int(statement, 6)) + + let lat = (sqlite3_column_double(statement, 7)) + let long = (sqlite3_column_double(statement, 8)) + + let rgb = sqlite3_column_text(statement, 9) != nil ? String(cString: sqlite3_column_text(statement, 9)) : "" + let depth = sqlite3_column_text(statement, 10) != nil ? String(cString: sqlite3_column_text(statement, 10)) : "" + let mask = sqlite3_column_text(statement, 11) != nil ? String(cString: sqlite3_column_text(statement, 11)) : "" + + let model = FishData(id: id, timestamp: timestamp, fish_length: fish_length, headx: headx, heady: heady, tailx: tailx, taily: taily, lat: lat, long: long, rgb: rgb, depth: depth, mask: mask) + mainList.append(model) + } + } else { + let errmsg = String(cString: sqlite3_errmsg(db)) + print("Query preparation has failed. Error: \(errmsg)") + } + sqlite3_finalize(statement) + print("Query preparation has succeeded.") + return mainList + } + + + //Upon taking a picture, print the entire table + func printEntireTable() { + let fishDataList = read(avg: 0) + for fishData in fishDataList { + print(fishData.description) + } + } +} diff --git a/Swift/FishSense/Local Database/Objects.swift b/Swift/FishSense/Local Database/Objects.swift new file mode 100644 index 0000000..4146088 --- /dev/null +++ b/Swift/FishSense/Local Database/Objects.swift @@ -0,0 +1,44 @@ +// +// Objects.swift +// FishSense +// +// Created by Allen Kan on 7/29/24. +// Copyright © 2024 E4E. All rights reserved. +// + +import Foundation + +class FishData : Codable { + var id:Int64 + var timestamp:Int64 + var fish_length:Int64 + var headx:Int64 + var heady:Int64 + var tailx:Int64 + var taily:Int64 + var lat:Double + var long:Double + var rgb:String + var depth:String + var mask:String + + init(id: Int64, timestamp: Int64, fish_length: Int64, headx: Int64, heady: Int64, tailx: Int64, taily: Int64, lat: Double, long: Double, rgb: String, depth: String, mask: String) { + self.id = id + self.timestamp = timestamp + self.fish_length = fish_length + self.headx = headx + self.heady = heady + self.tailx = tailx + self.taily = taily + self.lat = lat + self.long = long + self.rgb = rgb + self.depth = depth + self.mask = mask + } + //Written by ChatGPT + var description: String { + return "ID: \(id), Timestamp: \(timestamp), Length: \(fish_length), HeadX: \(headx), HeadY: \(heady), TailX: \(tailx), TailY: \(taily), Latitude: \(lat), Longitude: \(long), RGB: \(rgb), Depth: \(depth), Mask: \(mask)" + } + // +} diff --git a/Swift/FishSense/ViewController.swift b/Swift/FishSense/ViewController.swift index 995c7a5..f591845 100644 --- a/Swift/FishSense/ViewController.swift +++ b/Swift/FishSense/ViewController.swift @@ -2,8 +2,21 @@ import ARKit import RealityKit import FishSenseRS +//For Database +import UIKit +import SQLite3 +// + +//For the database + var db: DBHelper! + var fishDataList: [FishData]! + var fish_length = Int64() +// + + class ViewController: UIViewController, ARSessionDelegate { + @IBOutlet var arView: ARView! @IBOutlet weak var hideMeshButton: UIButton! @IBOutlet weak var resetButton: UIButton! @@ -46,6 +59,10 @@ class ViewController: UIViewController, ARSessionDelegate { override func viewDidLoad() { super.viewDidLoad() + // Initialize the database helper + db = DBHelper() + // + arView.session.delegate = self setupCoachingOverlay() @@ -89,6 +106,9 @@ class ViewController: UIViewController, ARSessionDelegate { view.addSubview(greyView) view.addSubview(statusLabel) view.addSubview(lengthLabel) + + // Read data from the database + fishDataList = db.read(avg: 0) } override func viewDidLayoutSubviews() { @@ -172,6 +192,7 @@ class ViewController: UIViewController, ARSessionDelegate { if #available(iOS 14.0, *) { if let depthData = currentFrame.sceneDepth?.depthMap { let image = UIImage(cgImage: cgImage) + let timestamp = Date().timeIntervalSince1970 // Save the RGB image @@ -214,6 +235,10 @@ class ViewController: UIViewController, ARSessionDelegate { // Save the length data saveLength(lengthResult, andTimeStamp: timestamp) + + // printing the database + //printFishDataList(fishDataList) + } else { print("We did not find a fish in swift!") @@ -229,9 +254,22 @@ class ViewController: UIViewController, ARSessionDelegate { } func saveLength(_ lengthResult: ComputeLengthResult, andTimeStamp timestamp: TimeInterval) { - displayErrorMessage(title: "Fish Length", message: "\((lengthResult.length * 1000).rounded() / 10)cm") + displayErrorMessage(title: "Fish Length", message: "\((lengthResult.length * 1000).rounded())mm") + + // Insert the length data into the database + db.insert(timestamp: Int64(Date().timeIntervalSince1970), fish_length:Int64((lengthResult.length * 1000).rounded()), headx: Int64(lengthResult.left.x), heady: Int64(lengthResult.left.y), tailx: Int64(lengthResult.right.x), taily: Int64(lengthResult.right.y), lat: 0.0, long: 0.0, rgb: "rgb_\(Int64(Date().timeIntervalSince1970)).jpg", depth: "depth_\(Int64(Date().timeIntervalSince1970)).png", mask: "temp_mask_data") + + /* + if let imageGalleryVC = storyboard?.instantiateViewController(withIdentifier: "ImageGallery") as? ImageGallery { + // Pass the calculated fish length + imageGalleryVC.fish_length = fish_length + + // Present or push the ImageGallery + self.present(imageGalleryVC, animated: true, completion: nil) + } + */ } - + /*func saveImage(_ image: UIImage, withName name: String) { if let imageData = image.jpegData(compressionQuality: 0.8) { let filePath = getDocumentsDirectory().appendingPathComponent(name) @@ -261,7 +299,6 @@ class ViewController: UIViewController, ARSessionDelegate { } } - func saveDepthData(_ depthData: CVPixelBuffer, withName name: String) { let ciImage = CIImage(cvPixelBuffer: depthData) let context = CIContext() @@ -480,8 +517,19 @@ class ViewController: UIViewController, ARSessionDelegate { } statusLabel.text = status } + + /* + //For the database + func printFishDataList(_ fishDataList: [FishData]) { + for fishData in fishDataList { + print(fishData.description) + } + } + */ } + + extension SCNGeometry { convenience init(arGeometry: ARMeshGeometry) { let verticesSource = SCNGeometrySource(arGeometry.vertices, semantic: .vertex) @@ -490,11 +538,13 @@ extension SCNGeometry { self.init(sources: [verticesSource, normalsSource], elements: [faces]) } } + extension SCNGeometrySource { convenience init(_ source: ARGeometrySource, semantic: Semantic) { self.init(buffer: source.buffer, vertexFormat: source.format, semantic: semantic, vertexCount: source.count, dataOffset: source.offset, dataStride: source.stride) } } + extension SCNGeometryElement { convenience init(_ source: ARGeometryElement) { let pointer = source.buffer.contents() @@ -503,6 +553,7 @@ extension SCNGeometryElement { self.init(data: data, primitiveType: .of(source.primitiveType), primitiveCount: source.count, bytesPerIndex: source.bytesPerIndex) } } + extension SCNGeometryPrimitiveType { static func of(_ type: ARGeometryPrimitiveType) -> SCNGeometryPrimitiveType { switch type {