react-native-image-stitcher 0.16.2 → 0.18.0
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/CHANGELOG.md +154 -0
- package/RNImageStitcher.podspec +26 -1
- package/android/build.gradle +20 -0
- package/android/src/main/cpp/CMakeLists.txt +46 -3
- package/android/src/main/cpp/stitcher_jsi_install_jni.cpp +436 -0
- package/android/src/main/java/io/imagestitcher/rn/RNImageStitcherPackage.kt +6 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +711 -6
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +156 -0
- package/android/src/main/java/io/imagestitcher/rn/StitcherJsiInstallerModule.kt +103 -0
- package/android/src/main/java/io/imagestitcher/rn/StitcherWorkletRuntime.kt +338 -0
- package/cpp/{stitcher_frame_data.hpp → camera_frame_data.hpp} +96 -13
- package/cpp/camera_frame_jsi.cpp +357 -0
- package/cpp/camera_frame_jsi.hpp +108 -0
- package/cpp/stitcher_proxy_jsi.cpp +140 -0
- package/cpp/stitcher_proxy_jsi.hpp +62 -0
- package/cpp/stitcher_worklet_dispatch.cpp +103 -0
- package/cpp/stitcher_worklet_dispatch.hpp +71 -0
- package/cpp/stitcher_worklet_registry.cpp +91 -0
- package/cpp/stitcher_worklet_registry.hpp +146 -0
- package/dist/camera/ARCameraView.d.ts +77 -0
- package/dist/camera/ARCameraView.js +90 -1
- package/dist/camera/Camera.d.ts +63 -4
- package/dist/camera/Camera.js +2 -2
- package/dist/camera/CaptureMemoryPill.d.ts +4 -3
- package/dist/camera/CaptureMemoryPill.js +4 -3
- package/dist/index.d.ts +2 -1
- package/dist/stitching/ARFrameMeta.d.ts +100 -0
- package/dist/stitching/{StitcherFrame.js → ARFrameMeta.js} +1 -1
- package/dist/stitching/{StitcherFrame.d.ts → CameraFrame.d.ts} +70 -11
- package/dist/stitching/CameraFrame.js +4 -0
- package/dist/stitching/ensureStitcherProxyInstalled.d.ts +8 -0
- package/dist/stitching/ensureStitcherProxyInstalled.js +81 -0
- package/dist/stitching/useStitcherWorklet.d.ts +4 -4
- package/dist/stitching/useStitcherWorklet.js +4 -4
- package/ios/Sources/RNImageStitcher/ARSessionBridge.m +23 -1
- package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +137 -2
- package/ios/Sources/RNImageStitcher/CameraFrameHostObject.h +83 -0
- package/ios/Sources/RNImageStitcher/CameraFrameHostObject.mm +760 -0
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +336 -40
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.h +128 -0
- package/ios/Sources/RNImageStitcher/RNSARWorkletRuntime.mm +313 -0
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.h +42 -0
- package/ios/Sources/RNImageStitcher/StitcherJsiInstaller.mm +160 -0
- package/package.json +1 -1
- package/src/camera/ARCameraView.tsx +211 -2
- package/src/camera/Camera.tsx +81 -4
- package/src/camera/CaptureMemoryPill.tsx +4 -3
- package/src/index.ts +7 -3
- package/src/stitching/ARFrameMeta.ts +107 -0
- package/src/stitching/{StitcherFrame.ts → CameraFrame.ts} +79 -11
- package/src/stitching/ensureStitcherProxyInstalled.ts +141 -0
- package/src/stitching/useStitcherWorklet.ts +9 -9
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ensureStitcherProxyInstalled = ensureStitcherProxyInstalled;
|
|
5
|
+
exports._resetStitcherProxyInstallStateForTests = _resetStitcherProxyInstallStateForTests;
|
|
6
|
+
const react_native_1 = require("react-native");
|
|
7
|
+
/**
|
|
8
|
+
* `__DEV__` is RN's global dev-flag. Guard the read with `typeof`
|
|
9
|
+
* so the helper works in any environment that imports it without
|
|
10
|
+
* defining __DEV__ (jest, SSR, custom tooling). Same pattern RN's
|
|
11
|
+
* own debug code uses.
|
|
12
|
+
*/
|
|
13
|
+
function isDev() {
|
|
14
|
+
return typeof __DEV__ !== 'undefined' && __DEV__;
|
|
15
|
+
}
|
|
16
|
+
let installed = false;
|
|
17
|
+
function ensureStitcherProxyInstalled() {
|
|
18
|
+
if (installed)
|
|
19
|
+
return true;
|
|
20
|
+
// Already installed by an earlier hook mount. Cheap fast-path.
|
|
21
|
+
if (typeof globalThis.__stitcherProxy !== 'undefined') {
|
|
22
|
+
installed = true;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
const mod = react_native_1.NativeModules
|
|
26
|
+
.StitcherJsiInstaller;
|
|
27
|
+
if (mod == null || typeof mod.install !== 'function') {
|
|
28
|
+
// Module not present — Android until Phase 4b.ii lands, or
|
|
29
|
+
// an old iOS build. Surface this once at debug-info level so
|
|
30
|
+
// the host can see "your worklets are JS-registered only" in
|
|
31
|
+
// logcat / Console.app without a noisy per-frame warning.
|
|
32
|
+
if (isDev() && !warnedAboutMissingModule) {
|
|
33
|
+
warnedAboutMissingModule = true;
|
|
34
|
+
console.info('[react-native-image-stitcher] StitcherJsiInstaller native ' +
|
|
35
|
+
'module not found; host worklets registered in JS-side ' +
|
|
36
|
+
'registry only. AR-mode dispatch requires the native install ' +
|
|
37
|
+
'(iOS Phase 4b.i — included in v0.8.0; Android Phase 4b.ii ' +
|
|
38
|
+
'— follow-up release).');
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const ok = mod.install();
|
|
44
|
+
if (!ok) {
|
|
45
|
+
// Native module ran but couldn't install (JSI runtime
|
|
46
|
+
// unreachable). Same fallback as the missing-module case.
|
|
47
|
+
if (isDev() && !warnedAboutFailedInstall) {
|
|
48
|
+
warnedAboutFailedInstall = true;
|
|
49
|
+
console.info('[react-native-image-stitcher] StitcherJsiInstaller.install() ' +
|
|
50
|
+
'returned false (JSI runtime unreachable — remote debug ' +
|
|
51
|
+
'mode?). Falling back to JS-side host worklet registry.');
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
installed = true;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isDev() && !warnedAboutFailedInstall) {
|
|
60
|
+
warnedAboutFailedInstall = true;
|
|
61
|
+
console.info('[react-native-image-stitcher] StitcherJsiInstaller.install() ' +
|
|
62
|
+
'threw: ' +
|
|
63
|
+
String(err) +
|
|
64
|
+
'. Falling back to JS-side host worklet registry.');
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
let warnedAboutMissingModule = false;
|
|
70
|
+
let warnedAboutFailedInstall = false;
|
|
71
|
+
/**
|
|
72
|
+
* Test-only — reset module-internal state. Used by jest to allow
|
|
73
|
+
* multiple test cases to re-trigger the install path independently.
|
|
74
|
+
* NOT exported from `src/index.ts`.
|
|
75
|
+
*/
|
|
76
|
+
function _resetStitcherProxyInstallStateForTests() {
|
|
77
|
+
installed = false;
|
|
78
|
+
warnedAboutMissingModule = false;
|
|
79
|
+
warnedAboutFailedInstall = false;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=ensureStitcherProxyInstalled.js.map
|
|
@@ -106,17 +106,17 @@
|
|
|
106
106
|
* their own worklet via this hook must do the wiring themselves.
|
|
107
107
|
*/
|
|
108
108
|
import type { Frame } from 'react-native-vision-camera';
|
|
109
|
-
import type {
|
|
109
|
+
import type { CameraFrame } from './CameraFrame';
|
|
110
110
|
/**
|
|
111
111
|
* Frames the lib's stitching worklet accepts. Accepting either a
|
|
112
112
|
* vc `Frame` (what the host's `useFrameProcessor` body sees) or the
|
|
113
|
-
* lib's `
|
|
113
|
+
* lib's `CameraFrame` (what the lib's `useFrameProcessor` body
|
|
114
114
|
* sees) keeps the same `useStitcherWorklet` usable from both kinds
|
|
115
115
|
* of host worklet bodies without a cast on the call site. The
|
|
116
116
|
* worklet only reads `width` / `height`; the rest of the frame
|
|
117
117
|
* object is forwarded verbatim to the native plugin.
|
|
118
118
|
*/
|
|
119
|
-
export type StitcherWorkletInput = Frame |
|
|
119
|
+
export type StitcherWorkletInput = Frame | CameraFrame;
|
|
120
120
|
export interface UseStitcherWorkletOptions {
|
|
121
121
|
/**
|
|
122
122
|
* Gyro sample interval in ms (~30 Hz default). Drives the JS-
|
|
@@ -145,7 +145,7 @@ export interface UseStitcherWorkletOptions {
|
|
|
145
145
|
}
|
|
146
146
|
export interface StitcherWorkletHandle {
|
|
147
147
|
/**
|
|
148
|
-
* Worklet function: pass a `
|
|
148
|
+
* Worklet function: pass a `CameraFrame` to perform one frame of
|
|
149
149
|
* the lib's first-party stitching (throttle + pose synthesis +
|
|
150
150
|
* native plugin call). Safe to call from inside another
|
|
151
151
|
* `'worklet'`-prefixed function (this is the canonical
|
|
@@ -228,7 +228,7 @@ function useStitcherWorklet(options = {}) {
|
|
|
228
228
|
// party callback installed in `RNSARWorkletRuntime`). Calling
|
|
229
229
|
// the vc Frame Processor plugin here would throw
|
|
230
230
|
// `getPropertyAsObject: property '__frame' is undefined`
|
|
231
|
-
// because AR frames are `
|
|
231
|
+
// because AR frames are `CameraFrameHostObject` instances
|
|
232
232
|
// and don't carry the vc `Frame` proxy's JSI marker. The
|
|
233
233
|
// throw is caught silently by the per-worklet error handler
|
|
234
234
|
// (`RNSARWorkletRuntime.mm:284-301`) and bubbles up only to
|
|
@@ -240,12 +240,12 @@ function useStitcherWorklet(options = {}) {
|
|
|
240
240
|
// hook (the AR-side stitching path runs natively, independent
|
|
241
241
|
// of the composed worklet body).
|
|
242
242
|
//
|
|
243
|
-
// The `(frame as
|
|
243
|
+
// The `(frame as CameraFrame).source` cast is safe: vc
|
|
244
244
|
// `Frame` doesn't carry a `source` property so the check
|
|
245
245
|
// returns `undefined !== 'ar'` → `true`, and the worklet
|
|
246
246
|
// proceeds normally. Only frames that explicitly tag
|
|
247
247
|
// themselves as AR-source (which our native AR dispatcher
|
|
248
|
-
// does — see `
|
|
248
|
+
// does — see `CameraFrameHostObject.mm`) get short-circuited.
|
|
249
249
|
if (frame.source === 'ar')
|
|
250
250
|
return;
|
|
251
251
|
// Throttle (verbatim from useFrameProcessorDriver).
|
|
@@ -273,7 +273,7 @@ function useStitcherWorklet(options = {}) {
|
|
|
273
273
|
const fx = w * sharedFxNumerator.value;
|
|
274
274
|
const fy = h * sharedFyNumerator.value;
|
|
275
275
|
// vc's `plugin.call` is typed against vc's `Frame`. The worklet
|
|
276
|
-
// accepts the union (`Frame |
|
|
276
|
+
// accepts the union (`Frame | CameraFrame`); cast through
|
|
277
277
|
// `unknown` because the union doesn't satisfy vc's interface
|
|
278
278
|
// even though structurally both members do.
|
|
279
279
|
plugin.call(frame, {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// imports as `NativeModules.RNSARSession`.
|
|
6
6
|
|
|
7
7
|
#import <React/RCTBridgeModule.h>
|
|
8
|
+
#import <React/RCTEventEmitter.h>
|
|
8
9
|
|
|
9
10
|
// REMAP form, NOT EXTERN_MODULE. The Swift singleton in
|
|
10
11
|
// RNSARSession.swift takes the @objc name "RNSARSession"
|
|
@@ -20,7 +21,14 @@
|
|
|
20
21
|
// `RNSARSessionBridge` and dispatch methods against THAT
|
|
21
22
|
// class — where takePhoto / startRecording / stopRecording etc.
|
|
22
23
|
// actually live.
|
|
23
|
-
|
|
24
|
+
//
|
|
25
|
+
// v0.18.0 — base class is now `RCTEventEmitter` (was `NSObject`) so the
|
|
26
|
+
// "RNSARSession" module can emit the `RNImageStitcherARFrame` device
|
|
27
|
+
// event for the `onArFrame` channel. RN auto-provides the
|
|
28
|
+
// `addListener:` / `removeListeners:` emitter selectors for a module
|
|
29
|
+
// whose remap base is RCTEventEmitter; the Swift class supplies
|
|
30
|
+
// `supportedEvents` / `startObserving` / `stopObserving`.
|
|
31
|
+
@interface RCT_EXTERN_REMAP_MODULE(RNSARSession, RNSARSessionBridge, RCTEventEmitter)
|
|
24
32
|
|
|
25
33
|
RCT_EXTERN_METHOD(isSupported:(RCTPromiseResolveBlock)resolver
|
|
26
34
|
rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
@@ -34,6 +42,20 @@ RCT_EXTERN_METHOD(stop:(RCTPromiseResolveBlock)resolver
|
|
|
34
42
|
RCT_EXTERN_METHOD(getState:(RCTPromiseResolveBlock)resolver
|
|
35
43
|
rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
36
44
|
|
|
45
|
+
RCT_EXTERN_METHOD(setSceneReconstructionEnabled:(nonnull NSNumber *)enabled
|
|
46
|
+
resolver:(RCTPromiseResolveBlock)resolver
|
|
47
|
+
rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
48
|
+
|
|
49
|
+
RCT_EXTERN_METHOD(setPlaneDetection:(nonnull NSString *)mode
|
|
50
|
+
resolver:(RCTPromiseResolveBlock)resolver
|
|
51
|
+
rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
52
|
+
|
|
53
|
+
// v0.18.0 — toggle the onArFrame LIGHT-metadata channel + its throttle.
|
|
54
|
+
RCT_EXTERN_METHOD(setArFrameMetaEnabled:(nonnull NSNumber *)enabled
|
|
55
|
+
intervalMs:(nonnull NSNumber *)intervalMs
|
|
56
|
+
resolver:(RCTPromiseResolveBlock)resolver
|
|
57
|
+
rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
58
|
+
|
|
37
59
|
RCT_EXTERN_METHOD(snapshotPoseLog:(RCTPromiseResolveBlock)resolver
|
|
38
60
|
rejecter:(RCTPromiseRejectBlock)rejecter)
|
|
39
61
|
|
|
@@ -17,15 +17,109 @@
|
|
|
17
17
|
import Foundation
|
|
18
18
|
import React
|
|
19
19
|
|
|
20
|
+
// v0.18.0 — `RNSARSessionBridge` is now an `RCTEventEmitter` (was a
|
|
21
|
+
// plain `NSObject`) so it can deliver the `onArFrame` LIGHT-metadata
|
|
22
|
+
// channel as the JS `RNImageStitcherARFrame` device event. The JS side
|
|
23
|
+
// subscribes via `new NativeEventEmitter(NativeModules.RNSARSession)` —
|
|
24
|
+
// the same module name this bridge is remapped to (see ARSessionBridge.m).
|
|
25
|
+
//
|
|
26
|
+
// Pattern mirrors `IncrementalStitcherBridge`: observe a NotificationCenter
|
|
27
|
+
// post from the framework-free `RNSARSession` engine, then re-emit on the
|
|
28
|
+
// main queue via `bridge.enqueueJSCall("RCTDeviceEventEmitter", "emit", …)`
|
|
29
|
+
// rather than `RCTEventEmitter.sendEvent(…)`, because under RN bridgeless
|
|
30
|
+
// interop `sendEvent` silently no-ops for some event-body shapes (see the
|
|
31
|
+
// IncrementalStitcherBridge.handleStateUpdate docstring).
|
|
20
32
|
@objc(RNSARSessionBridge)
|
|
21
|
-
public final class RNSARSessionBridge:
|
|
33
|
+
public final class RNSARSessionBridge: RCTEventEmitter {
|
|
34
|
+
|
|
35
|
+
/// Whether at least one JS listener is attached to the AR-frame event.
|
|
36
|
+
/// RN's EventEmitter contract: don't emit when no listeners are
|
|
37
|
+
/// registered. Toggled by `startObserving` / `stopObserving`.
|
|
38
|
+
private var hasListeners: Bool = false
|
|
39
|
+
|
|
40
|
+
private static let arFrameEvent = "RNImageStitcherARFrame"
|
|
41
|
+
|
|
42
|
+
public override init() {
|
|
43
|
+
super.init()
|
|
44
|
+
// Defensively de-dupe the observer: under RN bridgeless interop a
|
|
45
|
+
// bridge's init() can run twice on the same instance. Remove any
|
|
46
|
+
// prior registration for this notification before adding, so the
|
|
47
|
+
// observer fires at most once per post regardless.
|
|
48
|
+
NotificationCenter.default.removeObserver(
|
|
49
|
+
self,
|
|
50
|
+
name: .retailensARFrameMeta,
|
|
51
|
+
object: nil
|
|
52
|
+
)
|
|
53
|
+
NotificationCenter.default.addObserver(
|
|
54
|
+
self,
|
|
55
|
+
selector: #selector(handleArFrameMeta(_:)),
|
|
56
|
+
name: .retailensARFrameMeta,
|
|
57
|
+
object: nil
|
|
58
|
+
)
|
|
59
|
+
}
|
|
22
60
|
|
|
23
|
-
|
|
61
|
+
deinit {
|
|
62
|
+
NotificationCenter.default.removeObserver(self)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// MARK: - RCTEventEmitter protocol
|
|
66
|
+
|
|
67
|
+
public override static func requiresMainQueueSetup() -> Bool {
|
|
24
68
|
// ARSession.start() must be called on the main thread —
|
|
25
69
|
// ARKit needs to attach to the active CVDisplayLink.
|
|
26
70
|
return true
|
|
27
71
|
}
|
|
28
72
|
|
|
73
|
+
public override func supportedEvents() -> [String]! {
|
|
74
|
+
return [Self.arFrameEvent]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public override func startObserving() {
|
|
78
|
+
hasListeners = true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public override func stopObserving() {
|
|
82
|
+
hasListeners = false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// Forward a posted `ARFrameMeta` dictionary to JS as the
|
|
86
|
+
/// `RNImageStitcherARFrame` device event. Dropped when no JS listener
|
|
87
|
+
/// is attached. Emits via `enqueueJSCall` on the main queue (see the
|
|
88
|
+
/// class docstring for why not `sendEvent`).
|
|
89
|
+
@objc private func handleArFrameMeta(_ notification: Notification) {
|
|
90
|
+
guard hasListeners else { return }
|
|
91
|
+
guard let userInfo = notification.userInfo else { return }
|
|
92
|
+
DispatchQueue.main.async { [weak self] in
|
|
93
|
+
guard let self = self, let bridge = self.bridge else { return }
|
|
94
|
+
bridge.enqueueJSCall(
|
|
95
|
+
"RCTDeviceEventEmitter",
|
|
96
|
+
method: "emit",
|
|
97
|
+
args: [Self.arFrameEvent, userInfo],
|
|
98
|
+
completion: nil
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// MARK: - Module methods
|
|
104
|
+
|
|
105
|
+
/// v0.18.0 — toggle the `onArFrame` LIGHT-metadata channel. Called
|
|
106
|
+
/// from JS with `true` + the throttle interval (ms) when a host
|
|
107
|
+
/// supplies `<Camera onArFrame={...}>`, and `false` on
|
|
108
|
+
/// unmount / prop-removal. Resolves with no value.
|
|
109
|
+
@objc(setArFrameMetaEnabled:intervalMs:resolver:rejecter:)
|
|
110
|
+
public func setArFrameMetaEnabled(
|
|
111
|
+
enabled: NSNumber,
|
|
112
|
+
intervalMs: NSNumber,
|
|
113
|
+
resolver: @escaping RCTPromiseResolveBlock,
|
|
114
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
115
|
+
) {
|
|
116
|
+
RNSARSession.shared.setArFrameMetaEnabled(
|
|
117
|
+
enabled.boolValue,
|
|
118
|
+
intervalMs: intervalMs.doubleValue
|
|
119
|
+
)
|
|
120
|
+
resolver(nil)
|
|
121
|
+
}
|
|
122
|
+
|
|
29
123
|
@objc(isSupported:rejecter:)
|
|
30
124
|
public func isSupported(
|
|
31
125
|
resolver: @escaping RCTPromiseResolveBlock,
|
|
@@ -68,6 +162,47 @@ public final class RNSARSessionBridge: NSObject {
|
|
|
68
162
|
])
|
|
69
163
|
}
|
|
70
164
|
|
|
165
|
+
/// Toggle ARKit scene reconstruction (LiDAR mesh / `ARMeshAnchor`s).
|
|
166
|
+
/// Driven by the <Camera> `enableMesh` prop; gates the
|
|
167
|
+
/// StitcherFrame `meshGeometry` extraction at the SESSION level
|
|
168
|
+
/// (the per-frame `__stitcherProxy.setExtractionConfig(...mesh)`
|
|
169
|
+
/// gates the marshaling — both must be on for a host to receive
|
|
170
|
+
/// mesh). Resolves with no value.
|
|
171
|
+
///
|
|
172
|
+
/// Hops to the main queue: `setSceneReconstructionEnabled` may call
|
|
173
|
+
/// `arSession.run(config)` to reconfigure a live session, and
|
|
174
|
+
/// ARKit session lifecycle must run on the main thread (same
|
|
175
|
+
/// constraint as `start`).
|
|
176
|
+
@objc(setSceneReconstructionEnabled:resolver:rejecter:)
|
|
177
|
+
public func setSceneReconstructionEnabled(
|
|
178
|
+
enabled: NSNumber,
|
|
179
|
+
resolver: @escaping RCTPromiseResolveBlock,
|
|
180
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
181
|
+
) {
|
|
182
|
+
let on = enabled.boolValue
|
|
183
|
+
DispatchQueue.main.async {
|
|
184
|
+
RNSARSession.shared.setSceneReconstructionEnabled(on)
|
|
185
|
+
resolver(nil)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Hops to the main queue: `setPlaneDetection` may call
|
|
190
|
+
/// `arSession.run(config)` to reconfigure a live session, and ARKit
|
|
191
|
+
/// session lifecycle must run on the main thread (same constraint as
|
|
192
|
+
/// `start` / `setSceneReconstructionEnabled`).
|
|
193
|
+
@objc(setPlaneDetection:resolver:rejecter:)
|
|
194
|
+
public func setPlaneDetection(
|
|
195
|
+
mode: NSString,
|
|
196
|
+
resolver: @escaping RCTPromiseResolveBlock,
|
|
197
|
+
rejecter: @escaping RCTPromiseRejectBlock
|
|
198
|
+
) {
|
|
199
|
+
let m = mode as String
|
|
200
|
+
DispatchQueue.main.async {
|
|
201
|
+
RNSARSession.shared.setPlaneDetection(m)
|
|
202
|
+
resolver(nil)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
71
206
|
@objc(snapshotPoseLog:rejecter:)
|
|
72
207
|
public func snapshotPoseLog(
|
|
73
208
|
resolver: @escaping RCTPromiseResolveBlock,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// CameraFrameHostObject.h — Obj-C facade for the v0.8.0
|
|
4
|
+
// `StitcherFrame` JSI host object. Header is intentionally
|
|
5
|
+
// Obj-C-only (no `<jsi/jsi.h>` import) so this can land in the
|
|
6
|
+
// public CocoaPods umbrella without breaking `use_frameworks!` hosts
|
|
7
|
+
// (same rationale as `KeyframeGateBridge.h`).
|
|
8
|
+
//
|
|
9
|
+
// The C++ JSI host object class lives in the .mm; this facade
|
|
10
|
+
// exposes only what cross-module callers need:
|
|
11
|
+
//
|
|
12
|
+
// - Factory `+ fromARFrame:pose:` that the AR worklet runtime
|
|
13
|
+
// calls per ARFrame to construct a host object backed by the
|
|
14
|
+
// current AR session's frame.
|
|
15
|
+
// - Opaque accessor `- (void *)jsiHostObjectPtr` returning the
|
|
16
|
+
// `std::shared_ptr<facebook::jsi::HostObject> *` (boxed) that
|
|
17
|
+
// the worklet runtime hands to `jsi::Object::createFromHostObject`.
|
|
18
|
+
//
|
|
19
|
+
// Lifetime: the Obj-C wrapper holds the C++ shared_ptr; ARC frees
|
|
20
|
+
// the wrapper when nothing references it. Worklet runtime
|
|
21
|
+
// invalidates the underlying ARFrame retain when the dispatch
|
|
22
|
+
// returns; after invalidation, JSI access throws.
|
|
23
|
+
|
|
24
|
+
#pragma once
|
|
25
|
+
|
|
26
|
+
#import <Foundation/Foundation.h>
|
|
27
|
+
#import <ARKit/ARKit.h>
|
|
28
|
+
|
|
29
|
+
@class RNSARFramePose;
|
|
30
|
+
|
|
31
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
32
|
+
|
|
33
|
+
NS_SWIFT_NAME(CameraFrameHostObject)
|
|
34
|
+
@interface CameraFrameHostObject : NSObject
|
|
35
|
+
|
|
36
|
+
/// Construct a host object backed by the supplied ARFrame + pose.
|
|
37
|
+
/// Retains the ARFrame for the host object's lifetime — caller can
|
|
38
|
+
/// safely release their reference.
|
|
39
|
+
///
|
|
40
|
+
/// Thread: safe to call from the ARSession delegate queue; the
|
|
41
|
+
/// resulting host object's JSI access must happen on the worklet
|
|
42
|
+
/// runtime's thread (separate queue).
|
|
43
|
+
+ (instancetype)fromARFrame:(ARFrame *)arFrame pose:(RNSARFramePose *)pose;
|
|
44
|
+
|
|
45
|
+
/// Mark the host object's underlying ARFrame as no longer accessible.
|
|
46
|
+
/// Subsequent JSI property reads return `undefined` or throw,
|
|
47
|
+
/// depending on the property. Idempotent.
|
|
48
|
+
- (void)invalidate;
|
|
49
|
+
|
|
50
|
+
/// Opaque pointer to a `std::shared_ptr<facebook::jsi::HostObject>`.
|
|
51
|
+
/// The worklet runtime (Obj-C++ context with JSI available) casts
|
|
52
|
+
/// this back via `*reinterpret_cast<std::shared_ptr<facebook::jsi::HostObject>*>(ptr)`
|
|
53
|
+
/// to hand to `jsi::Object::createFromHostObject`.
|
|
54
|
+
///
|
|
55
|
+
/// Returns `NULL` if the host object has been invalidated.
|
|
56
|
+
- (nullable void *)jsiHostObjectPtr;
|
|
57
|
+
|
|
58
|
+
/// Build the LIGHT per-frame AR metadata dictionary for the `onArFrame`
|
|
59
|
+
/// callback (the `ARFrameMeta` TS shape). Distinct from the full
|
|
60
|
+
/// host-object factory above: this copies NO pixel / vertex / face bytes
|
|
61
|
+
/// — only scalars, dimensions, anchor transforms, and mesh COUNTS — so
|
|
62
|
+
/// it's cheap enough to run at the throttled `onArFrame` cadence.
|
|
63
|
+
///
|
|
64
|
+
/// Gating mirrors the full extraction path: `depth` only when the JS
|
|
65
|
+
/// `enableDepth` flag is on (read from the shared C++ extraction config),
|
|
66
|
+
/// `anchors` only when `enableAnchors`, `mesh` (counts) only when
|
|
67
|
+
/// `enableMesh`. `intrinsics` / `pose` / `trackingState` / `timestamp`
|
|
68
|
+
/// are always populated. `intrinsics` is `NSNull` only when the frame
|
|
69
|
+
/// reported a degenerate (zero) resolution.
|
|
70
|
+
///
|
|
71
|
+
/// Returns a JSON-safe `NSDictionary` (NSNumber / NSString / NSArray /
|
|
72
|
+
/// NSDictionary / NSNull leaves) ready to hand to
|
|
73
|
+
/// `bridge.enqueueJSCall("RCTDeviceEventEmitter", "emit", ...)`.
|
|
74
|
+
///
|
|
75
|
+
/// Thread: safe to call from the ARSession delegate queue (reads the
|
|
76
|
+
/// frame synchronously; copies nothing that outlives the call).
|
|
77
|
+
+ (NSDictionary *)lightArFrameMetaFromARFrame:(ARFrame *)arFrame
|
|
78
|
+
pose:(RNSARFramePose *)pose
|
|
79
|
+
NS_SWIFT_NAME(lightArFrameMeta(from:pose:));
|
|
80
|
+
|
|
81
|
+
@end
|
|
82
|
+
|
|
83
|
+
NS_ASSUME_NONNULL_END
|