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.
- package/README.md +347 -0
- package/app.plugin.js +24 -0
- package/ios/Bridge.h +8 -0
- package/ios/HybridARAnchor.swift +58 -0
- package/ios/HybridARBoundingBoxBuilder.swift +142 -0
- package/ios/HybridARDepthData.swift +138 -0
- package/ios/HybridARFrame.swift +121 -0
- package/ios/HybridARLightEstimate.swift +58 -0
- package/ios/HybridARMeasurement.swift +33 -0
- package/ios/HybridARMeshAnchor.swift +108 -0
- package/ios/HybridARPlaneAnchor.swift +114 -0
- package/ios/HybridARRaycastResult.swift +53 -0
- package/ios/HybridARSession.swift +505 -0
- package/ios/HybridARView.swift +725 -0
- package/ios/HybridARVolume.swift +52 -0
- package/ios/HybridARWorldMap.swift +55 -0
- package/lib/commonjs/index.js +24 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +3 -0
- package/lib/commonjs/specs/ARAnchor.nitro.js +6 -0
- package/lib/commonjs/specs/ARAnchor.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARBoundingBoxBuilder.nitro.js +6 -0
- package/lib/commonjs/specs/ARBoundingBoxBuilder.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARDepthData.nitro.js +6 -0
- package/lib/commonjs/specs/ARDepthData.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARFrame.nitro.js +6 -0
- package/lib/commonjs/specs/ARFrame.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARLightEstimate.nitro.js +6 -0
- package/lib/commonjs/specs/ARLightEstimate.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARMeasurement.nitro.js +6 -0
- package/lib/commonjs/specs/ARMeasurement.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARPlaneAnchor.nitro.js +6 -0
- package/lib/commonjs/specs/ARPlaneAnchor.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARRaycastResult.nitro.js +6 -0
- package/lib/commonjs/specs/ARRaycastResult.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARSceneMesh.nitro.js +6 -0
- package/lib/commonjs/specs/ARSceneMesh.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARSession.nitro.js +6 -0
- package/lib/commonjs/specs/ARSession.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARView.nitro.js +6 -0
- package/lib/commonjs/specs/ARView.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARVolume.nitro.js +6 -0
- package/lib/commonjs/specs/ARVolume.nitro.js.map +1 -0
- package/lib/commonjs/specs/ARWorldMap.nitro.js +6 -0
- package/lib/commonjs/specs/ARWorldMap.nitro.js.map +1 -0
- package/lib/module/index.js +18 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/specs/ARAnchor.nitro.js +4 -0
- package/lib/module/specs/ARAnchor.nitro.js.map +1 -0
- package/lib/module/specs/ARBoundingBoxBuilder.nitro.js +4 -0
- package/lib/module/specs/ARBoundingBoxBuilder.nitro.js.map +1 -0
- package/lib/module/specs/ARDepthData.nitro.js +4 -0
- package/lib/module/specs/ARDepthData.nitro.js.map +1 -0
- package/lib/module/specs/ARFrame.nitro.js +4 -0
- package/lib/module/specs/ARFrame.nitro.js.map +1 -0
- package/lib/module/specs/ARLightEstimate.nitro.js +4 -0
- package/lib/module/specs/ARLightEstimate.nitro.js.map +1 -0
- package/lib/module/specs/ARMeasurement.nitro.js +4 -0
- package/lib/module/specs/ARMeasurement.nitro.js.map +1 -0
- package/lib/module/specs/ARPlaneAnchor.nitro.js +4 -0
- package/lib/module/specs/ARPlaneAnchor.nitro.js.map +1 -0
- package/lib/module/specs/ARRaycastResult.nitro.js +4 -0
- package/lib/module/specs/ARRaycastResult.nitro.js.map +1 -0
- package/lib/module/specs/ARSceneMesh.nitro.js +4 -0
- package/lib/module/specs/ARSceneMesh.nitro.js.map +1 -0
- package/lib/module/specs/ARSession.nitro.js +4 -0
- package/lib/module/specs/ARSession.nitro.js.map +1 -0
- package/lib/module/specs/ARView.nitro.js +4 -0
- package/lib/module/specs/ARView.nitro.js.map +1 -0
- package/lib/module/specs/ARVolume.nitro.js +4 -0
- package/lib/module/specs/ARVolume.nitro.js.map +1 -0
- package/lib/module/specs/ARWorldMap.nitro.js +4 -0
- package/lib/module/specs/ARWorldMap.nitro.js.map +1 -0
- package/lib/typescript/src/index.d.ts +20 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARAnchor.nitro.d.ts +18 -0
- package/lib/typescript/src/specs/ARAnchor.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARBoundingBoxBuilder.nitro.d.ts +11 -0
- package/lib/typescript/src/specs/ARBoundingBoxBuilder.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARDepthData.nitro.d.ts +26 -0
- package/lib/typescript/src/specs/ARDepthData.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARFrame.nitro.d.ts +32 -0
- package/lib/typescript/src/specs/ARFrame.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARLightEstimate.nitro.d.ts +18 -0
- package/lib/typescript/src/specs/ARLightEstimate.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARMeasurement.nitro.d.ts +11 -0
- package/lib/typescript/src/specs/ARMeasurement.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARPlaneAnchor.nitro.d.ts +32 -0
- package/lib/typescript/src/specs/ARPlaneAnchor.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARRaycastResult.nitro.d.ts +26 -0
- package/lib/typescript/src/specs/ARRaycastResult.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARSceneMesh.nitro.d.ts +47 -0
- package/lib/typescript/src/specs/ARSceneMesh.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARSession.nitro.d.ts +75 -0
- package/lib/typescript/src/specs/ARSession.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARView.nitro.d.ts +51 -0
- package/lib/typescript/src/specs/ARView.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARVolume.nitro.d.ts +14 -0
- package/lib/typescript/src/specs/ARVolume.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ARWorldMap.nitro.d.ts +17 -0
- package/lib/typescript/src/specs/ARWorldMap.nitro.d.ts.map +1 -0
- package/nitro.json +23 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/ios/NitroAR+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroAR-Swift-Cxx-Bridge.cpp +335 -0
- package/nitrogen/generated/ios/NitroAR-Swift-Cxx-Bridge.hpp +934 -0
- package/nitrogen/generated/ios/NitroAR-Swift-Cxx-Umbrella.hpp +169 -0
- package/nitrogen/generated/ios/NitroARAutolinking.mm +49 -0
- package/nitrogen/generated/ios/NitroARAutolinking.swift +50 -0
- package/nitrogen/generated/ios/c++/HybridARAnchorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARAnchorSpecSwift.hpp +99 -0
- package/nitrogen/generated/ios/c++/HybridARBoundingBoxBuilderSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARBoundingBoxBuilderSpecSwift.hpp +95 -0
- package/nitrogen/generated/ios/c++/HybridARDepthDataSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARDepthDataSpecSwift.hpp +103 -0
- package/nitrogen/generated/ios/c++/HybridARDirectionalLightEstimateSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARDirectionalLightEstimateSpecSwift.hpp +88 -0
- package/nitrogen/generated/ios/c++/HybridARFrameSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARFrameSpecSwift.hpp +135 -0
- package/nitrogen/generated/ios/c++/HybridARLightEstimateSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARLightEstimateSpecSwift.hpp +80 -0
- package/nitrogen/generated/ios/c++/HybridARMeasurementSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARMeasurementSpecSwift.hpp +90 -0
- package/nitrogen/generated/ios/c++/HybridARMeshAnchorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARMeshAnchorSpecSwift.hpp +107 -0
- package/nitrogen/generated/ios/c++/HybridARPlaneAnchorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARPlaneAnchorSpecSwift.hpp +116 -0
- package/nitrogen/generated/ios/c++/HybridARPlaneGeometrySpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARPlaneGeometrySpecSwift.hpp +90 -0
- package/nitrogen/generated/ios/c++/HybridARRaycastResultSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARRaycastResultSpecSwift.hpp +97 -0
- package/nitrogen/generated/ios/c++/HybridARSessionSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARSessionSpecSwift.hpp +296 -0
- package/nitrogen/generated/ios/c++/HybridARViewSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARViewSpecSwift.hpp +243 -0
- package/nitrogen/generated/ios/c++/HybridARVolumeSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARVolumeSpecSwift.hpp +94 -0
- package/nitrogen/generated/ios/c++/HybridARWorldMapSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridARWorldMapSpecSwift.hpp +97 -0
- package/nitrogen/generated/ios/c++/views/HybridARViewComponent.mm +152 -0
- package/nitrogen/generated/ios/swift/ARSessionConfiguration.swift +189 -0
- package/nitrogen/generated/ios/swift/ARViewHitResult.swift +39 -0
- package/nitrogen/generated/ios/swift/CameraPose.swift +46 -0
- package/nitrogen/generated/ios/swift/EnvironmentTexturing.swift +44 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_TrackingState_TrackingStateReason.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_HybridARFrameSpec_.swift +50 -0
- package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_HybridARWorldMapSpec_.swift +50 -0
- 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
- 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
- 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
- package/nitrogen/generated/ios/swift/HybridARAnchorSpec.swift +60 -0
- package/nitrogen/generated/ios/swift/HybridARAnchorSpec_cxx.swift +192 -0
- package/nitrogen/generated/ios/swift/HybridARBoundingBoxBuilderSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridARBoundingBoxBuilderSpec_cxx.swift +161 -0
- package/nitrogen/generated/ios/swift/HybridARDepthDataSpec.swift +59 -0
- package/nitrogen/generated/ios/swift/HybridARDepthDataSpec_cxx.swift +188 -0
- package/nitrogen/generated/ios/swift/HybridARDirectionalLightEstimateSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridARDirectionalLightEstimateSpec_cxx.swift +162 -0
- package/nitrogen/generated/ios/swift/HybridARFrameSpec.swift +65 -0
- package/nitrogen/generated/ios/swift/HybridARFrameSpec_cxx.swift +285 -0
- package/nitrogen/generated/ios/swift/HybridARLightEstimateSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridARLightEstimateSpec_cxx.swift +140 -0
- package/nitrogen/generated/ios/swift/HybridARMeasurementSpec.swift +58 -0
- package/nitrogen/generated/ios/swift/HybridARMeasurementSpec_cxx.swift +160 -0
- package/nitrogen/generated/ios/swift/HybridARMeshAnchorSpec.swift +62 -0
- package/nitrogen/generated/ios/swift/HybridARMeshAnchorSpec_cxx.swift +212 -0
- package/nitrogen/generated/ios/swift/HybridARPlaneAnchorSpec.swift +62 -0
- package/nitrogen/generated/ios/swift/HybridARPlaneAnchorSpec_cxx.swift +209 -0
- package/nitrogen/generated/ios/swift/HybridARPlaneGeometrySpec.swift +58 -0
- package/nitrogen/generated/ios/swift/HybridARPlaneGeometrySpec_cxx.swift +178 -0
- package/nitrogen/generated/ios/swift/HybridARRaycastResultSpec.swift +59 -0
- package/nitrogen/generated/ios/swift/HybridARRaycastResultSpec_cxx.swift +179 -0
- package/nitrogen/generated/ios/swift/HybridARSessionSpec.swift +78 -0
- package/nitrogen/generated/ios/swift/HybridARSessionSpec_cxx.swift +591 -0
- package/nitrogen/generated/ios/swift/HybridARViewSpec.swift +78 -0
- package/nitrogen/generated/ios/swift/HybridARViewSpec_cxx.swift +561 -0
- package/nitrogen/generated/ios/swift/HybridARVolumeSpec.swift +60 -0
- package/nitrogen/generated/ios/swift/HybridARVolumeSpec_cxx.swift +180 -0
- package/nitrogen/generated/ios/swift/HybridARWorldMapSpec.swift +58 -0
- package/nitrogen/generated/ios/swift/HybridARWorldMapSpec_cxx.swift +176 -0
- package/nitrogen/generated/ios/swift/LiDARCapabilities.swift +39 -0
- package/nitrogen/generated/ios/swift/MeshClassification.swift +64 -0
- package/nitrogen/generated/ios/swift/PlaneAlignment.swift +40 -0
- package/nitrogen/generated/ios/swift/PlaneClassification.swift +64 -0
- package/nitrogen/generated/ios/swift/PlaneDetectionMode.swift +40 -0
- package/nitrogen/generated/ios/swift/RaycastAlignment.swift +44 -0
- package/nitrogen/generated/ios/swift/RaycastQuery.swift +44 -0
- package/nitrogen/generated/ios/swift/RaycastTarget.swift +48 -0
- package/nitrogen/generated/ios/swift/SceneReconstructionMode.swift +44 -0
- package/nitrogen/generated/ios/swift/TrackingState.swift +44 -0
- package/nitrogen/generated/ios/swift/TrackingStateReason.swift +52 -0
- package/nitrogen/generated/ios/swift/WorldAlignment.swift +44 -0
- package/nitrogen/generated/ios/swift/WorldMappingStatus.swift +48 -0
- package/nitrogen/generated/shared/c++/ARSessionConfiguration.hpp +132 -0
- package/nitrogen/generated/shared/c++/ARViewHitResult.hpp +91 -0
- package/nitrogen/generated/shared/c++/CameraPose.hpp +87 -0
- package/nitrogen/generated/shared/c++/EnvironmentTexturing.hpp +80 -0
- package/nitrogen/generated/shared/c++/HybridARAnchorSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridARAnchorSpec.hpp +69 -0
- package/nitrogen/generated/shared/c++/HybridARBoundingBoxBuilderSpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridARBoundingBoxBuilderSpec.hpp +68 -0
- package/nitrogen/generated/shared/c++/HybridARDepthDataSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridARDepthDataSpec.hpp +66 -0
- package/nitrogen/generated/shared/c++/HybridARDirectionalLightEstimateSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridARDirectionalLightEstimateSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridARFrameSpec.cpp +32 -0
- package/nitrogen/generated/shared/c++/HybridARFrameSpec.hpp +83 -0
- package/nitrogen/generated/shared/c++/HybridARLightEstimateSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridARLightEstimateSpec.hpp +63 -0
- package/nitrogen/generated/shared/c++/HybridARMeasurementSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridARMeasurementSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridARMeshAnchorSpec.cpp +28 -0
- package/nitrogen/generated/shared/c++/HybridARMeshAnchorSpec.hpp +72 -0
- package/nitrogen/generated/shared/c++/HybridARPlaneAnchorSpec.cpp +28 -0
- package/nitrogen/generated/shared/c++/HybridARPlaneAnchorSpec.hpp +79 -0
- package/nitrogen/generated/shared/c++/HybridARPlaneGeometrySpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridARPlaneGeometrySpec.hpp +65 -0
- package/nitrogen/generated/shared/c++/HybridARRaycastResultSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridARRaycastResultSpec.hpp +70 -0
- package/nitrogen/generated/shared/c++/HybridARSessionSpec.cpp +45 -0
- package/nitrogen/generated/shared/c++/HybridARSessionSpec.hpp +131 -0
- package/nitrogen/generated/shared/c++/HybridARViewSpec.cpp +55 -0
- package/nitrogen/generated/shared/c++/HybridARViewSpec.hpp +101 -0
- package/nitrogen/generated/shared/c++/HybridARVolumeSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridARVolumeSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridARWorldMapSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridARWorldMapSpec.hpp +66 -0
- package/nitrogen/generated/shared/c++/LiDARCapabilities.hpp +91 -0
- package/nitrogen/generated/shared/c++/MeshClassification.hpp +100 -0
- package/nitrogen/generated/shared/c++/PlaneAlignment.hpp +76 -0
- package/nitrogen/generated/shared/c++/PlaneClassification.hpp +100 -0
- package/nitrogen/generated/shared/c++/PlaneDetectionMode.hpp +76 -0
- package/nitrogen/generated/shared/c++/RaycastAlignment.hpp +80 -0
- package/nitrogen/generated/shared/c++/RaycastQuery.hpp +99 -0
- package/nitrogen/generated/shared/c++/RaycastTarget.hpp +84 -0
- package/nitrogen/generated/shared/c++/SceneReconstructionMode.hpp +80 -0
- package/nitrogen/generated/shared/c++/TrackingState.hpp +80 -0
- package/nitrogen/generated/shared/c++/TrackingStateReason.hpp +88 -0
- package/nitrogen/generated/shared/c++/WorldAlignment.hpp +80 -0
- package/nitrogen/generated/shared/c++/WorldMappingStatus.hpp +84 -0
- package/nitrogen/generated/shared/c++/views/HybridARViewComponent.cpp +182 -0
- package/nitrogen/generated/shared/c++/views/HybridARViewComponent.hpp +120 -0
- package/nitrogen/generated/shared/json/ARViewConfig.json +19 -0
- package/package.json +98 -0
- package/react-native-nitro-ar.podspec +40 -0
- package/src/index.ts +60 -0
- package/src/specs/ARAnchor.nitro.ts +21 -0
- package/src/specs/ARBoundingBoxBuilder.nitro.ts +11 -0
- package/src/specs/ARDepthData.nitro.ts +29 -0
- package/src/specs/ARFrame.nitro.ts +41 -0
- package/src/specs/ARLightEstimate.nitro.ts +20 -0
- package/src/specs/ARMeasurement.nitro.ts +10 -0
- package/src/specs/ARPlaneAnchor.nitro.ts +46 -0
- package/src/specs/ARRaycastResult.nitro.ts +32 -0
- package/src/specs/ARSceneMesh.nitro.ts +63 -0
- package/src/specs/ARSession.nitro.ts +112 -0
- package/src/specs/ARView.nitro.ts +84 -0
- package/src/specs/ARVolume.nitro.ts +15 -0
- 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
|
+
}
|