react-native-nitro-ar 2026.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/README.md +347 -0
  2. package/app.plugin.js +24 -0
  3. package/ios/Bridge.h +8 -0
  4. package/ios/HybridARAnchor.swift +58 -0
  5. package/ios/HybridARBoundingBoxBuilder.swift +142 -0
  6. package/ios/HybridARDepthData.swift +138 -0
  7. package/ios/HybridARFrame.swift +121 -0
  8. package/ios/HybridARLightEstimate.swift +58 -0
  9. package/ios/HybridARMeasurement.swift +33 -0
  10. package/ios/HybridARMeshAnchor.swift +108 -0
  11. package/ios/HybridARPlaneAnchor.swift +114 -0
  12. package/ios/HybridARRaycastResult.swift +53 -0
  13. package/ios/HybridARSession.swift +505 -0
  14. package/ios/HybridARView.swift +725 -0
  15. package/ios/HybridARVolume.swift +52 -0
  16. package/ios/HybridARWorldMap.swift +55 -0
  17. package/lib/commonjs/index.js +24 -0
  18. package/lib/commonjs/index.js.map +1 -0
  19. package/lib/commonjs/package.json +3 -0
  20. package/lib/commonjs/specs/ARAnchor.nitro.js +6 -0
  21. package/lib/commonjs/specs/ARAnchor.nitro.js.map +1 -0
  22. package/lib/commonjs/specs/ARBoundingBoxBuilder.nitro.js +6 -0
  23. package/lib/commonjs/specs/ARBoundingBoxBuilder.nitro.js.map +1 -0
  24. package/lib/commonjs/specs/ARDepthData.nitro.js +6 -0
  25. package/lib/commonjs/specs/ARDepthData.nitro.js.map +1 -0
  26. package/lib/commonjs/specs/ARFrame.nitro.js +6 -0
  27. package/lib/commonjs/specs/ARFrame.nitro.js.map +1 -0
  28. package/lib/commonjs/specs/ARLightEstimate.nitro.js +6 -0
  29. package/lib/commonjs/specs/ARLightEstimate.nitro.js.map +1 -0
  30. package/lib/commonjs/specs/ARMeasurement.nitro.js +6 -0
  31. package/lib/commonjs/specs/ARMeasurement.nitro.js.map +1 -0
  32. package/lib/commonjs/specs/ARPlaneAnchor.nitro.js +6 -0
  33. package/lib/commonjs/specs/ARPlaneAnchor.nitro.js.map +1 -0
  34. package/lib/commonjs/specs/ARRaycastResult.nitro.js +6 -0
  35. package/lib/commonjs/specs/ARRaycastResult.nitro.js.map +1 -0
  36. package/lib/commonjs/specs/ARSceneMesh.nitro.js +6 -0
  37. package/lib/commonjs/specs/ARSceneMesh.nitro.js.map +1 -0
  38. package/lib/commonjs/specs/ARSession.nitro.js +6 -0
  39. package/lib/commonjs/specs/ARSession.nitro.js.map +1 -0
  40. package/lib/commonjs/specs/ARView.nitro.js +6 -0
  41. package/lib/commonjs/specs/ARView.nitro.js.map +1 -0
  42. package/lib/commonjs/specs/ARVolume.nitro.js +6 -0
  43. package/lib/commonjs/specs/ARVolume.nitro.js.map +1 -0
  44. package/lib/commonjs/specs/ARWorldMap.nitro.js +6 -0
  45. package/lib/commonjs/specs/ARWorldMap.nitro.js.map +1 -0
  46. package/lib/module/index.js +18 -0
  47. package/lib/module/index.js.map +1 -0
  48. package/lib/module/specs/ARAnchor.nitro.js +4 -0
  49. package/lib/module/specs/ARAnchor.nitro.js.map +1 -0
  50. package/lib/module/specs/ARBoundingBoxBuilder.nitro.js +4 -0
  51. package/lib/module/specs/ARBoundingBoxBuilder.nitro.js.map +1 -0
  52. package/lib/module/specs/ARDepthData.nitro.js +4 -0
  53. package/lib/module/specs/ARDepthData.nitro.js.map +1 -0
  54. package/lib/module/specs/ARFrame.nitro.js +4 -0
  55. package/lib/module/specs/ARFrame.nitro.js.map +1 -0
  56. package/lib/module/specs/ARLightEstimate.nitro.js +4 -0
  57. package/lib/module/specs/ARLightEstimate.nitro.js.map +1 -0
  58. package/lib/module/specs/ARMeasurement.nitro.js +4 -0
  59. package/lib/module/specs/ARMeasurement.nitro.js.map +1 -0
  60. package/lib/module/specs/ARPlaneAnchor.nitro.js +4 -0
  61. package/lib/module/specs/ARPlaneAnchor.nitro.js.map +1 -0
  62. package/lib/module/specs/ARRaycastResult.nitro.js +4 -0
  63. package/lib/module/specs/ARRaycastResult.nitro.js.map +1 -0
  64. package/lib/module/specs/ARSceneMesh.nitro.js +4 -0
  65. package/lib/module/specs/ARSceneMesh.nitro.js.map +1 -0
  66. package/lib/module/specs/ARSession.nitro.js +4 -0
  67. package/lib/module/specs/ARSession.nitro.js.map +1 -0
  68. package/lib/module/specs/ARView.nitro.js +4 -0
  69. package/lib/module/specs/ARView.nitro.js.map +1 -0
  70. package/lib/module/specs/ARVolume.nitro.js +4 -0
  71. package/lib/module/specs/ARVolume.nitro.js.map +1 -0
  72. package/lib/module/specs/ARWorldMap.nitro.js +4 -0
  73. package/lib/module/specs/ARWorldMap.nitro.js.map +1 -0
  74. package/lib/typescript/src/index.d.ts +20 -0
  75. package/lib/typescript/src/index.d.ts.map +1 -0
  76. package/lib/typescript/src/specs/ARAnchor.nitro.d.ts +18 -0
  77. package/lib/typescript/src/specs/ARAnchor.nitro.d.ts.map +1 -0
  78. package/lib/typescript/src/specs/ARBoundingBoxBuilder.nitro.d.ts +11 -0
  79. package/lib/typescript/src/specs/ARBoundingBoxBuilder.nitro.d.ts.map +1 -0
  80. package/lib/typescript/src/specs/ARDepthData.nitro.d.ts +26 -0
  81. package/lib/typescript/src/specs/ARDepthData.nitro.d.ts.map +1 -0
  82. package/lib/typescript/src/specs/ARFrame.nitro.d.ts +32 -0
  83. package/lib/typescript/src/specs/ARFrame.nitro.d.ts.map +1 -0
  84. package/lib/typescript/src/specs/ARLightEstimate.nitro.d.ts +18 -0
  85. package/lib/typescript/src/specs/ARLightEstimate.nitro.d.ts.map +1 -0
  86. package/lib/typescript/src/specs/ARMeasurement.nitro.d.ts +11 -0
  87. package/lib/typescript/src/specs/ARMeasurement.nitro.d.ts.map +1 -0
  88. package/lib/typescript/src/specs/ARPlaneAnchor.nitro.d.ts +32 -0
  89. package/lib/typescript/src/specs/ARPlaneAnchor.nitro.d.ts.map +1 -0
  90. package/lib/typescript/src/specs/ARRaycastResult.nitro.d.ts +26 -0
  91. package/lib/typescript/src/specs/ARRaycastResult.nitro.d.ts.map +1 -0
  92. package/lib/typescript/src/specs/ARSceneMesh.nitro.d.ts +47 -0
  93. package/lib/typescript/src/specs/ARSceneMesh.nitro.d.ts.map +1 -0
  94. package/lib/typescript/src/specs/ARSession.nitro.d.ts +75 -0
  95. package/lib/typescript/src/specs/ARSession.nitro.d.ts.map +1 -0
  96. package/lib/typescript/src/specs/ARView.nitro.d.ts +51 -0
  97. package/lib/typescript/src/specs/ARView.nitro.d.ts.map +1 -0
  98. package/lib/typescript/src/specs/ARVolume.nitro.d.ts +14 -0
  99. package/lib/typescript/src/specs/ARVolume.nitro.d.ts.map +1 -0
  100. package/lib/typescript/src/specs/ARWorldMap.nitro.d.ts +17 -0
  101. package/lib/typescript/src/specs/ARWorldMap.nitro.d.ts.map +1 -0
  102. package/nitro.json +23 -0
  103. package/nitrogen/generated/.gitattributes +1 -0
  104. package/nitrogen/generated/ios/NitroAR+autolinking.rb +60 -0
  105. package/nitrogen/generated/ios/NitroAR-Swift-Cxx-Bridge.cpp +335 -0
  106. package/nitrogen/generated/ios/NitroAR-Swift-Cxx-Bridge.hpp +934 -0
  107. package/nitrogen/generated/ios/NitroAR-Swift-Cxx-Umbrella.hpp +169 -0
  108. package/nitrogen/generated/ios/NitroARAutolinking.mm +49 -0
  109. package/nitrogen/generated/ios/NitroARAutolinking.swift +50 -0
  110. package/nitrogen/generated/ios/c++/HybridARAnchorSpecSwift.cpp +11 -0
  111. package/nitrogen/generated/ios/c++/HybridARAnchorSpecSwift.hpp +99 -0
  112. package/nitrogen/generated/ios/c++/HybridARBoundingBoxBuilderSpecSwift.cpp +11 -0
  113. package/nitrogen/generated/ios/c++/HybridARBoundingBoxBuilderSpecSwift.hpp +95 -0
  114. package/nitrogen/generated/ios/c++/HybridARDepthDataSpecSwift.cpp +11 -0
  115. package/nitrogen/generated/ios/c++/HybridARDepthDataSpecSwift.hpp +103 -0
  116. package/nitrogen/generated/ios/c++/HybridARDirectionalLightEstimateSpecSwift.cpp +11 -0
  117. package/nitrogen/generated/ios/c++/HybridARDirectionalLightEstimateSpecSwift.hpp +88 -0
  118. package/nitrogen/generated/ios/c++/HybridARFrameSpecSwift.cpp +11 -0
  119. package/nitrogen/generated/ios/c++/HybridARFrameSpecSwift.hpp +135 -0
  120. package/nitrogen/generated/ios/c++/HybridARLightEstimateSpecSwift.cpp +11 -0
  121. package/nitrogen/generated/ios/c++/HybridARLightEstimateSpecSwift.hpp +80 -0
  122. package/nitrogen/generated/ios/c++/HybridARMeasurementSpecSwift.cpp +11 -0
  123. package/nitrogen/generated/ios/c++/HybridARMeasurementSpecSwift.hpp +90 -0
  124. package/nitrogen/generated/ios/c++/HybridARMeshAnchorSpecSwift.cpp +11 -0
  125. package/nitrogen/generated/ios/c++/HybridARMeshAnchorSpecSwift.hpp +107 -0
  126. package/nitrogen/generated/ios/c++/HybridARPlaneAnchorSpecSwift.cpp +11 -0
  127. package/nitrogen/generated/ios/c++/HybridARPlaneAnchorSpecSwift.hpp +116 -0
  128. package/nitrogen/generated/ios/c++/HybridARPlaneGeometrySpecSwift.cpp +11 -0
  129. package/nitrogen/generated/ios/c++/HybridARPlaneGeometrySpecSwift.hpp +90 -0
  130. package/nitrogen/generated/ios/c++/HybridARRaycastResultSpecSwift.cpp +11 -0
  131. package/nitrogen/generated/ios/c++/HybridARRaycastResultSpecSwift.hpp +97 -0
  132. package/nitrogen/generated/ios/c++/HybridARSessionSpecSwift.cpp +11 -0
  133. package/nitrogen/generated/ios/c++/HybridARSessionSpecSwift.hpp +296 -0
  134. package/nitrogen/generated/ios/c++/HybridARViewSpecSwift.cpp +11 -0
  135. package/nitrogen/generated/ios/c++/HybridARViewSpecSwift.hpp +243 -0
  136. package/nitrogen/generated/ios/c++/HybridARVolumeSpecSwift.cpp +11 -0
  137. package/nitrogen/generated/ios/c++/HybridARVolumeSpecSwift.hpp +94 -0
  138. package/nitrogen/generated/ios/c++/HybridARWorldMapSpecSwift.cpp +11 -0
  139. package/nitrogen/generated/ios/c++/HybridARWorldMapSpecSwift.hpp +97 -0
  140. package/nitrogen/generated/ios/c++/views/HybridARViewComponent.mm +152 -0
  141. package/nitrogen/generated/ios/swift/ARSessionConfiguration.swift +189 -0
  142. package/nitrogen/generated/ios/swift/ARViewHitResult.swift +39 -0
  143. package/nitrogen/generated/ios/swift/CameraPose.swift +46 -0
  144. package/nitrogen/generated/ios/swift/EnvironmentTexturing.swift +44 -0
  145. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  146. package/nitrogen/generated/ios/swift/Func_void_TrackingState_TrackingStateReason.swift +46 -0
  147. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  148. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_HybridARFrameSpec_.swift +50 -0
  149. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_HybridARWorldMapSpec_.swift +50 -0
  150. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__shared_ptr_HybridARAnchorSpec___std__vector_std__shared_ptr_HybridARAnchorSpec___std__vector_std__string_.swift +54 -0
  151. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__shared_ptr_HybridARMeshAnchorSpec___std__vector_std__shared_ptr_HybridARMeshAnchorSpec___std__vector_std__string_.swift +54 -0
  152. package/nitrogen/generated/ios/swift/Func_void_std__vector_std__shared_ptr_HybridARPlaneAnchorSpec___std__vector_std__shared_ptr_HybridARPlaneAnchorSpec___std__vector_std__string_.swift +54 -0
  153. package/nitrogen/generated/ios/swift/HybridARAnchorSpec.swift +60 -0
  154. package/nitrogen/generated/ios/swift/HybridARAnchorSpec_cxx.swift +192 -0
  155. package/nitrogen/generated/ios/swift/HybridARBoundingBoxBuilderSpec.swift +56 -0
  156. package/nitrogen/generated/ios/swift/HybridARBoundingBoxBuilderSpec_cxx.swift +161 -0
  157. package/nitrogen/generated/ios/swift/HybridARDepthDataSpec.swift +59 -0
  158. package/nitrogen/generated/ios/swift/HybridARDepthDataSpec_cxx.swift +188 -0
  159. package/nitrogen/generated/ios/swift/HybridARDirectionalLightEstimateSpec.swift +57 -0
  160. package/nitrogen/generated/ios/swift/HybridARDirectionalLightEstimateSpec_cxx.swift +162 -0
  161. package/nitrogen/generated/ios/swift/HybridARFrameSpec.swift +65 -0
  162. package/nitrogen/generated/ios/swift/HybridARFrameSpec_cxx.swift +285 -0
  163. package/nitrogen/generated/ios/swift/HybridARLightEstimateSpec.swift +56 -0
  164. package/nitrogen/generated/ios/swift/HybridARLightEstimateSpec_cxx.swift +140 -0
  165. package/nitrogen/generated/ios/swift/HybridARMeasurementSpec.swift +58 -0
  166. package/nitrogen/generated/ios/swift/HybridARMeasurementSpec_cxx.swift +160 -0
  167. package/nitrogen/generated/ios/swift/HybridARMeshAnchorSpec.swift +62 -0
  168. package/nitrogen/generated/ios/swift/HybridARMeshAnchorSpec_cxx.swift +212 -0
  169. package/nitrogen/generated/ios/swift/HybridARPlaneAnchorSpec.swift +62 -0
  170. package/nitrogen/generated/ios/swift/HybridARPlaneAnchorSpec_cxx.swift +209 -0
  171. package/nitrogen/generated/ios/swift/HybridARPlaneGeometrySpec.swift +58 -0
  172. package/nitrogen/generated/ios/swift/HybridARPlaneGeometrySpec_cxx.swift +178 -0
  173. package/nitrogen/generated/ios/swift/HybridARRaycastResultSpec.swift +59 -0
  174. package/nitrogen/generated/ios/swift/HybridARRaycastResultSpec_cxx.swift +179 -0
  175. package/nitrogen/generated/ios/swift/HybridARSessionSpec.swift +78 -0
  176. package/nitrogen/generated/ios/swift/HybridARSessionSpec_cxx.swift +591 -0
  177. package/nitrogen/generated/ios/swift/HybridARViewSpec.swift +78 -0
  178. package/nitrogen/generated/ios/swift/HybridARViewSpec_cxx.swift +561 -0
  179. package/nitrogen/generated/ios/swift/HybridARVolumeSpec.swift +60 -0
  180. package/nitrogen/generated/ios/swift/HybridARVolumeSpec_cxx.swift +180 -0
  181. package/nitrogen/generated/ios/swift/HybridARWorldMapSpec.swift +58 -0
  182. package/nitrogen/generated/ios/swift/HybridARWorldMapSpec_cxx.swift +176 -0
  183. package/nitrogen/generated/ios/swift/LiDARCapabilities.swift +39 -0
  184. package/nitrogen/generated/ios/swift/MeshClassification.swift +64 -0
  185. package/nitrogen/generated/ios/swift/PlaneAlignment.swift +40 -0
  186. package/nitrogen/generated/ios/swift/PlaneClassification.swift +64 -0
  187. package/nitrogen/generated/ios/swift/PlaneDetectionMode.swift +40 -0
  188. package/nitrogen/generated/ios/swift/RaycastAlignment.swift +44 -0
  189. package/nitrogen/generated/ios/swift/RaycastQuery.swift +44 -0
  190. package/nitrogen/generated/ios/swift/RaycastTarget.swift +48 -0
  191. package/nitrogen/generated/ios/swift/SceneReconstructionMode.swift +44 -0
  192. package/nitrogen/generated/ios/swift/TrackingState.swift +44 -0
  193. package/nitrogen/generated/ios/swift/TrackingStateReason.swift +52 -0
  194. package/nitrogen/generated/ios/swift/WorldAlignment.swift +44 -0
  195. package/nitrogen/generated/ios/swift/WorldMappingStatus.swift +48 -0
  196. package/nitrogen/generated/shared/c++/ARSessionConfiguration.hpp +132 -0
  197. package/nitrogen/generated/shared/c++/ARViewHitResult.hpp +91 -0
  198. package/nitrogen/generated/shared/c++/CameraPose.hpp +87 -0
  199. package/nitrogen/generated/shared/c++/EnvironmentTexturing.hpp +80 -0
  200. package/nitrogen/generated/shared/c++/HybridARAnchorSpec.cpp +26 -0
  201. package/nitrogen/generated/shared/c++/HybridARAnchorSpec.hpp +69 -0
  202. package/nitrogen/generated/shared/c++/HybridARBoundingBoxBuilderSpec.cpp +23 -0
  203. package/nitrogen/generated/shared/c++/HybridARBoundingBoxBuilderSpec.hpp +68 -0
  204. package/nitrogen/generated/shared/c++/HybridARDepthDataSpec.cpp +26 -0
  205. package/nitrogen/generated/shared/c++/HybridARDepthDataSpec.hpp +66 -0
  206. package/nitrogen/generated/shared/c++/HybridARDirectionalLightEstimateSpec.cpp +24 -0
  207. package/nitrogen/generated/shared/c++/HybridARDirectionalLightEstimateSpec.hpp +67 -0
  208. package/nitrogen/generated/shared/c++/HybridARFrameSpec.cpp +32 -0
  209. package/nitrogen/generated/shared/c++/HybridARFrameSpec.hpp +83 -0
  210. package/nitrogen/generated/shared/c++/HybridARLightEstimateSpec.cpp +22 -0
  211. package/nitrogen/generated/shared/c++/HybridARLightEstimateSpec.hpp +63 -0
  212. package/nitrogen/generated/shared/c++/HybridARMeasurementSpec.cpp +24 -0
  213. package/nitrogen/generated/shared/c++/HybridARMeasurementSpec.hpp +67 -0
  214. package/nitrogen/generated/shared/c++/HybridARMeshAnchorSpec.cpp +28 -0
  215. package/nitrogen/generated/shared/c++/HybridARMeshAnchorSpec.hpp +72 -0
  216. package/nitrogen/generated/shared/c++/HybridARPlaneAnchorSpec.cpp +28 -0
  217. package/nitrogen/generated/shared/c++/HybridARPlaneAnchorSpec.hpp +79 -0
  218. package/nitrogen/generated/shared/c++/HybridARPlaneGeometrySpec.cpp +24 -0
  219. package/nitrogen/generated/shared/c++/HybridARPlaneGeometrySpec.hpp +65 -0
  220. package/nitrogen/generated/shared/c++/HybridARRaycastResultSpec.cpp +25 -0
  221. package/nitrogen/generated/shared/c++/HybridARRaycastResultSpec.hpp +70 -0
  222. package/nitrogen/generated/shared/c++/HybridARSessionSpec.cpp +45 -0
  223. package/nitrogen/generated/shared/c++/HybridARSessionSpec.hpp +131 -0
  224. package/nitrogen/generated/shared/c++/HybridARViewSpec.cpp +55 -0
  225. package/nitrogen/generated/shared/c++/HybridARViewSpec.hpp +101 -0
  226. package/nitrogen/generated/shared/c++/HybridARVolumeSpec.cpp +26 -0
  227. package/nitrogen/generated/shared/c++/HybridARVolumeSpec.hpp +67 -0
  228. package/nitrogen/generated/shared/c++/HybridARWorldMapSpec.cpp +25 -0
  229. package/nitrogen/generated/shared/c++/HybridARWorldMapSpec.hpp +66 -0
  230. package/nitrogen/generated/shared/c++/LiDARCapabilities.hpp +91 -0
  231. package/nitrogen/generated/shared/c++/MeshClassification.hpp +100 -0
  232. package/nitrogen/generated/shared/c++/PlaneAlignment.hpp +76 -0
  233. package/nitrogen/generated/shared/c++/PlaneClassification.hpp +100 -0
  234. package/nitrogen/generated/shared/c++/PlaneDetectionMode.hpp +76 -0
  235. package/nitrogen/generated/shared/c++/RaycastAlignment.hpp +80 -0
  236. package/nitrogen/generated/shared/c++/RaycastQuery.hpp +99 -0
  237. package/nitrogen/generated/shared/c++/RaycastTarget.hpp +84 -0
  238. package/nitrogen/generated/shared/c++/SceneReconstructionMode.hpp +80 -0
  239. package/nitrogen/generated/shared/c++/TrackingState.hpp +80 -0
  240. package/nitrogen/generated/shared/c++/TrackingStateReason.hpp +88 -0
  241. package/nitrogen/generated/shared/c++/WorldAlignment.hpp +80 -0
  242. package/nitrogen/generated/shared/c++/WorldMappingStatus.hpp +84 -0
  243. package/nitrogen/generated/shared/c++/views/HybridARViewComponent.cpp +182 -0
  244. package/nitrogen/generated/shared/c++/views/HybridARViewComponent.hpp +120 -0
  245. package/nitrogen/generated/shared/json/ARViewConfig.json +19 -0
  246. package/package.json +98 -0
  247. package/react-native-nitro-ar.podspec +40 -0
  248. package/src/index.ts +60 -0
  249. package/src/specs/ARAnchor.nitro.ts +21 -0
  250. package/src/specs/ARBoundingBoxBuilder.nitro.ts +11 -0
  251. package/src/specs/ARDepthData.nitro.ts +29 -0
  252. package/src/specs/ARFrame.nitro.ts +41 -0
  253. package/src/specs/ARLightEstimate.nitro.ts +20 -0
  254. package/src/specs/ARMeasurement.nitro.ts +10 -0
  255. package/src/specs/ARPlaneAnchor.nitro.ts +46 -0
  256. package/src/specs/ARRaycastResult.nitro.ts +32 -0
  257. package/src/specs/ARSceneMesh.nitro.ts +63 -0
  258. package/src/specs/ARSession.nitro.ts +112 -0
  259. package/src/specs/ARView.nitro.ts +84 -0
  260. package/src/specs/ARVolume.nitro.ts +15 -0
  261. package/src/specs/ARWorldMap.nitro.ts +20 -0
@@ -0,0 +1,725 @@
1
+ import ARKit
2
+ import SceneKit
3
+ import UIKit
4
+ import NitroModules
5
+
6
+ class HybridARView: HybridARViewSpec {
7
+ // The underlying AR view
8
+ private lazy var arView: NitroARSceneView = {
9
+ let view = NitroARSceneView(frame: .zero)
10
+ view.onPlaneAdded = { [weak self] anchor, node in
11
+ self?.handlePlaneAdded(anchor: anchor, node: node)
12
+ }
13
+ view.onPlaneUpdated = { [weak self] anchor, node in
14
+ self?.handlePlaneUpdated(anchor: anchor, node: node)
15
+ }
16
+ view.onPlaneRemoved = { [weak self] anchor in
17
+ self?.handlePlaneRemoved(anchor: anchor)
18
+ }
19
+ return view
20
+ }()
21
+
22
+ // MARK: - HybridView
23
+
24
+ var view: UIView {
25
+ return arView
26
+ }
27
+
28
+ // MARK: - Properties
29
+
30
+ var showDebugOptions: Bool? {
31
+ didSet {
32
+ updateDebugOptions()
33
+ }
34
+ }
35
+
36
+ var showPlanes: Bool? {
37
+ didSet {
38
+ arView.showPlaneOverlay = showPlanes ?? false
39
+ }
40
+ }
41
+
42
+ var showFeaturePoints: Bool? {
43
+ didSet {
44
+ updateDebugOptions()
45
+ }
46
+ }
47
+
48
+ var showWorldOrigin: Bool? {
49
+ didSet {
50
+ updateDebugOptions()
51
+ }
52
+ }
53
+
54
+ var autoenablesDefaultLighting: Bool? {
55
+ didSet {
56
+ arView.autoenablesDefaultLighting = autoenablesDefaultLighting ?? true
57
+ }
58
+ }
59
+
60
+ // MARK: - LiDAR Properties
61
+
62
+ var sceneReconstruction: SceneReconstructionMode? {
63
+ didSet {
64
+ // Will be applied on next session start
65
+ }
66
+ }
67
+
68
+ var showSceneMesh: Bool? {
69
+ didSet {
70
+ arView.showMeshOverlay = showSceneMesh ?? false
71
+ }
72
+ }
73
+
74
+ var sceneDepth: Bool? {
75
+ didSet {
76
+ // Will be applied on next session start
77
+ }
78
+ }
79
+
80
+ var objectOcclusion: Bool? {
81
+ didSet {
82
+ updateOcclusionSettings()
83
+ }
84
+ }
85
+
86
+ var peopleOcclusion: Bool? {
87
+ didSet {
88
+ updateOcclusionSettings()
89
+ }
90
+ }
91
+
92
+ private func updateOcclusionSettings() {
93
+ #if !targetEnvironment(simulator)
94
+ if #available(iOS 13.0, *) {
95
+ if peopleOcclusion == true {
96
+ arView.environment.sceneUnderstanding.options.insert(.occlusion)
97
+ }
98
+ }
99
+ #endif
100
+ }
101
+
102
+ private func updateDebugOptions() {
103
+ // Debug visualization options
104
+ // Note: showFeaturePoints and showWorldOrigin are available in ARSCNDebugOptions
105
+ #if !targetEnvironment(simulator)
106
+ if showFeaturePoints == true || showDebugOptions == true || showWorldOrigin == true {
107
+ // These debug options require device (not simulator)
108
+ // Enable any available debug visualization
109
+ }
110
+ #endif
111
+ }
112
+
113
+ // MARK: - Measurement Point Methods
114
+
115
+ func addMeasurementPoint(id: String, x: Double, y: Double, z: Double, color: String?) throws {
116
+ let position = SCNVector3(Float(x), Float(y), Float(z))
117
+ let uiColor = color.flatMap { UIColor(hex: $0) } ?? .systemYellow
118
+ arView.addMeasurementPoint(id: id, position: position, color: uiColor)
119
+ }
120
+
121
+ func removeMeasurementPoint(id: String) throws {
122
+ arView.removeMeasurementPoint(id: id)
123
+ }
124
+
125
+ func updateMeasurementPoint(id: String, x: Double, y: Double, z: Double) throws {
126
+ let position = SCNVector3(Float(x), Float(y), Float(z))
127
+ arView.updateMeasurementPoint(id: id, position: position)
128
+ }
129
+
130
+ // MARK: - Line Methods
131
+
132
+ func addLine(id: String, fromX: Double, fromY: Double, fromZ: Double, toX: Double, toY: Double, toZ: Double, color: String?) throws {
133
+ let from = SCNVector3(Float(fromX), Float(fromY), Float(fromZ))
134
+ let to = SCNVector3(Float(toX), Float(toY), Float(toZ))
135
+ let uiColor = color.flatMap { UIColor(hex: $0) } ?? .white
136
+ arView.addLine(id: id, from: from, to: to, color: uiColor)
137
+ }
138
+
139
+ func updateLine(id: String, fromX: Double, fromY: Double, fromZ: Double, toX: Double, toY: Double, toZ: Double) throws {
140
+ let from = SCNVector3(Float(fromX), Float(fromY), Float(fromZ))
141
+ let to = SCNVector3(Float(toX), Float(toY), Float(toZ))
142
+ arView.updateLine(id: id, from: from, to: to)
143
+ }
144
+
145
+ func removeLine(id: String) throws {
146
+ arView.removeLine(id: id)
147
+ }
148
+
149
+ // MARK: - Distance Label Methods
150
+
151
+ func addDistanceLabel(id: String, x: Double, y: Double, z: Double, distance: Double) throws {
152
+ let position = SCNVector3(Float(x), Float(y), Float(z))
153
+ arView.addDistanceLabel(id: id, position: position, distance: Float(distance))
154
+ }
155
+
156
+ func updateDistanceLabel(id: String, x: Double, y: Double, z: Double, distance: Double) throws {
157
+ let position = SCNVector3(Float(x), Float(y), Float(z))
158
+ arView.updateDistanceLabel(id: id, position: position, distance: Float(distance))
159
+ }
160
+
161
+ func removeDistanceLabel(id: String) throws {
162
+ arView.removeDistanceLabel(id: id)
163
+ }
164
+
165
+ // MARK: - Clear All
166
+
167
+ func clearAllVisuals() throws {
168
+ arView.clearAllVisuals()
169
+ }
170
+
171
+ // MARK: - Raycast
172
+
173
+ func raycast(x: Double, y: Double) throws -> ARViewHitResult? {
174
+ // Convert normalized coordinates to view coordinates
175
+ let viewPoint = CGPoint(
176
+ x: CGFloat(x) * arView.bounds.width,
177
+ y: CGFloat(y) * arView.bounds.height
178
+ )
179
+
180
+ guard let query = arView.raycastQuery(from: viewPoint, allowing: .estimatedPlane, alignment: .any) else {
181
+ return nil
182
+ }
183
+
184
+ guard let result = arView.session.raycast(query).first else {
185
+ return nil
186
+ }
187
+
188
+ let position = result.worldTransform.columns.3
189
+ return ARViewHitResult(
190
+ x: Double(position.x),
191
+ y: Double(position.y),
192
+ z: Double(position.z)
193
+ )
194
+ }
195
+
196
+ // MARK: - LiDAR
197
+
198
+ func isLiDARAvailable() throws -> Bool {
199
+ if #available(iOS 13.4, *) {
200
+ return ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh)
201
+ }
202
+ return false
203
+ }
204
+
205
+ // MARK: - Session Control
206
+
207
+ func startSession() throws {
208
+ let configuration = ARWorldTrackingConfiguration()
209
+ configuration.planeDetection = [.horizontal, .vertical]
210
+ configuration.isLightEstimationEnabled = true
211
+
212
+ if #available(iOS 16.0, *) {
213
+ configuration.environmentTexturing = .automatic
214
+ }
215
+
216
+ // LiDAR: Scene Reconstruction
217
+ if #available(iOS 13.4, *) {
218
+ if let mode = sceneReconstruction {
219
+ switch mode {
220
+ case .mesh:
221
+ if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) {
222
+ configuration.sceneReconstruction = .mesh
223
+ }
224
+ case .meshwithclassification:
225
+ if ARWorldTrackingConfiguration.supportsSceneReconstruction(.meshWithClassification) {
226
+ configuration.sceneReconstruction = .meshWithClassification
227
+ }
228
+ case .none:
229
+ break
230
+ @unknown default:
231
+ break
232
+ }
233
+ }
234
+ }
235
+
236
+ // LiDAR: Scene Depth
237
+ if #available(iOS 14.0, *) {
238
+ if sceneDepth == true {
239
+ if ARWorldTrackingConfiguration.supportsFrameSemantics(.sceneDepth) {
240
+ configuration.frameSemantics.insert(.sceneDepth)
241
+ }
242
+ }
243
+ }
244
+
245
+ // People Occlusion
246
+ if #available(iOS 13.0, *) {
247
+ if peopleOcclusion == true {
248
+ if ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentationWithDepth) {
249
+ configuration.frameSemantics.insert(.personSegmentationWithDepth)
250
+ }
251
+ }
252
+ }
253
+
254
+ arView.session.run(configuration)
255
+ }
256
+
257
+ func pauseSession() throws {
258
+ arView.session.pause()
259
+ }
260
+
261
+ func resetSession() throws {
262
+ let configuration = ARWorldTrackingConfiguration()
263
+ configuration.planeDetection = [.horizontal, .vertical]
264
+ configuration.isLightEstimationEnabled = true
265
+
266
+ if #available(iOS 16.0, *) {
267
+ configuration.environmentTexturing = .automatic
268
+ }
269
+
270
+ // LiDAR: Scene Reconstruction
271
+ if #available(iOS 13.4, *) {
272
+ if let mode = sceneReconstruction {
273
+ switch mode {
274
+ case .mesh:
275
+ if ARWorldTrackingConfiguration.supportsSceneReconstruction(.mesh) {
276
+ configuration.sceneReconstruction = .mesh
277
+ }
278
+ case .meshwithclassification:
279
+ if ARWorldTrackingConfiguration.supportsSceneReconstruction(.meshWithClassification) {
280
+ configuration.sceneReconstruction = .meshWithClassification
281
+ }
282
+ case .none:
283
+ break
284
+ @unknown default:
285
+ break
286
+ }
287
+ }
288
+ }
289
+
290
+ // LiDAR: Scene Depth
291
+ if #available(iOS 14.0, *) {
292
+ if sceneDepth == true {
293
+ if ARWorldTrackingConfiguration.supportsFrameSemantics(.sceneDepth) {
294
+ configuration.frameSemantics.insert(.sceneDepth)
295
+ }
296
+ }
297
+ }
298
+
299
+ // People Occlusion
300
+ if #available(iOS 13.0, *) {
301
+ if peopleOcclusion == true {
302
+ if ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentationWithDepth) {
303
+ configuration.frameSemantics.insert(.personSegmentationWithDepth)
304
+ }
305
+ }
306
+ }
307
+
308
+ arView.session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
309
+ try? clearAllVisuals()
310
+ }
311
+ }
312
+
313
+ // MARK: - Plane Delegate Handlers
314
+
315
+ extension HybridARView {
316
+ func handlePlaneAdded(anchor: ARPlaneAnchor, node: SCNNode) {
317
+ guard showPlanes == true else { return }
318
+ arView.addPlaneNode(for: anchor, node: node)
319
+ }
320
+
321
+ func handlePlaneUpdated(anchor: ARPlaneAnchor, node: SCNNode) {
322
+ guard showPlanes == true else { return }
323
+ arView.updatePlaneNode(for: anchor, node: node)
324
+ }
325
+
326
+ func handlePlaneRemoved(anchor: ARPlaneAnchor) {
327
+ arView.removePlaneNode(for: anchor.identifier)
328
+ }
329
+ }
330
+
331
+ // MARK: - NitroARSceneView
332
+
333
+ class NitroARSceneView: ARSCNView, ARSCNViewDelegate {
334
+ var showPlaneOverlay: Bool = false
335
+ var showMeshOverlay: Bool = false
336
+
337
+ // Callbacks for plane events
338
+ var onPlaneAdded: ((ARPlaneAnchor, SCNNode) -> Void)?
339
+ var onPlaneUpdated: ((ARPlaneAnchor, SCNNode) -> Void)?
340
+ var onPlaneRemoved: ((ARPlaneAnchor) -> Void)?
341
+
342
+ // Callbacks for mesh events
343
+ var onMeshAdded: ((ARMeshAnchor, SCNNode) -> Void)?
344
+ var onMeshUpdated: ((ARMeshAnchor, SCNNode) -> Void)?
345
+ var onMeshRemoved: ((ARMeshAnchor) -> Void)?
346
+
347
+ private var measurementNodes: [String: SCNNode] = [:]
348
+ private var lineNodes: [String: SCNNode] = [:]
349
+ private var labelNodes: [String: SCNNode] = [:]
350
+ private var planeNodes: [UUID: SCNNode] = [:]
351
+ private var meshNodes: [UUID: SCNNode] = [:]
352
+
353
+ override init(frame: CGRect, options: [String: Any]? = nil) {
354
+ super.init(frame: frame, options: options)
355
+ setup()
356
+ }
357
+
358
+ required init?(coder: NSCoder) {
359
+ super.init(coder: coder)
360
+ setup()
361
+ }
362
+
363
+ private func setup() {
364
+ autoenablesDefaultLighting = true
365
+ automaticallyUpdatesLighting = true
366
+ delegate = self
367
+ }
368
+
369
+ // MARK: - ARSCNViewDelegate
370
+
371
+ func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
372
+ if let planeAnchor = anchor as? ARPlaneAnchor {
373
+ onPlaneAdded?(planeAnchor, node)
374
+ }
375
+
376
+ if #available(iOS 13.4, *) {
377
+ if let meshAnchor = anchor as? ARMeshAnchor {
378
+ if showMeshOverlay {
379
+ addMeshVisualization(for: meshAnchor, node: node)
380
+ }
381
+ onMeshAdded?(meshAnchor, node)
382
+ }
383
+ }
384
+ }
385
+
386
+ func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
387
+ if let planeAnchor = anchor as? ARPlaneAnchor {
388
+ onPlaneUpdated?(planeAnchor, node)
389
+ }
390
+
391
+ if #available(iOS 13.4, *) {
392
+ if let meshAnchor = anchor as? ARMeshAnchor {
393
+ if showMeshOverlay {
394
+ updateMeshVisualization(for: meshAnchor, node: node)
395
+ }
396
+ onMeshUpdated?(meshAnchor, node)
397
+ }
398
+ }
399
+ }
400
+
401
+ func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
402
+ if let planeAnchor = anchor as? ARPlaneAnchor {
403
+ onPlaneRemoved?(planeAnchor)
404
+ }
405
+
406
+ if #available(iOS 13.4, *) {
407
+ if let meshAnchor = anchor as? ARMeshAnchor {
408
+ meshNodes.removeValue(forKey: meshAnchor.identifier)
409
+ onMeshRemoved?(meshAnchor)
410
+ }
411
+ }
412
+ }
413
+
414
+ // MARK: - Measurement Points
415
+
416
+ func addMeasurementPoint(id: String, position: SCNVector3, color: UIColor) {
417
+ measurementNodes[id]?.removeFromParentNode()
418
+
419
+ let sphere = SCNSphere(radius: 0.008)
420
+ sphere.firstMaterial?.diffuse.contents = color
421
+ sphere.firstMaterial?.lightingModel = .constant
422
+ sphere.firstMaterial?.isDoubleSided = true
423
+
424
+ let node = SCNNode(geometry: sphere)
425
+ node.position = position
426
+ node.name = "measurement_\(id)"
427
+
428
+ scene.rootNode.addChildNode(node)
429
+ measurementNodes[id] = node
430
+ }
431
+
432
+ func removeMeasurementPoint(id: String) {
433
+ measurementNodes[id]?.removeFromParentNode()
434
+ measurementNodes.removeValue(forKey: id)
435
+ }
436
+
437
+ func updateMeasurementPoint(id: String, position: SCNVector3) {
438
+ measurementNodes[id]?.position = position
439
+ }
440
+
441
+ // MARK: - Lines
442
+
443
+ func addLine(id: String, from: SCNVector3, to: SCNVector3, color: UIColor) {
444
+ lineNodes[id]?.removeFromParentNode()
445
+
446
+ let lineNode = createLineNode(from: from, to: to, color: color)
447
+ lineNode.name = "line_\(id)"
448
+
449
+ scene.rootNode.addChildNode(lineNode)
450
+ lineNodes[id] = lineNode
451
+ }
452
+
453
+ func updateLine(id: String, from: SCNVector3, to: SCNVector3) {
454
+ lineNodes[id]?.removeFromParentNode()
455
+ lineNodes.removeValue(forKey: id)
456
+ addLine(id: id, from: from, to: to, color: .white)
457
+ }
458
+
459
+ func removeLine(id: String) {
460
+ lineNodes[id]?.removeFromParentNode()
461
+ lineNodes.removeValue(forKey: id)
462
+ }
463
+
464
+ private func createLineNode(from: SCNVector3, to: SCNVector3, color: UIColor) -> SCNNode {
465
+ let distance = SCNVector3.distance(from, to)
466
+
467
+ let cylinder = SCNCylinder(radius: 0.002, height: CGFloat(distance))
468
+ cylinder.firstMaterial?.diffuse.contents = color
469
+ cylinder.firstMaterial?.lightingModel = .constant
470
+
471
+ let lineNode = SCNNode(geometry: cylinder)
472
+
473
+ lineNode.position = SCNVector3(
474
+ (from.x + to.x) / 2,
475
+ (from.y + to.y) / 2,
476
+ (from.z + to.z) / 2
477
+ )
478
+
479
+ lineNode.look(at: to, up: scene.rootNode.worldUp, localFront: SCNVector3(0, 1, 0))
480
+
481
+ return lineNode
482
+ }
483
+
484
+ // MARK: - Labels
485
+
486
+ func addDistanceLabel(id: String, position: SCNVector3, distance: Float) {
487
+ labelNodes[id]?.removeFromParentNode()
488
+
489
+ let text = formatDistance(distance)
490
+ let labelNode = createLabelNode(text: text, position: position)
491
+ labelNode.name = "label_\(id)"
492
+
493
+ scene.rootNode.addChildNode(labelNode)
494
+ labelNodes[id] = labelNode
495
+ }
496
+
497
+ func updateDistanceLabel(id: String, position: SCNVector3, distance: Float) {
498
+ labelNodes[id]?.removeFromParentNode()
499
+ labelNodes.removeValue(forKey: id)
500
+ addDistanceLabel(id: id, position: position, distance: distance)
501
+ }
502
+
503
+ func removeDistanceLabel(id: String) {
504
+ labelNodes[id]?.removeFromParentNode()
505
+ labelNodes.removeValue(forKey: id)
506
+ }
507
+
508
+ private func createLabelNode(text: String, position: SCNVector3) -> SCNNode {
509
+ let textGeometry = SCNText(string: text, extrusionDepth: 0.1)
510
+ textGeometry.font = UIFont.systemFont(ofSize: 10, weight: .bold)
511
+ textGeometry.firstMaterial?.diffuse.contents = UIColor.white
512
+ textGeometry.firstMaterial?.lightingModel = .constant
513
+ textGeometry.flatness = 0.1
514
+ textGeometry.alignmentMode = CATextLayerAlignmentMode.center.rawValue
515
+
516
+ let textNode = SCNNode(geometry: textGeometry)
517
+
518
+ let scale: Float = 0.005
519
+ textNode.scale = SCNVector3(scale, scale, scale)
520
+
521
+ let (min, max) = textGeometry.boundingBox
522
+ let width = max.x - min.x
523
+ let height = max.y - min.y
524
+ textNode.pivot = SCNMatrix4MakeTranslation(width / 2, height / 2, 0)
525
+
526
+ let containerNode = SCNNode()
527
+ containerNode.position = position
528
+ containerNode.addChildNode(textNode)
529
+
530
+ let billboardConstraint = SCNBillboardConstraint()
531
+ billboardConstraint.freeAxes = .all
532
+ containerNode.constraints = [billboardConstraint]
533
+
534
+ let padding: Float = 0.005
535
+ let bgWidth = CGFloat(width * scale + padding * 2)
536
+ let bgHeight = CGFloat(height * scale + padding * 2)
537
+ let background = SCNPlane(width: bgWidth, height: bgHeight)
538
+ background.firstMaterial?.diffuse.contents = UIColor.black.withAlphaComponent(0.7)
539
+ background.firstMaterial?.lightingModel = .constant
540
+ background.cornerRadius = bgHeight / 4
541
+
542
+ let bgNode = SCNNode(geometry: background)
543
+ bgNode.position = SCNVector3(0, 0, -0.001)
544
+ containerNode.addChildNode(bgNode)
545
+
546
+ return containerNode
547
+ }
548
+
549
+ private func formatDistance(_ meters: Float) -> String {
550
+ if meters < 0.01 {
551
+ return String(format: "%.1f mm", meters * 1000)
552
+ } else if meters < 1.0 {
553
+ return String(format: "%.1f cm", meters * 100)
554
+ } else {
555
+ return String(format: "%.2f m", meters)
556
+ }
557
+ }
558
+
559
+ // MARK: - Plane Visualization
560
+
561
+ func addPlaneNode(for anchor: ARPlaneAnchor, node: SCNNode) {
562
+ guard showPlaneOverlay else { return }
563
+
564
+ let planeGeometry = SCNPlane(
565
+ width: CGFloat(anchor.planeExtent.width),
566
+ height: CGFloat(anchor.planeExtent.height)
567
+ )
568
+
569
+ let material = SCNMaterial()
570
+ material.diffuse.contents = UIColor.cyan.withAlphaComponent(0.3)
571
+ material.isDoubleSided = true
572
+ planeGeometry.materials = [material]
573
+
574
+ let planeNode = SCNNode(geometry: planeGeometry)
575
+ planeNode.position = SCNVector3(anchor.center.x, 0, anchor.center.z)
576
+ planeNode.eulerAngles.x = -.pi / 2
577
+
578
+ node.addChildNode(planeNode)
579
+ planeNodes[anchor.identifier] = planeNode
580
+ }
581
+
582
+ func updatePlaneNode(for anchor: ARPlaneAnchor, node: SCNNode) {
583
+ guard let planeNode = planeNodes[anchor.identifier],
584
+ let planeGeometry = planeNode.geometry as? SCNPlane else { return }
585
+
586
+ planeGeometry.width = CGFloat(anchor.planeExtent.width)
587
+ planeGeometry.height = CGFloat(anchor.planeExtent.height)
588
+ planeNode.position = SCNVector3(anchor.center.x, 0, anchor.center.z)
589
+ }
590
+
591
+ func removePlaneNode(for id: UUID) {
592
+ planeNodes[id]?.removeFromParentNode()
593
+ planeNodes.removeValue(forKey: id)
594
+ }
595
+
596
+ // MARK: - Mesh Visualization
597
+
598
+ @available(iOS 13.4, *)
599
+ private func addMeshVisualization(for meshAnchor: ARMeshAnchor, node: SCNNode) {
600
+ let geometry = createGeometry(from: meshAnchor)
601
+ let meshNode = SCNNode(geometry: geometry)
602
+ meshNode.name = "mesh_\(meshAnchor.identifier)"
603
+
604
+ node.addChildNode(meshNode)
605
+ meshNodes[meshAnchor.identifier] = meshNode
606
+ }
607
+
608
+ @available(iOS 13.4, *)
609
+ private func updateMeshVisualization(for meshAnchor: ARMeshAnchor, node: SCNNode) {
610
+ meshNodes[meshAnchor.identifier]?.removeFromParentNode()
611
+ addMeshVisualization(for: meshAnchor, node: node)
612
+ }
613
+
614
+ @available(iOS 13.4, *)
615
+ private func createGeometry(from meshAnchor: ARMeshAnchor) -> SCNGeometry {
616
+ let meshGeometry = meshAnchor.geometry
617
+
618
+ // Vertices
619
+ let vertexSource = SCNGeometrySource(
620
+ buffer: meshGeometry.vertices.buffer,
621
+ vertexFormat: meshGeometry.vertices.format,
622
+ semantic: .vertex,
623
+ vertexCount: meshGeometry.vertices.count,
624
+ dataOffset: meshGeometry.vertices.offset,
625
+ dataStride: meshGeometry.vertices.stride
626
+ )
627
+
628
+ // Normals
629
+ let normalSource = SCNGeometrySource(
630
+ buffer: meshGeometry.normals.buffer,
631
+ vertexFormat: meshGeometry.normals.format,
632
+ semantic: .normal,
633
+ vertexCount: meshGeometry.normals.count,
634
+ dataOffset: meshGeometry.normals.offset,
635
+ dataStride: meshGeometry.normals.stride
636
+ )
637
+
638
+ // Faces
639
+ let faceData = Data(
640
+ bytesNoCopy: meshGeometry.faces.buffer.contents(),
641
+ count: meshGeometry.faces.buffer.length,
642
+ deallocator: .none
643
+ )
644
+
645
+ let element = SCNGeometryElement(
646
+ data: faceData,
647
+ primitiveType: .triangles,
648
+ primitiveCount: meshGeometry.faces.count,
649
+ bytesPerIndex: MemoryLayout<UInt32>.size
650
+ )
651
+
652
+ let geometry = SCNGeometry(sources: [vertexSource, normalSource], elements: [element])
653
+
654
+ // Wireframe material for visualization
655
+ let material = SCNMaterial()
656
+ material.fillMode = .lines
657
+ material.diffuse.contents = UIColor.cyan.withAlphaComponent(0.6)
658
+ material.isDoubleSided = true
659
+ geometry.materials = [material]
660
+
661
+ return geometry
662
+ }
663
+
664
+ // MARK: - Clear All
665
+
666
+ func clearAllVisuals() {
667
+ for node in measurementNodes.values {
668
+ node.removeFromParentNode()
669
+ }
670
+ measurementNodes.removeAll()
671
+
672
+ for node in lineNodes.values {
673
+ node.removeFromParentNode()
674
+ }
675
+ lineNodes.removeAll()
676
+
677
+ for node in labelNodes.values {
678
+ node.removeFromParentNode()
679
+ }
680
+ labelNodes.removeAll()
681
+ }
682
+ }
683
+
684
+ // MARK: - SCNVector3 Extensions
685
+
686
+ extension SCNVector3 {
687
+ static func distance(_ a: SCNVector3, _ b: SCNVector3) -> Float {
688
+ let dx = b.x - a.x
689
+ let dy = b.y - a.y
690
+ let dz = b.z - a.z
691
+ return sqrt(dx * dx + dy * dy + dz * dz)
692
+ }
693
+ }
694
+
695
+ // MARK: - UIColor Hex Extension
696
+
697
+ extension UIColor {
698
+ convenience init?(hex: String) {
699
+ var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
700
+ hexSanitized = hexSanitized.replacingOccurrences(of: "#", with: "")
701
+
702
+ var rgb: UInt64 = 0
703
+ guard Scanner(string: hexSanitized).scanHexInt64(&rgb) else { return nil }
704
+
705
+ let length = hexSanitized.count
706
+ let r, g, b, a: CGFloat
707
+
708
+ switch length {
709
+ case 6: // RGB
710
+ r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
711
+ g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
712
+ b = CGFloat(rgb & 0x0000FF) / 255.0
713
+ a = 1.0
714
+ case 8: // RGBA
715
+ r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0
716
+ g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0
717
+ b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0
718
+ a = CGFloat(rgb & 0x000000FF) / 255.0
719
+ default:
720
+ return nil
721
+ }
722
+
723
+ self.init(red: r, green: g, blue: b, alpha: a)
724
+ }
725
+ }