├── .gitignore ├── Demo.mp4 ├── Packages └── RealityKitContent │ ├── Package.realitycomposerpro │ ├── PluginData │ │ └── D66134B1-3681-4A8E-AFE5-29F257229F3B │ │ │ └── ShaderGraphEditorPluginID │ │ │ └── ShaderGraphEditorPluginID │ ├── ProjectData │ │ └── main.json │ └── WorkspaceData │ │ ├── SceneMetadataList.json │ │ ├── Settings.rcprojectdata │ │ └── taat.rcuserdata │ ├── Package.swift │ ├── README.md │ └── Sources │ └── RealityKitContent │ ├── RealityKitContent.rkassets │ ├── Drone.usdz │ ├── Low_Poly_Nature.usdz │ ├── Materials │ │ └── GridMaterial.usda │ └── Scene.usda │ └── RealityKitContent.swift ├── README.md ├── RealityKitPerspectiveCamera.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── RealityKitPerspectiveCamera ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.solidimagestack │ │ ├── Back.solidimagestacklayer │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Front.solidimagestacklayer │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ └── Middle.solidimagestacklayer │ │ │ ├── Content.imageset │ │ │ └── Contents.json │ │ │ └── Contents.json │ ├── Contents.json │ └── Skybox.imageset │ │ ├── Contents.json │ │ └── panorama_image.png ├── Data │ └── ControlParameter.swift ├── ECS │ └── DroneControl.swift ├── Extensions │ ├── Entity+Extension.swift │ ├── MTLTexture+Extension.swift │ └── SIMD+Extension.swift ├── Info.plist ├── Models │ ├── AppModel.swift │ └── OffscreenRenderModel.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── RealityKitPerspectiveCameraApp.swift ├── Sunlight.skybox │ └── Sunlight.png └── Views │ ├── ContentView.swift │ ├── ControllerButton.swift │ ├── ImmersiveView.swift │ ├── OffscreenRenderView.swift │ └── ToggleImmersiveSpaceButton.swift └── RealityKitPerspectiveCameraTests └── RealityKitPerspectiveCameraTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## Obj-C/Swift specific 9 | *.hmap 10 | 11 | ## App packaging 12 | *.ipa 13 | *.dSYM.zip 14 | *.dSYM 15 | 16 | ## Playgrounds 17 | timeline.xctimeline 18 | playground.xcworkspace 19 | 20 | # Swift Package Manager 21 | # 22 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 23 | # Packages/ 24 | # Package.pins 25 | # Package.resolved 26 | # *.xcodeproj 27 | # 28 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 29 | # hence it is not needed unless you have added a package configuration file to your project 30 | # .swiftpm 31 | 32 | .build/ 33 | 34 | # CocoaPods 35 | # 36 | # We recommend against adding the Pods directory to your .gitignore. However 37 | # you should judge for yourself, the pros and cons are mentioned at: 38 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 39 | # 40 | # Pods/ 41 | # 42 | # Add this line if you want to avoid checking in source code from the Xcode workspace 43 | # *.xcworkspace 44 | 45 | # Carthage 46 | # 47 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 48 | # Carthage/Checkouts 49 | 50 | Carthage/Build/ 51 | 52 | # fastlane 53 | # 54 | # It is recommended to not store the screenshots in the git repo. 55 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 56 | # For more information about the recommended setup visit: 57 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 58 | 59 | fastlane/report.xml 60 | fastlane/Preview.html 61 | fastlane/screenshots/**/*.png 62 | fastlane/test_output 63 | .DS_Store 64 | -------------------------------------------------------------------------------- /Demo.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TAATHub/RealityKitPerspectiveCamera/f6495acd5383efaf119e7224ebe78e12773f129c/Demo.mp4 -------------------------------------------------------------------------------- /Packages/RealityKitContent/Package.realitycomposerpro/PluginData/D66134B1-3681-4A8E-AFE5-29F257229F3B/ShaderGraphEditorPluginID/ShaderGraphEditorPluginID: -------------------------------------------------------------------------------- 1 | { 2 | "materialPreviewObjectType" : 1, 3 | "materialPreviewEnvironmentType" : 2 4 | } -------------------------------------------------------------------------------- /Packages/RealityKitContent/Package.realitycomposerpro/ProjectData/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "pathsToIds" : { 3 | "\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/GridMaterial.usda" : "CB766F92-EE55-4A63-9401-E7B8C009764D", 4 | "\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Immersive.usda" : "65F6F990-A780-4474-B78B-572E0E4E273D", 5 | "\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Scene.usda" : "0A9B4653-B11E-4D6A-850E-C6FCB621626C", 6 | "\/RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Untitled Scene.usda" : "D560BB77-AAF3-4BDE-B7C4-989332A4688B", 7 | "RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/GridMaterial.usda" : "66168B71-AB05-424E-8B6C-D33D6E61B08F", 8 | "RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Immersive.usda" : "AF09ED6F-1707-48FD-8720-65B998362C09", 9 | "RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Scene.usda" : "D66134B1-3681-4A8E-AFE5-29F257229F3B", 10 | "RealityKitContent\/Sources\/RealityKitContent\/RealityKitContent.rkassets\/Untitled%20Scene.usda" : "A7A5E022-00ED-4B1F-9C36-C3AF8528D848" 11 | } 12 | } -------------------------------------------------------------------------------- /Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/SceneMetadataList.json: -------------------------------------------------------------------------------- 1 | { 2 | "0A9B4653-B11E-4D6A-850E-C6FCB621626C" : { 3 | "objectMetadataList" : [ 4 | [ 5 | "0A9B4653-B11E-4D6A-850E-C6FCB621626C", 6 | "Root" 7 | ], 8 | { 9 | "isExpanded" : true, 10 | "isLocked" : false 11 | } 12 | ] 13 | }, 14 | "65F6F990-A780-4474-B78B-572E0E4E273D" : { 15 | "objectMetadataList" : [ 16 | [ 17 | "65F6F990-A780-4474-B78B-572E0E4E273D", 18 | "Root" 19 | ], 20 | { 21 | "isExpanded" : true, 22 | "isLocked" : false 23 | } 24 | ] 25 | }, 26 | "66168B71-AB05-424E-8B6C-D33D6E61B08F" : { 27 | "objectMetadataList" : [ 28 | [ 29 | "66168B71-AB05-424E-8B6C-D33D6E61B08F", 30 | "Root" 31 | ], 32 | { 33 | "isExpanded" : true, 34 | "isLocked" : false 35 | } 36 | ] 37 | }, 38 | "A7A5E022-00ED-4B1F-9C36-C3AF8528D848" : { 39 | "objectMetadataList" : [ 40 | [ 41 | "A7A5E022-00ED-4B1F-9C36-C3AF8528D848", 42 | "Root" 43 | ], 44 | { 45 | "isExpanded" : true, 46 | "isLocked" : false 47 | } 48 | ] 49 | }, 50 | "AF09ED6F-1707-48FD-8720-65B998362C09" : { 51 | "objectMetadataList" : [ 52 | [ 53 | "AF09ED6F-1707-48FD-8720-65B998362C09", 54 | "Root", 55 | "Sphere_Left" 56 | ], 57 | { 58 | "isExpanded" : true, 59 | "isLocked" : false 60 | }, 61 | [ 62 | "AF09ED6F-1707-48FD-8720-65B998362C09", 63 | "Root" 64 | ], 65 | { 66 | "isExpanded" : true, 67 | "isLocked" : false 68 | }, 69 | [ 70 | "AF09ED6F-1707-48FD-8720-65B998362C09", 71 | "Root", 72 | "Sphere_Right" 73 | ], 74 | { 75 | "isExpanded" : true, 76 | "isLocked" : false 77 | } 78 | ] 79 | }, 80 | "CB766F92-EE55-4A63-9401-E7B8C009764D" : { 81 | "objectMetadataList" : [ 82 | [ 83 | "CB766F92-EE55-4A63-9401-E7B8C009764D", 84 | "Root", 85 | "GridMaterial" 86 | ], 87 | { 88 | "isExpanded" : true, 89 | "isLocked" : false 90 | }, 91 | [ 92 | "CB766F92-EE55-4A63-9401-E7B8C009764D", 93 | "Root" 94 | ], 95 | { 96 | "isExpanded" : true, 97 | "isLocked" : false 98 | } 99 | ] 100 | }, 101 | "D560BB77-AAF3-4BDE-B7C4-989332A4688B" : { 102 | "objectMetadataList" : [ 103 | 104 | ] 105 | }, 106 | "D66134B1-3681-4A8E-AFE5-29F257229F3B" : { 107 | "objectMetadataList" : [ 108 | [ 109 | "D66134B1-3681-4A8E-AFE5-29F257229F3B", 110 | "Root" 111 | ], 112 | { 113 | "isExpanded" : true, 114 | "isLocked" : false 115 | }, 116 | [ 117 | "D66134B1-3681-4A8E-AFE5-29F257229F3B", 118 | "Root", 119 | "SkyDome" 120 | ], 121 | { 122 | "disallowsFraming" : false, 123 | "isExpanded" : false, 124 | "isLocked" : false 125 | }, 126 | [ 127 | "D66134B1-3681-4A8E-AFE5-29F257229F3B", 128 | "Root", 129 | "SkyDome", 130 | "Looks", 131 | "DomeMATSG" 132 | ], 133 | { 134 | "isExpanded" : true, 135 | "isLocked" : false 136 | }, 137 | [ 138 | "D66134B1-3681-4A8E-AFE5-29F257229F3B", 139 | "Root", 140 | "Sphere", 141 | "DefaultMaterial" 142 | ], 143 | { 144 | "isExpanded" : true, 145 | "isLocked" : false 146 | }, 147 | [ 148 | "D66134B1-3681-4A8E-AFE5-29F257229F3B", 149 | "Root", 150 | "GridMaterial", 151 | "GridMaterial" 152 | ], 153 | { 154 | "isExpanded" : true, 155 | "isLocked" : false 156 | }, 157 | [ 158 | "D66134B1-3681-4A8E-AFE5-29F257229F3B", 159 | "Root", 160 | "SkySphere" 161 | ], 162 | { 163 | "disallowsFraming" : true, 164 | "isExpanded" : false, 165 | "isLocked" : false 166 | }, 167 | [ 168 | "D66134B1-3681-4A8E-AFE5-29F257229F3B", 169 | "Root", 170 | "Low_Poly_Nature" 171 | ], 172 | { 173 | "isExpanded" : true, 174 | "isLocked" : false 175 | } 176 | ] 177 | } 178 | } -------------------------------------------------------------------------------- /Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/Settings.rcprojectdata: -------------------------------------------------------------------------------- 1 | { 2 | "cameraPresets" : { 3 | 4 | }, 5 | "secondaryToolbarData" : { 6 | "isGridVisible" : true 7 | }, 8 | "unitDefaults" : { 9 | "kg" : "g", 10 | "kg⋅m²" : "kg⋅m²", 11 | "m" : "cm", 12 | "m\/s" : "m\/s", 13 | "m\/s²" : "m\/s²", 14 | "s" : "s", 15 | "°" : "°" 16 | } 17 | } -------------------------------------------------------------------------------- /Packages/RealityKitContent/Package.realitycomposerpro/WorkspaceData/taat.rcuserdata: -------------------------------------------------------------------------------- 1 | { 2 | "advancedEditorSelectedIdentifier" : "ProjectBrowser", 3 | "openSceneRelativePaths" : [ 4 | "Scene.usda" 5 | ], 6 | "recentIdentifiers" : { 7 | "A7A5E022-00ED-4B1F-9C36-C3AF8528D848" : [ 8 | 9 | ], 10 | "D66134B1-3681-4A8E-AFE5-29F257229F3B" : [ 11 | 12 | ] 13 | }, 14 | "sceneCameraHistory" : { 15 | "A7A5E022-00ED-4B1F-9C36-C3AF8528D848" : [ 16 | { 17 | "date" : 759988149.139215, 18 | "title" : "Untitled", 19 | "transform" : [ 20 | 0.6795796, 21 | 1.4487611e-08, 22 | 0.7336018, 23 | 0, 24 | 0.19118245, 25 | 0.9654448, 26 | -0.17710382, 27 | 0, 28 | -0.708252, 29 | 0.26060793, 30 | 0.6560966, 31 | 0, 32 | -2.312674, 33 | 0.86956894, 34 | 0.90425926, 35 | 1 36 | ] 37 | }, 38 | { 39 | "date" : 759981859.17541, 40 | "title" : "Untitled", 41 | "transform" : [ 42 | 0.3020571, 43 | 0, 44 | 0.95328987, 45 | 0, 46 | 0.6740777, 47 | 0.7071069, 48 | -0.2135866, 49 | 0, 50 | -0.6740778, 51 | 0.7071067, 52 | 0.21358664, 53 | 0, 54 | -1.7712253, 55 | 1.9057121, 56 | -0.39155838, 57 | 1 58 | ] 59 | }, 60 | { 61 | "date" : 759981801.728176, 62 | "title" : "Untitled", 63 | "transform" : [ 64 | 1, 65 | 0, 66 | 0, 67 | 0, 68 | 0, 69 | 0.7071069, 70 | -0.7071067, 71 | 0, 72 | 0, 73 | 0.7071067, 74 | 0.7071069, 75 | 0, 76 | 0.04120922, 77 | 1.8292296, 78 | 0.7141714, 79 | 1 80 | ] 81 | }, 82 | { 83 | "date" : 759981775.91679, 84 | "title" : "Untitled", 85 | "transform" : [ 86 | 1, 87 | 0, 88 | -0, 89 | 0, 90 | -0, 91 | 0.7071069, 92 | -0.7071067, 93 | 0, 94 | 0, 95 | 0.7071067, 96 | 0.7071069, 97 | 0, 98 | 0.21532345, 99 | 0.24524689, 100 | 2.8691864, 101 | 1 102 | ] 103 | }, 104 | { 105 | "date" : 759981761.026172, 106 | "title" : "Untitled", 107 | "transform" : [ 108 | 1, 109 | 0, 110 | 0, 111 | 0, 112 | 0, 113 | 0.7071069, 114 | -0.7071067, 115 | 0, 116 | 0, 117 | 0.7071067, 118 | 0.7071069, 119 | 0, 120 | 0, 121 | 19.636896, 122 | 19.636902, 123 | 1 124 | ] 125 | }, 126 | { 127 | "date" : 759981738.342347, 128 | "title" : "Untitled", 129 | "transform" : [ 130 | 1, 131 | 0, 132 | 0, 133 | 0, 134 | 0, 135 | 0.7071069, 136 | -0.7071067, 137 | 0, 138 | 0, 139 | 0.7071067, 140 | 0.7071069, 141 | 0, 142 | 0, 143 | 1.4918201, 144 | 1.4918203, 145 | 1 146 | ] 147 | }, 148 | { 149 | "date" : 759981722.734098, 150 | "title" : "Untitled", 151 | "transform" : [ 152 | 1, 153 | 0, 154 | -0, 155 | 0, 156 | -0, 157 | 0.7071069, 158 | -0.7071067, 159 | 0, 160 | 0, 161 | 0.7071067, 162 | 0.7071069, 163 | 0, 164 | 0, 165 | 0.09768093, 166 | 0.09768081, 167 | 1 168 | ] 169 | } 170 | ], 171 | "D66134B1-3681-4A8E-AFE5-29F257229F3B" : [ 172 | { 173 | "date" : 760424770.13711, 174 | "title" : "Untitled", 175 | "transform" : [ 176 | -0.5150496, 177 | 3.973966e-08, 178 | -0.8571604, 179 | 0, 180 | -0.027409825, 181 | 0.9994886, 182 | 0.016470043, 183 | 0, 184 | 0.856722, 185 | 0.03197751, 186 | -0.51478624, 187 | 0, 188 | 150.73586, 189 | 63.221245, 190 | -150.36374, 191 | 1 192 | ] 193 | }, 194 | { 195 | "date" : 760424676.562955, 196 | "title" : "Untitled", 197 | "transform" : [ 198 | -0.9360494, 199 | 3.973966e-08, 200 | 0.35186863, 201 | 0, 202 | 0.011251926, 203 | 0.9994886, 204 | 0.029932506, 205 | 0, 206 | -0.3516887, 207 | 0.03197751, 208 | -0.93557066, 209 | 0, 210 | -120.68547, 211 | 63.221245, 212 | -175.40115, 213 | 1 214 | ] 215 | }, 216 | { 217 | "date" : 760424659.144772, 218 | "title" : "Untitled", 219 | "transform" : [ 220 | -0.9360494, 221 | 3.973966e-08, 222 | 0.35186863, 223 | 0, 224 | 0.011251926, 225 | 0.9994886, 226 | 0.029932506, 227 | 0, 228 | -0.3516887, 229 | 0.03197751, 230 | -0.93557066, 231 | 0, 232 | -502.32544, 233 | 263.14386, 234 | -730.0669, 235 | 1 236 | ] 237 | }, 238 | { 239 | "date" : 760424633.583476, 240 | "title" : "Untitled", 241 | "transform" : [ 242 | -0.9360494, 243 | 3.973966e-08, 244 | 0.35186863, 245 | 0, 246 | 0.011251926, 247 | 0.9994886, 248 | 0.029932506, 249 | 0, 250 | -0.3516887, 251 | 0.03197751, 252 | -0.93557066, 253 | 0, 254 | -394.05743, 255 | 206.42752, 256 | -572.713, 257 | 1 258 | ] 259 | }, 260 | { 261 | "date" : 760424626.469207, 262 | "title" : "Untitled", 263 | "transform" : [ 264 | -0.999828, 265 | 3.787534e-08, 266 | 0.018542334, 267 | 0, 268 | -0.0035333368, 269 | 0.9816759, 270 | -0.19052498, 271 | 0, 272 | -0.018202621, 273 | -0.19055772, 274 | -0.9815071, 275 | 0, 276 | -667.8655, 277 | 191.03908, 278 | -2593.967, 279 | 1 280 | ] 281 | }, 282 | { 283 | "date" : 760424491.039866, 284 | "title" : "Untitled", 285 | "transform" : [ 286 | -0.9962827, 287 | 3.81872e-08, 288 | -0.086144246, 289 | 0, 290 | -0.023279544, 291 | 0.96279305, 292 | 0.2692351, 293 | 0, 294 | 0.082939036, 295 | 0.27023965, 296 | -0.95921403, 297 | 0, 298 | -424.12326, 299 | 1340.1371, 300 | -2288.0903, 301 | 1 302 | ] 303 | }, 304 | { 305 | "date" : 760424485.089654, 306 | "title" : "Untitled", 307 | "transform" : [ 308 | -0.9059724, 309 | 3.81872e-08, 310 | 0.42333686, 311 | 0, 312 | 0.114402436, 313 | 0.96279305, 314 | 0.24482964, 315 | 0, 316 | -0.40758577, 317 | 0.27023965, 318 | -0.87226385, 319 | 0, 320 | -1404.5072, 321 | 1245.725, 322 | -1645.1356, 323 | 1 324 | ] 325 | }, 326 | { 327 | "date" : 760424476.146142, 328 | "title" : "Untitled", 329 | "transform" : [ 330 | -0.55958897, 331 | 3.81872e-08, 332 | -0.8287703, 333 | 0, 334 | -0.22396658, 335 | 0.96279305, 336 | 0.15122315, 337 | 0, 338 | 0.7979343, 339 | 0.27023965, 340 | -0.53876835, 341 | 0, 342 | 194.22754, 343 | 34.98883, 344 | -191.66748, 345 | 1 346 | ] 347 | }, 348 | { 349 | "date" : 760424474.12837, 350 | "title" : "Untitled", 351 | "transform" : [ 352 | -0.55958897, 353 | 3.632168e-08, 354 | -0.8287703, 355 | 0, 356 | -0.22396657, 357 | 0.96279305, 358 | 0.15122315, 359 | 0, 360 | 0.7979343, 361 | 0.27023965, 362 | -0.5387684, 363 | 0, 364 | 3045.9219, 365 | 784.2408, 366 | -2284.6611, 367 | 1 368 | ] 369 | }, 370 | { 371 | "date" : 760424461.758179, 372 | "title" : "Untitled", 373 | "transform" : [ 374 | -0.55958897, 375 | 2.4582832e-08, 376 | -0.8287702, 377 | 0, 378 | -0.22396655, 379 | 0.9627931, 380 | 0.15122315, 381 | 0, 382 | 0.7979343, 383 | 0.27023965, 384 | -0.5387685, 385 | 0, 386 | 186.8855, 387 | 60.14001, 388 | -123.41817, 389 | 1 390 | ] 391 | } 392 | ] 393 | }, 394 | "sceneViewportBackgroundColors" : { 395 | 396 | }, 397 | "selectedSceneRelativePath" : "Untitled Scene.usda" 398 | } -------------------------------------------------------------------------------- /Packages/RealityKitContent/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:6.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "RealityKitContent", 8 | platforms: [ 9 | .visionOS(.v2), 10 | .macOS(.v15), 11 | .iOS(.v18) 12 | ], 13 | products: [ 14 | // Products define the executables and libraries a package produces, and make them visible to other packages. 15 | .library( 16 | name: "RealityKitContent", 17 | targets: ["RealityKitContent"]), 18 | ], 19 | dependencies: [ 20 | // Dependencies declare other packages that this package depends on. 21 | // .package(url: /* package url */, from: "1.0.0"), 22 | ], 23 | targets: [ 24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 25 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 26 | .target( 27 | name: "RealityKitContent", 28 | dependencies: []), 29 | ] 30 | ) -------------------------------------------------------------------------------- /Packages/RealityKitContent/README.md: -------------------------------------------------------------------------------- 1 | # RealityKitContent 2 | 3 | A description of this package. -------------------------------------------------------------------------------- /Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Drone.usdz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TAATHub/RealityKitPerspectiveCamera/f6495acd5383efaf119e7224ebe78e12773f129c/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Drone.usdz -------------------------------------------------------------------------------- /Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Low_Poly_Nature.usdz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TAATHub/RealityKitPerspectiveCamera/f6495acd5383efaf119e7224ebe78e12773f129c/Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Low_Poly_Nature.usdz -------------------------------------------------------------------------------- /Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Materials/GridMaterial.usda: -------------------------------------------------------------------------------- 1 | #usda 1.0 2 | ( 3 | defaultPrim = "Root" 4 | metersPerUnit = 1 5 | upAxis = "Y" 6 | ) 7 | 8 | def Xform "Root" 9 | { 10 | def Material "GridMaterial" 11 | { 12 | reorder nameChildren = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "DefaultSurfaceShader", "MaterialXPreviewSurface", "Texcoord", "Add", "Multiply", "Fractional", "LineCounts", "Multiply_1", "Separate2", "Separate2_1", "Ifgreater", "Ifgreater_1", "Max", "Background_Color"] 13 | token outputs:mtlx:surface.connect = 14 | token outputs:realitykit:vertex 15 | token outputs:surface 16 | float2 ui:nodegraph:realitykit:subgraphOutputs:pos = (2222, 300.5) 17 | float2 ui:nodegraph:realitykit:subgraphOutputs:size = (182, 89) 18 | int ui:nodegraph:realitykit:subgraphOutputs:stackingOrder = 749 19 | 20 | def Shader "DefaultSurfaceShader" 21 | { 22 | uniform token info:id = "UsdPreviewSurface" 23 | color3f inputs:diffuseColor = (1, 1, 1) 24 | float inputs:roughness = 0.75 25 | token outputs:surface 26 | } 27 | 28 | def Shader "MaterialXPreviewSurface" 29 | { 30 | uniform token info:id = "ND_UsdPreviewSurface_surfaceshader" 31 | float inputs:clearcoat 32 | float inputs:clearcoatRoughness 33 | color3f inputs:diffuseColor.connect = 34 | color3f inputs:emissiveColor 35 | float inputs:ior 36 | float inputs:metallic = 0.15 37 | float3 inputs:normal 38 | float inputs:occlusion 39 | float inputs:opacity 40 | float inputs:opacityThreshold 41 | float inputs:roughness = 0.5 42 | token outputs:out 43 | float2 ui:nodegraph:node:pos = (1967, 300.5) 44 | float2 ui:nodegraph:node:size = (208, 297) 45 | int ui:nodegraph:node:stackingOrder = 870 46 | string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["Advanced"] 47 | } 48 | 49 | def Shader "Texcoord" 50 | { 51 | uniform token info:id = "ND_texcoord_vector2" 52 | float2 outputs:out 53 | float2 ui:nodegraph:node:pos = (94.14453, 35.29297) 54 | float2 ui:nodegraph:node:size = (182, 43) 55 | int ui:nodegraph:node:stackingOrder = 1358 56 | } 57 | 58 | def Shader "Multiply" 59 | { 60 | uniform token info:id = "ND_multiply_vector2" 61 | float2 inputs:in1.connect = 62 | float2 inputs:in2 = (32, 15) 63 | float2 inputs:in2.connect = 64 | float2 outputs:out 65 | float2 ui:nodegraph:node:pos = (275.64453, 47.29297) 66 | float2 ui:nodegraph:node:size = (61, 36) 67 | int ui:nodegraph:node:stackingOrder = 1348 68 | string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["inputs:in2"] 69 | } 70 | 71 | def Shader "Fractional" 72 | { 73 | uniform token info:id = "ND_realitykit_fractional_vector2" 74 | float2 inputs:in.connect = 75 | float2 outputs:out 76 | float2 ui:nodegraph:node:pos = (440.5, 49.5) 77 | float2 ui:nodegraph:node:size = (155, 99) 78 | int ui:nodegraph:node:stackingOrder = 1345 79 | } 80 | 81 | def Shader "BaseColor" 82 | { 83 | uniform token info:id = "ND_constant_color3" 84 | color3f inputs:value = (0.89737034, 0.89737034, 0.89737034) ( 85 | colorSpace = "Input - Texture - sRGB - sRGB" 86 | ) 87 | color3f inputs:value.connect = None 88 | color3f outputs:out 89 | float2 ui:nodegraph:node:pos = (1537.5977, 363.07812) 90 | float2 ui:nodegraph:node:size = (150, 43) 91 | int ui:nodegraph:node:stackingOrder = 1353 92 | } 93 | 94 | def Shader "LineColor" 95 | { 96 | uniform token info:id = "ND_constant_color3" 97 | color3f inputs:value = (0.55945957, 0.55945957, 0.55945957) ( 98 | colorSpace = "Input - Texture - sRGB - sRGB" 99 | ) 100 | color3f inputs:value.connect = None 101 | color3f outputs:out 102 | float2 ui:nodegraph:node:pos = (1536.9844, 287.86328) 103 | float2 ui:nodegraph:node:size = (146, 43) 104 | int ui:nodegraph:node:stackingOrder = 1355 105 | } 106 | 107 | def Shader "LineWidths" 108 | { 109 | uniform token info:id = "ND_combine2_vector2" 110 | float inputs:in1 = 0.1 111 | float inputs:in2 = 0.1 112 | float2 outputs:out 113 | float2 ui:nodegraph:node:pos = (443.64453, 233.79297) 114 | float2 ui:nodegraph:node:size = (151, 43) 115 | int ui:nodegraph:node:stackingOrder = 1361 116 | } 117 | 118 | def Shader "LineCounts" 119 | { 120 | uniform token info:id = "ND_combine2_vector2" 121 | float inputs:in1 = 24 122 | float inputs:in2 = 12 123 | float2 outputs:out 124 | float2 ui:nodegraph:node:pos = (94.14453, 138.29297) 125 | float2 ui:nodegraph:node:size = (153, 43) 126 | int ui:nodegraph:node:stackingOrder = 1359 127 | } 128 | 129 | def Shader "Remap" 130 | { 131 | uniform token info:id = "ND_remap_color3" 132 | color3f inputs:in.connect = 133 | color3f inputs:inhigh.connect = None 134 | color3f inputs:inlow.connect = None 135 | color3f inputs:outhigh.connect = 136 | color3f inputs:outlow.connect = 137 | color3f outputs:out 138 | float2 ui:nodegraph:node:pos = (1755.5, 300.5) 139 | float2 ui:nodegraph:node:size = (95, 171) 140 | int ui:nodegraph:node:stackingOrder = 1282 141 | string[] ui:nodegraph:realitykit:node:attributesShowingChildren = ["inputs:outlow"] 142 | } 143 | 144 | def Shader "Separate2" 145 | { 146 | uniform token info:id = "ND_separate2_vector2" 147 | float2 inputs:in.connect = 148 | float outputs:outx 149 | float outputs:outy 150 | float2 ui:nodegraph:node:pos = (1212.6445, 128.91797) 151 | float2 ui:nodegraph:node:size = (116, 117) 152 | int ui:nodegraph:node:stackingOrder = 1363 153 | } 154 | 155 | def Shader "Combine3" 156 | { 157 | uniform token info:id = "ND_combine3_color3" 158 | float inputs:in1.connect = 159 | float inputs:in2.connect = 160 | float inputs:in3.connect = 161 | color3f outputs:out 162 | float2 ui:nodegraph:node:pos = (1578.1445, 128.91797) 163 | float2 ui:nodegraph:node:size = (146, 54) 164 | int ui:nodegraph:node:stackingOrder = 1348 165 | } 166 | 167 | def Shader "Range" 168 | { 169 | uniform token info:id = "ND_range_vector2" 170 | bool inputs:doclamp = 1 171 | float2 inputs:gamma = (2, 2) 172 | float2 inputs:in.connect = 173 | float2 inputs:inhigh.connect = 174 | float2 inputs:inlow = (0.02, 0.02) 175 | float2 inputs:outhigh 176 | float2 inputs:outlow 177 | float2 outputs:out 178 | float2 ui:nodegraph:node:pos = (990.64453, 128.91797) 179 | float2 ui:nodegraph:node:size = (98, 207) 180 | int ui:nodegraph:node:stackingOrder = 1364 181 | } 182 | 183 | def Shader "Subtract" 184 | { 185 | uniform token info:id = "ND_subtract_vector2" 186 | float2 inputs:in1.connect = 187 | float2 inputs:in2.connect = 188 | float2 outputs:out 189 | float2 ui:nodegraph:node:pos = (612.64453, 87.04297) 190 | float2 ui:nodegraph:node:size = (63, 36) 191 | int ui:nodegraph:node:stackingOrder = 1348 192 | } 193 | 194 | def Shader "Absval" 195 | { 196 | uniform token info:id = "ND_absval_vector2" 197 | float2 inputs:in.connect = 198 | float2 outputs:out 199 | float2 ui:nodegraph:node:pos = (765.64453, 87.04297) 200 | float2 ui:nodegraph:node:size = (123, 43) 201 | int ui:nodegraph:node:stackingOrder = 1348 202 | } 203 | 204 | def Shader "Min" 205 | { 206 | uniform token info:id = "ND_min_float" 207 | float inputs:in1.connect = 208 | float inputs:in2.connect = 209 | float outputs:out 210 | float2 ui:nodegraph:node:pos = (1388.1445, 128.91797) 211 | float2 ui:nodegraph:node:size = (114, 36) 212 | int ui:nodegraph:node:stackingOrder = 1363 213 | } 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.rkassets/Scene.usda: -------------------------------------------------------------------------------- 1 | #usda 1.0 2 | ( 3 | customLayerData = { 4 | string creator = "Reality Composer Pro Version 2.0 (448.60.4)" 5 | } 6 | defaultPrim = "Root" 7 | metersPerUnit = 1 8 | upAxis = "Y" 9 | ) 10 | 11 | def Xform "Root" 12 | { 13 | reorder nameChildren = ["SkyDome", "ToyBiplane", "ToyCar", "ToyRocket", "Camera"] 14 | def "Camera" ( 15 | active = false 16 | customData = { 17 | float3 rotationEulerHint = (0, 3.1415927, 0) 18 | } 19 | references = @DslrCamera.usdz@ 20 | ) 21 | { 22 | quatf xformOp:orient = (-4.371139e-8, 0, 1, 0) 23 | float3 xformOp:scale = (10, 10, 10) 24 | float3 xformOp:translate = (0.0000018514693, 0, 0) 25 | uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] 26 | } 27 | 28 | def "Low_Poly_Nature" ( 29 | active = true 30 | customData = { 31 | float3 rotationEulerHint = (0, 1.5707963, 0) 32 | } 33 | prepend references = @Low_Poly_Nature.usdz@ 34 | ) 35 | { 36 | quatf xformOp:orient = (0.70710677, 0, 0.70710677, 0) 37 | float3 xformOp:scale = (0.5, 0.5, 0.5) 38 | float3 xformOp:translate = (32.79717, -136.3, -14.06719) 39 | uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:orient", "xformOp:scale"] 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /Packages/RealityKitContent/Sources/RealityKitContent/RealityKitContent.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Bundle for the RealityKitContent project 4 | public let realityKitContentBundle = Bundle.module 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This sample code display a view on visionOS SwiftUI 2D window, which is rendered from a perspective camera using RealityRenderer. 2 | 3 | https://github.com/user-attachments/assets/e4986f5e-867d-4d92-a8c8-2e57750048a1 4 | 5 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9A8E37492D4C630E0035E316 /* RealityKitContent in Frameworks */ = {isa = PBXBuildFile; productRef = 9A8E37482D4C630E0035E316 /* RealityKitContent */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXContainerItemProxy section */ 14 | 9A8E375F2D4C63100035E316 /* PBXContainerItemProxy */ = { 15 | isa = PBXContainerItemProxy; 16 | containerPortal = 9A8E373B2D4C630E0035E316 /* Project object */; 17 | proxyType = 1; 18 | remoteGlobalIDString = 9A8E37422D4C630E0035E316; 19 | remoteInfo = RealityKitPerspectiveCamera; 20 | }; 21 | /* End PBXContainerItemProxy section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | 9A8E37432D4C630E0035E316 /* RealityKitPerspectiveCamera.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RealityKitPerspectiveCamera.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | 9A8E37472D4C630E0035E316 /* RealityKitContent */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = RealityKitContent; sourceTree = ""; }; 26 | 9A8E375E2D4C63100035E316 /* RealityKitPerspectiveCameraTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealityKitPerspectiveCameraTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ 30 | 9A8E37642D4C63100035E316 /* Exceptions for "RealityKitPerspectiveCamera" folder in "RealityKitPerspectiveCamera" target */ = { 31 | isa = PBXFileSystemSynchronizedBuildFileExceptionSet; 32 | membershipExceptions = ( 33 | Info.plist, 34 | ); 35 | target = 9A8E37422D4C630E0035E316 /* RealityKitPerspectiveCamera */; 36 | }; 37 | /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ 38 | 39 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 40 | 9A8E37452D4C630E0035E316 /* RealityKitPerspectiveCamera */ = { 41 | isa = PBXFileSystemSynchronizedRootGroup; 42 | exceptions = ( 43 | 9A8E37642D4C63100035E316 /* Exceptions for "RealityKitPerspectiveCamera" folder in "RealityKitPerspectiveCamera" target */, 44 | ); 45 | path = RealityKitPerspectiveCamera; 46 | sourceTree = ""; 47 | }; 48 | 9A8E37612D4C63100035E316 /* RealityKitPerspectiveCameraTests */ = { 49 | isa = PBXFileSystemSynchronizedRootGroup; 50 | path = RealityKitPerspectiveCameraTests; 51 | sourceTree = ""; 52 | }; 53 | /* End PBXFileSystemSynchronizedRootGroup section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 9A8E37402D4C630E0035E316 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | 9A8E37492D4C630E0035E316 /* RealityKitContent in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | 9A8E375B2D4C63100035E316 /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 9A8E373A2D4C630E0035E316 = { 75 | isa = PBXGroup; 76 | children = ( 77 | 9A8E37452D4C630E0035E316 /* RealityKitPerspectiveCamera */, 78 | 9A8E37462D4C630E0035E316 /* Packages */, 79 | 9A8E37612D4C63100035E316 /* RealityKitPerspectiveCameraTests */, 80 | 9A8E37442D4C630E0035E316 /* Products */, 81 | ); 82 | sourceTree = ""; 83 | }; 84 | 9A8E37442D4C630E0035E316 /* Products */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 9A8E37432D4C630E0035E316 /* RealityKitPerspectiveCamera.app */, 88 | 9A8E375E2D4C63100035E316 /* RealityKitPerspectiveCameraTests.xctest */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | 9A8E37462D4C630E0035E316 /* Packages */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 9A8E37472D4C630E0035E316 /* RealityKitContent */, 97 | ); 98 | path = Packages; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 9A8E37422D4C630E0035E316 /* RealityKitPerspectiveCamera */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 9A8E37652D4C63100035E316 /* Build configuration list for PBXNativeTarget "RealityKitPerspectiveCamera" */; 107 | buildPhases = ( 108 | 9A8E373F2D4C630E0035E316 /* Sources */, 109 | 9A8E37402D4C630E0035E316 /* Frameworks */, 110 | 9A8E37412D4C630E0035E316 /* Resources */, 111 | ); 112 | buildRules = ( 113 | ); 114 | dependencies = ( 115 | ); 116 | fileSystemSynchronizedGroups = ( 117 | 9A8E37452D4C630E0035E316 /* RealityKitPerspectiveCamera */, 118 | ); 119 | name = RealityKitPerspectiveCamera; 120 | packageProductDependencies = ( 121 | 9A8E37482D4C630E0035E316 /* RealityKitContent */, 122 | ); 123 | productName = RealityKitPerspectiveCamera; 124 | productReference = 9A8E37432D4C630E0035E316 /* RealityKitPerspectiveCamera.app */; 125 | productType = "com.apple.product-type.application"; 126 | }; 127 | 9A8E375D2D4C63100035E316 /* RealityKitPerspectiveCameraTests */ = { 128 | isa = PBXNativeTarget; 129 | buildConfigurationList = 9A8E376A2D4C63100035E316 /* Build configuration list for PBXNativeTarget "RealityKitPerspectiveCameraTests" */; 130 | buildPhases = ( 131 | 9A8E375A2D4C63100035E316 /* Sources */, 132 | 9A8E375B2D4C63100035E316 /* Frameworks */, 133 | 9A8E375C2D4C63100035E316 /* Resources */, 134 | ); 135 | buildRules = ( 136 | ); 137 | dependencies = ( 138 | 9A8E37602D4C63100035E316 /* PBXTargetDependency */, 139 | ); 140 | fileSystemSynchronizedGroups = ( 141 | 9A8E37612D4C63100035E316 /* RealityKitPerspectiveCameraTests */, 142 | ); 143 | name = RealityKitPerspectiveCameraTests; 144 | packageProductDependencies = ( 145 | ); 146 | productName = RealityKitPerspectiveCameraTests; 147 | productReference = 9A8E375E2D4C63100035E316 /* RealityKitPerspectiveCameraTests.xctest */; 148 | productType = "com.apple.product-type.bundle.unit-test"; 149 | }; 150 | /* End PBXNativeTarget section */ 151 | 152 | /* Begin PBXProject section */ 153 | 9A8E373B2D4C630E0035E316 /* Project object */ = { 154 | isa = PBXProject; 155 | attributes = { 156 | BuildIndependentTargetsInParallel = 1; 157 | LastSwiftUpdateCheck = 1620; 158 | LastUpgradeCheck = 1620; 159 | TargetAttributes = { 160 | 9A8E37422D4C630E0035E316 = { 161 | CreatedOnToolsVersion = 16.2; 162 | }; 163 | 9A8E375D2D4C63100035E316 = { 164 | CreatedOnToolsVersion = 16.2; 165 | TestTargetID = 9A8E37422D4C630E0035E316; 166 | }; 167 | }; 168 | }; 169 | buildConfigurationList = 9A8E373E2D4C630E0035E316 /* Build configuration list for PBXProject "RealityKitPerspectiveCamera" */; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = 9A8E373A2D4C630E0035E316; 177 | minimizedProjectReferenceProxies = 1; 178 | preferredProjectObjectVersion = 77; 179 | productRefGroup = 9A8E37442D4C630E0035E316 /* Products */; 180 | projectDirPath = ""; 181 | projectRoot = ""; 182 | targets = ( 183 | 9A8E37422D4C630E0035E316 /* RealityKitPerspectiveCamera */, 184 | 9A8E375D2D4C63100035E316 /* RealityKitPerspectiveCameraTests */, 185 | ); 186 | }; 187 | /* End PBXProject section */ 188 | 189 | /* Begin PBXResourcesBuildPhase section */ 190 | 9A8E37412D4C630E0035E316 /* Resources */ = { 191 | isa = PBXResourcesBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | 9A8E375C2D4C63100035E316 /* Resources */ = { 198 | isa = PBXResourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | ); 202 | runOnlyForDeploymentPostprocessing = 0; 203 | }; 204 | /* End PBXResourcesBuildPhase section */ 205 | 206 | /* Begin PBXSourcesBuildPhase section */ 207 | 9A8E373F2D4C630E0035E316 /* Sources */ = { 208 | isa = PBXSourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | 9A8E375A2D4C63100035E316 /* Sources */ = { 215 | isa = PBXSourcesBuildPhase; 216 | buildActionMask = 2147483647; 217 | files = ( 218 | ); 219 | runOnlyForDeploymentPostprocessing = 0; 220 | }; 221 | /* End PBXSourcesBuildPhase section */ 222 | 223 | /* Begin PBXTargetDependency section */ 224 | 9A8E37602D4C63100035E316 /* PBXTargetDependency */ = { 225 | isa = PBXTargetDependency; 226 | target = 9A8E37422D4C630E0035E316 /* RealityKitPerspectiveCamera */; 227 | targetProxy = 9A8E375F2D4C63100035E316 /* PBXContainerItemProxy */; 228 | }; 229 | /* End PBXTargetDependency section */ 230 | 231 | /* Begin XCBuildConfiguration section */ 232 | 9A8E37662D4C63100035E316 /* Debug */ = { 233 | isa = XCBuildConfiguration; 234 | buildSettings = { 235 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 236 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 237 | CODE_SIGN_STYLE = Automatic; 238 | CURRENT_PROJECT_VERSION = 1; 239 | DEVELOPMENT_ASSET_PATHS = "\"RealityKitPerspectiveCamera/Preview Content\""; 240 | ENABLE_PREVIEWS = YES; 241 | GENERATE_INFOPLIST_FILE = YES; 242 | INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist"; 243 | LD_RUNPATH_SEARCH_PATHS = ( 244 | "$(inherited)", 245 | "@executable_path/Frameworks", 246 | ); 247 | MARKETING_VERSION = 1.0; 248 | PRODUCT_BUNDLE_IDENTIFIER = com.TAATHub.RealityKitPerspectiveCamera; 249 | PRODUCT_NAME = "$(TARGET_NAME)"; 250 | SUPPORTED_PLATFORMS = "xros xrsimulator"; 251 | SWIFT_EMIT_LOC_STRINGS = YES; 252 | SWIFT_VERSION = 5.0; 253 | TARGETED_DEVICE_FAMILY = 7; 254 | }; 255 | name = Debug; 256 | }; 257 | 9A8E37672D4C63100035E316 /* Release */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 261 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 262 | CODE_SIGN_STYLE = Automatic; 263 | CURRENT_PROJECT_VERSION = 1; 264 | DEVELOPMENT_ASSET_PATHS = "\"RealityKitPerspectiveCamera/Preview Content\""; 265 | ENABLE_PREVIEWS = YES; 266 | GENERATE_INFOPLIST_FILE = YES; 267 | INFOPLIST_FILE = "$(TARGET_NAME)/Info.plist"; 268 | LD_RUNPATH_SEARCH_PATHS = ( 269 | "$(inherited)", 270 | "@executable_path/Frameworks", 271 | ); 272 | MARKETING_VERSION = 1.0; 273 | PRODUCT_BUNDLE_IDENTIFIER = com.TAATHub.RealityKitPerspectiveCamera; 274 | PRODUCT_NAME = "$(TARGET_NAME)"; 275 | SUPPORTED_PLATFORMS = "xros xrsimulator"; 276 | SWIFT_EMIT_LOC_STRINGS = YES; 277 | SWIFT_VERSION = 5.0; 278 | TARGETED_DEVICE_FAMILY = 7; 279 | }; 280 | name = Release; 281 | }; 282 | 9A8E37682D4C63100035E316 /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | ALWAYS_SEARCH_USER_PATHS = NO; 286 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 287 | CLANG_ANALYZER_NONNULL = YES; 288 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 289 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 290 | CLANG_ENABLE_MODULES = YES; 291 | CLANG_ENABLE_OBJC_ARC = YES; 292 | CLANG_ENABLE_OBJC_WEAK = YES; 293 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 294 | CLANG_WARN_BOOL_CONVERSION = YES; 295 | CLANG_WARN_COMMA = YES; 296 | CLANG_WARN_CONSTANT_CONVERSION = YES; 297 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 299 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 300 | CLANG_WARN_EMPTY_BODY = YES; 301 | CLANG_WARN_ENUM_CONVERSION = YES; 302 | CLANG_WARN_INFINITE_RECURSION = YES; 303 | CLANG_WARN_INT_CONVERSION = YES; 304 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 305 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 306 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 307 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 308 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 309 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 310 | CLANG_WARN_STRICT_PROTOTYPES = YES; 311 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 312 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 313 | CLANG_WARN_UNREACHABLE_CODE = YES; 314 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 315 | COPY_PHASE_STRIP = NO; 316 | DEBUG_INFORMATION_FORMAT = dwarf; 317 | ENABLE_STRICT_OBJC_MSGSEND = YES; 318 | ENABLE_TESTABILITY = YES; 319 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 320 | GCC_C_LANGUAGE_STANDARD = gnu17; 321 | GCC_DYNAMIC_NO_PIC = NO; 322 | GCC_NO_COMMON_BLOCKS = YES; 323 | GCC_OPTIMIZATION_LEVEL = 0; 324 | GCC_PREPROCESSOR_DEFINITIONS = ( 325 | "DEBUG=1", 326 | "$(inherited)", 327 | ); 328 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 329 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 330 | GCC_WARN_UNDECLARED_SELECTOR = YES; 331 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 332 | GCC_WARN_UNUSED_FUNCTION = YES; 333 | GCC_WARN_UNUSED_VARIABLE = YES; 334 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 335 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 336 | MTL_FAST_MATH = YES; 337 | ONLY_ACTIVE_ARCH = YES; 338 | SDKROOT = xros; 339 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 340 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 341 | XROS_DEPLOYMENT_TARGET = 2.2; 342 | }; 343 | name = Debug; 344 | }; 345 | 9A8E37692D4C63100035E316 /* Release */ = { 346 | isa = XCBuildConfiguration; 347 | buildSettings = { 348 | ALWAYS_SEARCH_USER_PATHS = NO; 349 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 350 | CLANG_ANALYZER_NONNULL = YES; 351 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 352 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 353 | CLANG_ENABLE_MODULES = YES; 354 | CLANG_ENABLE_OBJC_ARC = YES; 355 | CLANG_ENABLE_OBJC_WEAK = YES; 356 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 357 | CLANG_WARN_BOOL_CONVERSION = YES; 358 | CLANG_WARN_COMMA = YES; 359 | CLANG_WARN_CONSTANT_CONVERSION = YES; 360 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 363 | CLANG_WARN_EMPTY_BODY = YES; 364 | CLANG_WARN_ENUM_CONVERSION = YES; 365 | CLANG_WARN_INFINITE_RECURSION = YES; 366 | CLANG_WARN_INT_CONVERSION = YES; 367 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 368 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 369 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 370 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 371 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 372 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 373 | CLANG_WARN_STRICT_PROTOTYPES = YES; 374 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 375 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 376 | CLANG_WARN_UNREACHABLE_CODE = YES; 377 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 378 | COPY_PHASE_STRIP = NO; 379 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 380 | ENABLE_NS_ASSERTIONS = NO; 381 | ENABLE_STRICT_OBJC_MSGSEND = YES; 382 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 383 | GCC_C_LANGUAGE_STANDARD = gnu17; 384 | GCC_NO_COMMON_BLOCKS = YES; 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 392 | MTL_ENABLE_DEBUG_INFO = NO; 393 | MTL_FAST_MATH = YES; 394 | SDKROOT = xros; 395 | SWIFT_COMPILATION_MODE = wholemodule; 396 | VALIDATE_PRODUCT = YES; 397 | XROS_DEPLOYMENT_TARGET = 2.2; 398 | }; 399 | name = Release; 400 | }; 401 | 9A8E376B2D4C63100035E316 /* Debug */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | BUNDLE_LOADER = "$(TEST_HOST)"; 405 | CODE_SIGN_STYLE = Automatic; 406 | CURRENT_PROJECT_VERSION = 1; 407 | GENERATE_INFOPLIST_FILE = YES; 408 | MARKETING_VERSION = 1.0; 409 | PRODUCT_BUNDLE_IDENTIFIER = com.TAATHub.RealityKitPerspectiveCameraTests; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | SUPPORTED_PLATFORMS = "xros xrsimulator"; 412 | SWIFT_EMIT_LOC_STRINGS = NO; 413 | SWIFT_VERSION = 5.0; 414 | TARGETED_DEVICE_FAMILY = 7; 415 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RealityKitPerspectiveCamera.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/RealityKitPerspectiveCamera"; 416 | XROS_DEPLOYMENT_TARGET = 2.2; 417 | }; 418 | name = Debug; 419 | }; 420 | 9A8E376C2D4C63100035E316 /* Release */ = { 421 | isa = XCBuildConfiguration; 422 | buildSettings = { 423 | BUNDLE_LOADER = "$(TEST_HOST)"; 424 | CODE_SIGN_STYLE = Automatic; 425 | CURRENT_PROJECT_VERSION = 1; 426 | GENERATE_INFOPLIST_FILE = YES; 427 | MARKETING_VERSION = 1.0; 428 | PRODUCT_BUNDLE_IDENTIFIER = com.TAATHub.RealityKitPerspectiveCameraTests; 429 | PRODUCT_NAME = "$(TARGET_NAME)"; 430 | SUPPORTED_PLATFORMS = "xros xrsimulator"; 431 | SWIFT_EMIT_LOC_STRINGS = NO; 432 | SWIFT_VERSION = 5.0; 433 | TARGETED_DEVICE_FAMILY = 7; 434 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RealityKitPerspectiveCamera.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/RealityKitPerspectiveCamera"; 435 | XROS_DEPLOYMENT_TARGET = 2.2; 436 | }; 437 | name = Release; 438 | }; 439 | /* End XCBuildConfiguration section */ 440 | 441 | /* Begin XCConfigurationList section */ 442 | 9A8E373E2D4C630E0035E316 /* Build configuration list for PBXProject "RealityKitPerspectiveCamera" */ = { 443 | isa = XCConfigurationList; 444 | buildConfigurations = ( 445 | 9A8E37682D4C63100035E316 /* Debug */, 446 | 9A8E37692D4C63100035E316 /* Release */, 447 | ); 448 | defaultConfigurationIsVisible = 0; 449 | defaultConfigurationName = Release; 450 | }; 451 | 9A8E37652D4C63100035E316 /* Build configuration list for PBXNativeTarget "RealityKitPerspectiveCamera" */ = { 452 | isa = XCConfigurationList; 453 | buildConfigurations = ( 454 | 9A8E37662D4C63100035E316 /* Debug */, 455 | 9A8E37672D4C63100035E316 /* Release */, 456 | ); 457 | defaultConfigurationIsVisible = 0; 458 | defaultConfigurationName = Release; 459 | }; 460 | 9A8E376A2D4C63100035E316 /* Build configuration list for PBXNativeTarget "RealityKitPerspectiveCameraTests" */ = { 461 | isa = XCConfigurationList; 462 | buildConfigurations = ( 463 | 9A8E376B2D4C63100035E316 /* Debug */, 464 | 9A8E376C2D4C63100035E316 /* Release */, 465 | ); 466 | defaultConfigurationIsVisible = 0; 467 | defaultConfigurationName = Release; 468 | }; 469 | /* End XCConfigurationList section */ 470 | 471 | /* Begin XCSwiftPackageProductDependency section */ 472 | 9A8E37482D4C630E0035E316 /* RealityKitContent */ = { 473 | isa = XCSwiftPackageProductDependency; 474 | productName = RealityKitContent; 475 | }; 476 | /* End XCSwiftPackageProductDependency section */ 477 | }; 478 | rootObject = 9A8E373B2D4C630E0035E316 /* Project object */; 479 | } 480 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "vision", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/AppIcon.solidimagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.solidimagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.solidimagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.solidimagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "vision", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "vision", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/Skybox.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "panorama_image.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Assets.xcassets/Skybox.imageset/panorama_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TAATHub/RealityKitPerspectiveCamera/f6495acd5383efaf119e7224ebe78e12773f129c/RealityKitPerspectiveCamera/Assets.xcassets/Skybox.imageset/panorama_image.png -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Data/ControlParameter.swift: -------------------------------------------------------------------------------- 1 | import Observation 2 | 3 | @Observable 4 | class ControlParameter { 5 | var forward: Double = 0 6 | var up: Double = 0 7 | var rotation: Double = 0 8 | } 9 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/ECS/DroneControl.swift: -------------------------------------------------------------------------------- 1 | import RealityKit 2 | 3 | struct DroneControlComponent: Component { 4 | init () { 5 | DroneControlSystem.registerSystem() 6 | } 7 | } 8 | 9 | final class DroneControlSystem: System { 10 | private let query = EntityQuery(where: .has(DroneControlComponent.self)) 11 | private let speed = 1.0 12 | 13 | init(scene: Scene) {} 14 | 15 | func update(context: SceneUpdateContext) { 16 | let entities = context.entities(matching: query, updatingSystemWhen: .rendering) 17 | for entity in entities { 18 | var rotation = entity.transform.rotation 19 | rotation *= simd_quatf(angle: Float(AppModel.shared.controlParameter.rotation * context.deltaTime * speed), 20 | axis: .init(x: 0, y: 1, z: 0)) 21 | entity.transform.rotation = rotation 22 | 23 | let localTranslation = SIMD3(x: 0, 24 | y: Float(AppModel.shared.controlParameter.up * context.deltaTime * speed), 25 | z: Float(AppModel.shared.controlParameter.forward * context.deltaTime * speed)) 26 | let translation = entity.transform.matrix * SIMD4(localTranslation, 0) 27 | entity.transform.translation += translation.xyz 28 | 29 | AppModel.shared.cameraTransform = entity.transform 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Extensions/Entity+Extension.swift: -------------------------------------------------------------------------------- 1 | import RealityFoundation 2 | 3 | extension Entity { 4 | static func createSkybox(name: String) -> Entity? { 5 | let sphere = MeshResource.generateSphere(radius: 1000) 6 | var material = UnlitMaterial() 7 | 8 | do { 9 | let texture = try TextureResource.load(named: name) 10 | material.color = .init(texture: .init(texture)) 11 | 12 | let entity = Entity() 13 | entity.components.set(ModelComponent(mesh: sphere, materials: [material])) 14 | entity.scale = .init(x: -1, y: 1, z: 1) 15 | return entity 16 | } catch { 17 | return nil 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Extensions/MTLTexture+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CoreGraphics 3 | import MetalKit 4 | import Accelerate 5 | 6 | // https://gist.github.com/codelynx/4e56758fb89e94d0d1a58b40ddaade45 7 | extension MTLTexture { 8 | var cgImage: CGImage? { 9 | assert(self.pixelFormat == .bgra8Unorm) 10 | 11 | // read texture as byte array 12 | let rowBytes = self.width * 4 13 | let length = rowBytes * self.height 14 | let bgraBytes = [UInt8](repeating: 0, count: length) 15 | let region = MTLRegionMake2D(0, 0, self.width, self.height) 16 | self.getBytes(UnsafeMutableRawPointer(mutating: bgraBytes), bytesPerRow: rowBytes, from: region, mipmapLevel: 0) 17 | 18 | // use Accelerate framework to convert from BGRA to RGBA 19 | var bgraBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: bgraBytes), 20 | height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes) 21 | let rgbaBytes = [UInt8](repeating: 0, count: length) 22 | var rgbaBuffer = vImage_Buffer(data: UnsafeMutableRawPointer(mutating: rgbaBytes), 23 | height: vImagePixelCount(self.height), width: vImagePixelCount(self.width), rowBytes: rowBytes) 24 | let map: [UInt8] = [2, 1, 0, 3] 25 | vImagePermuteChannels_ARGB8888(&bgraBuffer, &rgbaBuffer, map, 0) 26 | 27 | // create CGImage with RGBA 28 | let colorScape = CGColorSpaceCreateDeviceRGB() 29 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) 30 | guard let data = CFDataCreate(nil, rgbaBytes, length) else { return nil } 31 | guard let dataProvider = CGDataProvider(data: data) else { return nil } 32 | let cgImage = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes, 33 | space: colorScape, bitmapInfo: bitmapInfo, provider: dataProvider, 34 | decode: nil, shouldInterpolate: true, intent: .defaultIntent) 35 | return cgImage 36 | } 37 | 38 | var image: UIImage? { 39 | guard let cgImage = self.cgImage else { return nil } 40 | return UIImage(cgImage: cgImage) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Extensions/SIMD+Extension.swift: -------------------------------------------------------------------------------- 1 | import simd 2 | 3 | extension SIMD3 { 4 | 5 | static func distance(_ first: SIMD3, _ second: SIMD3) -> Float { 6 | (first - second).length() 7 | } 8 | 9 | static func dot(_ first: SIMD3, _ second: SIMD3) -> Float { 10 | first.x * second.x + first.y * second.y + first.z * second.z 11 | } 12 | 13 | static var upward: SIMD3 { 14 | SIMD3(0, 1, 0) 15 | } 16 | 17 | static var down: SIMD3 { 18 | SIMD3(0, -1, 0) 19 | } 20 | 21 | static var left: SIMD3 { 22 | SIMD3(1, 0, 0) 23 | } 24 | 25 | static var right: SIMD3 { 26 | SIMD3(-1, 0, 0) 27 | } 28 | 29 | static var forward: SIMD3 { 30 | SIMD3(0, 0, 1) 31 | } 32 | 33 | static var back: SIMD3 { 34 | SIMD3(0, 0, -1) 35 | } 36 | 37 | static func normalizeIfNonZero(_ vector: SIMD3) -> SIMD3 { 38 | return simd_length(vector) > 0 ? normalize(vector) : vector 39 | } 40 | 41 | init(_ float4: SIMD4) { 42 | self.init() 43 | 44 | x = float4.x 45 | y = float4.y 46 | z = float4.z 47 | } 48 | 49 | func length() -> Float { 50 | sqrt(x * x + y * y + z * z) 51 | } 52 | 53 | func normalized() -> SIMD3 { 54 | self * 1 / length() 55 | } 56 | 57 | func setX(_ value: Float) -> SIMD3 { 58 | SIMD3(value, y, z) 59 | } 60 | 61 | func setY(_ value: Float) -> SIMD3 { 62 | SIMD3(x, value, z) 63 | } 64 | 65 | func setZ(_ value: Float) -> SIMD3 { 66 | SIMD3(x, y, value) 67 | } 68 | } 69 | 70 | extension SIMD4 { 71 | var xyz: SIMD3 { 72 | [x, y, z] 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationPreferredDefaultSceneSessionRole 8 | UIWindowSceneSessionRoleApplication 9 | UIApplicationSupportsMultipleScenes 10 | 11 | UISceneConfigurations 12 | 13 | UISceneSessionRoleImmersiveSpaceApplication 14 | 15 | 16 | UISceneInitialImmersionStyle 17 | UIImmersionStyleFull 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Models/AppModel.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import RealityFoundation 3 | 4 | @MainActor 5 | @Observable 6 | class AppModel { 7 | static let shared = AppModel() 8 | 9 | var scene: Entity? 10 | var cameraTransform: Transform? 11 | var controlParameter: ControlParameter = .init() 12 | 13 | let immersiveSpaceID = "ImmersiveSpace" 14 | enum ImmersiveSpaceState { 15 | case closed 16 | case inTransition 17 | case open 18 | } 19 | var immersiveSpaceState = ImmersiveSpaceState.closed 20 | } 21 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Models/OffscreenRenderModel.swift: -------------------------------------------------------------------------------- 1 | import Observation 2 | import Metal 3 | import RealityFoundation 4 | import SwiftUI 5 | 6 | /// Model for rendering given scene using RealityRenderer 7 | /// 8 | /// - note: See [this thread](https://developer.apple.com/forums/thread/762238?answerId=801164022#801164022) 9 | @Observable 10 | @MainActor 11 | final class OffscreenRenderModel { 12 | var lowLevelTexture: LowLevelTexture? 13 | 14 | private var renderer: RealityRenderer? 15 | private var commandQueue: MTLCommandQueue? 16 | 17 | init() { 18 | lowLevelTexture = try? LowLevelTexture(descriptor: lowLevelTextureDescriptor) 19 | commandQueue = MTLCreateSystemDefaultDevice()?.makeCommandQueue() 20 | } 21 | 22 | var isRendererUpdated = false 23 | 24 | func setup(scene: Entity) throws { 25 | renderer = try RealityRenderer() 26 | guard let renderer else { return } 27 | 28 | // When the scene entity is added to RealityRenderer, it removes scene from the immersive space's content. 29 | // So we have to clone the scene entity recursively. 30 | // See this thread: https://developer.apple.com/forums/thread/773957 31 | renderer.entities.append(scene.clone(recursive: true)) 32 | 33 | let camera = PerspectiveCamera() 34 | renderer.activeCamera = camera 35 | renderer.entities.append(camera) 36 | } 37 | 38 | func render(position: SIMD3, orientation: simd_quatf) throws { 39 | guard let renderer, 40 | let commandBuffer = commandQueue?.makeCommandBuffer(), 41 | let colorTexture = lowLevelTexture?.replace(using: commandBuffer) else { return } 42 | 43 | renderer.activeCamera?.setPosition(position, relativeTo: nil) 44 | renderer.activeCamera?.setOrientation(orientation, relativeTo: nil) 45 | 46 | // Pass the colorTexture replaced from lowLevelTexture directly to RealityRenderer.CameraOutput 47 | let cameraOutputDesc = RealityRenderer.CameraOutput.Descriptor.singleProjection(colorTexture: colorTexture) 48 | let cameraOutput = try RealityRenderer.CameraOutput(cameraOutputDesc) 49 | try renderer.updateAndRender(deltaTime: 0.1, cameraOutput: cameraOutput) 50 | 51 | if !isRendererUpdated { 52 | isRendererUpdated = true 53 | } 54 | } 55 | 56 | // Descriptor for LowLevelTexture 57 | private var lowLevelTextureDescriptor: LowLevelTexture.Descriptor { 58 | var desc = LowLevelTexture.Descriptor() 59 | 60 | desc.textureType = .type2D 61 | desc.arrayLength = 1 62 | 63 | desc.width = 1600 64 | desc.height = 900 65 | desc.depth = 1 66 | 67 | desc.mipmapLevelCount = 1 68 | desc.pixelFormat = .bgra8Unorm 69 | desc.textureUsage = [.renderTarget] // Set the usage for rendering 70 | desc.swizzle = .init(red: .red, green: .green, blue: .blue, alpha: .alpha) 71 | 72 | return desc 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/RealityKitPerspectiveCameraApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct RealityKitPerspectiveCameraApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | .environment(AppModel.shared) 9 | } 10 | .windowResizability(.contentSize) 11 | 12 | ImmersiveSpace(id: AppModel.shared.immersiveSpaceID) { 13 | ImmersiveView() 14 | .environment(AppModel.shared) 15 | .onAppear { 16 | AppModel.shared.immersiveSpaceState = .open 17 | } 18 | .onDisappear { 19 | AppModel.shared.immersiveSpaceState = .closed 20 | } 21 | } 22 | .immersionStyle(selection: .constant(.full), in: .full) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Sunlight.skybox/Sunlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TAATHub/RealityKitPerspectiveCamera/f6495acd5383efaf119e7224ebe78e12773f129c/RealityKitPerspectiveCamera/Sunlight.skybox/Sunlight.png -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import RealityKit 3 | import RealityKitContent 4 | 5 | struct ContentView: View { 6 | @Environment(AppModel.self) private var appModel 7 | 8 | @Environment(\.openWindow) var openWindow 9 | @Environment(\.openImmersiveSpace) var openImmersiveSpace 10 | 11 | var body: some View { 12 | VStack(spacing: 40) { 13 | HStack(spacing: 40) { 14 | VStack(spacing: 0) { 15 | ControllerButton(imageName: "arrowtriangle.up.fill") { 16 | appModel.controlParameter.forward = $0 ? 1 : 0 17 | } 18 | 19 | HStack(spacing: 0) { 20 | ControllerButton(imageName: "arrowtriangle.left.fill") { 21 | appModel.controlParameter.rotation = $0 ? 1 : 0 22 | } 23 | 24 | Spacer() 25 | 26 | ControllerButton(imageName: "arrowtriangle.right.fill") { 27 | appModel.controlParameter.rotation = $0 ? -1 : 0 28 | } 29 | } 30 | 31 | ControllerButton(imageName: "arrowtriangle.down.fill") { 32 | appModel.controlParameter.forward = $0 ? -1 : 0 33 | } 34 | } 35 | .frame(width: 192) 36 | 37 | OffscreenRenderView() 38 | .aspectRatio(16/9, contentMode: .fit) 39 | 40 | VStack(spacing: 40) { 41 | ControllerButton(imageName: "arrowtriangle.up.fill") { 42 | appModel.controlParameter.up = $0 ? 1 : 0 43 | } 44 | 45 | ControllerButton(imageName: "arrowtriangle.down.fill") { 46 | appModel.controlParameter.up = $0 ? -1 : 0 47 | } 48 | } 49 | .frame(width: 192) 50 | } 51 | 52 | ToggleImmersiveSpaceButton() 53 | } 54 | .padding(40) 55 | .frame(width: 1200, height: 600) 56 | } 57 | } 58 | 59 | #Preview(windowStyle: .automatic) { 60 | ContentView() 61 | .environment(AppModel()) 62 | } 63 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Views/ControllerButton.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ControllerButton: View { 4 | let imageName: String 5 | let action: (Bool) -> Void 6 | 7 | var body: some View { 8 | Button {} label: { 9 | Image(systemName: imageName) 10 | .resizable() 11 | .frame(width: 64, height: 64) 12 | // .padding(8) 13 | .contentShape(Rectangle()) 14 | } 15 | .buttonStyle(PressedButtonStyle { 16 | action($0) 17 | }) 18 | } 19 | } 20 | 21 | struct PressedButtonStyle: ButtonStyle { 22 | var onPressChanged: (Bool) -> Void 23 | func makeBody(configuration: Configuration) -> some View { 24 | configuration.label 25 | .opacity(configuration.isPressed ? 0.5 : 1) 26 | .onChange(of: configuration.isPressed, { _, isPressed in 27 | onPressChanged(isPressed) 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Views/ImmersiveView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import RealityKit 3 | import RealityKitContent 4 | 5 | struct ImmersiveView: View { 6 | @Environment(AppModel.self) private var appModel 7 | 8 | var body: some View { 9 | RealityView { content in 10 | if let scene = try? await Entity(named: "Scene", in: realityKitContentBundle) { 11 | content.add(scene) 12 | 13 | if let skybox = Entity.createSkybox(name: "Skybox") { 14 | scene.addChild(skybox) 15 | } 16 | 17 | guard let env = try? await EnvironmentResource(named: "Sunlight") else { return } 18 | let iblComponent = ImageBasedLightComponent(source: .single(env), intensityExponent: 1.0) 19 | scene.components[ImageBasedLightComponent.self] = iblComponent 20 | scene.components.set(ImageBasedLightReceiverComponent(imageBasedLight: scene)) 21 | 22 | appModel.scene = scene 23 | 24 | if let drone = try? await Entity(named: "Drone", in: realityKitContentBundle) { 25 | drone.position = [0, 1, -1] 26 | drone.orientation = .init(angle: Float.pi, axis: .init(x: 0, y: 1, z: 0)) 27 | drone.components.set(DroneControlComponent()) 28 | 29 | if let animation = drone.availableAnimations.last { 30 | drone.playAnimation(animation.repeat()) 31 | } 32 | scene.addChild(drone) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | #Preview(immersionStyle: .mixed) { 40 | ImmersiveView() 41 | .environment(AppModel()) 42 | } 43 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Views/OffscreenRenderView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import RealityKit 3 | 4 | struct OffscreenRenderView: View { 5 | @Environment(AppModel.self) private var appModel 6 | 7 | @State private var model: OffscreenRenderModel = .init() 8 | @State private var timer: Timer? 9 | 10 | var body: some View { 11 | ZStack { 12 | if model.isRendererUpdated { 13 | RealityView { content in 14 | if let entity = try? renderEntity() { 15 | content.add(entity) 16 | } 17 | } 18 | } else { 19 | Color.black 20 | } 21 | } 22 | .onChange(of: appModel.immersiveSpaceState, { _, state in 23 | switch state { 24 | case .closed: 25 | // Invalidate timer when immersive space is closed 26 | timer?.invalidate() 27 | default: 28 | break 29 | } 30 | }) 31 | .onChange(of: appModel.scene, { _, newValue in 32 | guard let scene = newValue else { return } 33 | 34 | do { 35 | try model.setup(scene: scene) 36 | 37 | timer = Timer.scheduledTimer(withTimeInterval: 1/30, repeats: true) { _ in 38 | Task { @MainActor in 39 | guard let transform = appModel.cameraTransform else { return } 40 | // Render with shared camera translation & rotation 41 | try? model.render(position: transform.translation, 42 | orientation: transform.rotation * simd_quatf(angle: -.pi, axis: .init(x: 0, y: 1, z: 0))) 43 | } 44 | } 45 | } catch { 46 | // do nothing 47 | } 48 | }) 49 | } 50 | } 51 | 52 | // ref: https://gist.github.com/Matt54/c4d2ce778ffb6f9d2ddc6b8c7332c7d5 53 | extension OffscreenRenderView { 54 | // Entity for rendering with LowLevelTexture 55 | private func renderEntity() throws -> Entity? { 56 | guard let lowLevelTexture = model.lowLevelTexture, 57 | let resource = try? TextureResource(from: lowLevelTexture) else { return nil } 58 | 59 | var material = UnlitMaterial() 60 | material.color.texture = .init(resource) 61 | 62 | let mesh = MeshResource.generatePlane(width: 1.6, height: 0.9) 63 | let modelComponent = ModelComponent(mesh: mesh, materials: [material]) 64 | 65 | let entity = Entity() 66 | entity.components.set(modelComponent) 67 | entity.scale *= 0.3 68 | entity.position = [0, 0, -0.1] 69 | 70 | return entity 71 | } 72 | } 73 | 74 | #Preview(windowStyle: .automatic) { 75 | OffscreenRenderView() 76 | .environment(AppModel()) 77 | } 78 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCamera/Views/ToggleImmersiveSpaceButton.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ToggleImmersiveSpaceButton: View { 4 | 5 | @Environment(AppModel.self) private var appModel 6 | 7 | @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace 8 | @Environment(\.openImmersiveSpace) private var openImmersiveSpace 9 | 10 | var body: some View { 11 | Button { 12 | Task { @MainActor in 13 | switch appModel.immersiveSpaceState { 14 | case .open: 15 | appModel.immersiveSpaceState = .inTransition 16 | await dismissImmersiveSpace() 17 | // Don't set immersiveSpaceState to .closed because there 18 | // are multiple paths to ImmersiveView.onDisappear(). 19 | // Only set .closed in ImmersiveView.onDisappear(). 20 | case .closed: 21 | appModel.immersiveSpaceState = .inTransition 22 | switch await openImmersiveSpace(id: appModel.immersiveSpaceID) { 23 | case .opened: 24 | // Don't set immersiveSpaceState to .open because there 25 | // may be multiple paths to ImmersiveView.onAppear(). 26 | // Only set .open in ImmersiveView.onAppear(). 27 | break 28 | 29 | case .userCancelled, .error: 30 | // On error, we need to mark the immersive space 31 | // as closed because it failed to open. 32 | fallthrough 33 | @unknown default: 34 | // On unknown response, assume space did not open. 35 | appModel.immersiveSpaceState = .closed 36 | } 37 | 38 | case .inTransition: 39 | // This case should not ever happen because button is disabled for this case. 40 | break 41 | } 42 | } 43 | } label: { 44 | Text(appModel.immersiveSpaceState == .open ? "EXIT" : "PLAY") 45 | .font(.largeTitle) 46 | .padding() 47 | } 48 | .disabled(appModel.immersiveSpaceState == .inTransition) 49 | .animation(.none, value: 0) 50 | .fontWeight(.semibold) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RealityKitPerspectiveCameraTests/RealityKitPerspectiveCameraTests.swift: -------------------------------------------------------------------------------- 1 | import Testing 2 | @testable import RealityKitPerspectiveCamera 3 | 4 | struct RealityKitPerspectiveCameraTests { 5 | 6 | @Test func example() async throws { 7 | // Write your test here and use APIs like `#expect(...)` to check expected conditions. 8 | } 9 | 10 | } 11 | --------------------------------------------------------------------------------