react-native-image-stitcher 0.19.0 → 0.20.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/CHANGELOG.md +52 -0
- package/android/src/main/java/io/imagestitcher/rn/AROverlayRenderer.kt +406 -0
- package/android/src/main/java/io/imagestitcher/rn/AROverlayStore.kt +441 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraView.kt +290 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARCameraViewManager.kt +30 -5
- package/android/src/main/java/io/imagestitcher/rn/RNSARPluginRegistry.kt +68 -0
- package/android/src/main/java/io/imagestitcher/rn/RNSARSession.kt +99 -0
- package/dist/camera/ARCameraView.d.ts +33 -1
- package/dist/camera/ARCameraView.js +33 -2
- package/dist/camera/Camera.d.ts +45 -1
- package/dist/camera/Camera.js +24 -6
- package/dist/camera/arOverlayController.d.ts +52 -0
- package/dist/camera/arOverlayController.js +132 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +5 -2
- package/dist/stitching/AROverlay.d.ts +97 -0
- package/dist/stitching/AROverlay.js +4 -0
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.m +15 -8
- package/ios/Sources/RNImageStitcher/ARCameraViewManager.swift +22 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.m +14 -0
- package/ios/Sources/RNImageStitcher/ARSessionBridge.swift +81 -0
- package/ios/Sources/RNImageStitcher/RNISARFramePlugin.swift +37 -0
- package/ios/Sources/RNImageStitcher/RNISAROverlay.swift +409 -0
- package/ios/Sources/RNImageStitcher/RNSARCameraView.swift +301 -3
- package/ios/Sources/RNImageStitcher/RNSARSession.swift +70 -65
- package/package.json +1 -1
- package/src/camera/ARCameraView.tsx +73 -2
- package/src/camera/Camera.tsx +71 -2
- package/src/camera/arOverlayController.ts +184 -0
- package/src/index.ts +15 -0
- package/src/stitching/AROverlay.ts +105 -0
package/src/camera/Camera.tsx
CHANGED
|
@@ -41,8 +41,10 @@
|
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
43
|
import React, {
|
|
44
|
+
forwardRef,
|
|
44
45
|
useCallback,
|
|
45
46
|
useEffect,
|
|
47
|
+
useImperativeHandle,
|
|
46
48
|
useMemo,
|
|
47
49
|
useRef,
|
|
48
50
|
useState,
|
|
@@ -68,6 +70,8 @@ import type {
|
|
|
68
70
|
import { useARSession } from '../ar/useARSession';
|
|
69
71
|
import type { CameraFrameProcessor } from '../stitching/CameraFrame';
|
|
70
72
|
import type { ARFrameMeta, ARPluginResult } from '../stitching/ARFrameMeta';
|
|
73
|
+
import type { AROverlay } from '../stitching/AROverlay';
|
|
74
|
+
import type { AROverlayMethods } from './arOverlayController';
|
|
71
75
|
import { ARCameraView, type ARCameraViewHandle } from './ARCameraView';
|
|
72
76
|
import { CameraShutter } from './CameraShutter';
|
|
73
77
|
import { CameraView } from './CameraView';
|
|
@@ -834,6 +838,26 @@ export interface CameraProps {
|
|
|
834
838
|
*/
|
|
835
839
|
onArPluginResult?: (e: ARPluginResult) => void;
|
|
836
840
|
|
|
841
|
+
/**
|
|
842
|
+
* v0.20.0 — AR OVERLAY / ANNOTATION renderer. A declarative array of 2D
|
|
843
|
+
* shapes drawn ON TOP of the AR camera preview, each anchored to WORLD
|
|
844
|
+
* positions and REPROJECTED to screen on every AR frame from the current
|
|
845
|
+
* camera pose + intrinsics (smooth display-rate tracking, no 3D engine).
|
|
846
|
+
* Only meaningful in AR capture (`captureSource === 'ar'`); `<Camera>`
|
|
847
|
+
* threads this straight through to the underlying `<ARCameraView>`.
|
|
848
|
+
*
|
|
849
|
+
* State-driven: pass a React-state array and update it as your world points
|
|
850
|
+
* change (e.g. from {@link CameraProps.onArFrame} plane anchors). The set is
|
|
851
|
+
* diffed against the current overlays BY `id`. For zero-render-latency
|
|
852
|
+
* mutations use the imperative ref methods on the `<Camera>` handle instead
|
|
853
|
+
* ({@link CameraHandle}: `setOverlays` / `addOverlay` / `updateOverlay` /
|
|
854
|
+
* `removeOverlay` / `clearOverlays`) — both paths funnel through the same
|
|
855
|
+
* native channel. JS-set overlays merge on the native side with overlays a
|
|
856
|
+
* registered AR plugin placed directly (namespaced so neither clobbers the
|
|
857
|
+
* other). See {@link AROverlay} for the shape.
|
|
858
|
+
*/
|
|
859
|
+
overlays?: AROverlay[];
|
|
860
|
+
|
|
837
861
|
// ── Panorama GUIDANCE (feature/pano-ux-guidance) ──────────────────
|
|
838
862
|
/**
|
|
839
863
|
* Which device holds the non-AR panorama capture accepts.
|
|
@@ -922,6 +946,26 @@ export interface CameraProps {
|
|
|
922
946
|
}
|
|
923
947
|
|
|
924
948
|
|
|
949
|
+
/**
|
|
950
|
+
* v0.20.0 — imperative handle exposed via the `<Camera>` ref.
|
|
951
|
+
*
|
|
952
|
+
* Currently scoped to the AR-overlay methods ({@link AROverlayMethods}:
|
|
953
|
+
* `setOverlays` / `addOverlay` / `updateOverlay` / `removeOverlay` /
|
|
954
|
+
* `clearOverlays`), which forward to the underlying `<ARCameraView>`'s overlay
|
|
955
|
+
* channel when AR mode is mounted. They are no-ops while the camera is in
|
|
956
|
+
* non-AR mode (no `<ARCameraView>` is mounted, and overlays only render over
|
|
957
|
+
* the AR preview) — use the declarative {@link CameraProps.overlays} prop for
|
|
958
|
+
* a set that survives AR↔non-AR transitions, since it re-applies automatically
|
|
959
|
+
* whenever `<ARCameraView>` (re)mounts.
|
|
960
|
+
*
|
|
961
|
+
* The shape is identical to {@link ARCameraViewHandle}'s overlay subset so a
|
|
962
|
+
* host can use either component with the same overlay code. Photo / panorama
|
|
963
|
+
* capture remain driven by the built-in shutter (no imperative capture methods
|
|
964
|
+
* on this handle — see the component docstring's scope note).
|
|
965
|
+
*/
|
|
966
|
+
export interface CameraHandle extends AROverlayMethods {}
|
|
967
|
+
|
|
968
|
+
|
|
925
969
|
// ─── Sub-components ─────────────────────────────────────────────────
|
|
926
970
|
|
|
927
971
|
/**
|
|
@@ -1207,8 +1251,15 @@ function extractPanoramaOverrides(props: CameraProps): PanoramaPropOverrides {
|
|
|
1207
1251
|
|
|
1208
1252
|
/**
|
|
1209
1253
|
* The public `<Camera>` component.
|
|
1254
|
+
*
|
|
1255
|
+
* v0.20.0 — now a `forwardRef`. The ref exposes {@link CameraHandle} (the AR
|
|
1256
|
+
* overlay methods); existing callers that don't pass a ref are unaffected
|
|
1257
|
+
* (`forwardRef` makes the ref optional).
|
|
1210
1258
|
*/
|
|
1211
|
-
export
|
|
1259
|
+
export const Camera = forwardRef<CameraHandle, CameraProps>(function Camera(
|
|
1260
|
+
props: CameraProps,
|
|
1261
|
+
ref,
|
|
1262
|
+
): React.JSX.Element {
|
|
1212
1263
|
const {
|
|
1213
1264
|
defaultCaptureSource = 'non-ar',
|
|
1214
1265
|
defaultLens = '1x',
|
|
@@ -1248,6 +1299,7 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
1248
1299
|
onArFrame,
|
|
1249
1300
|
arFrameMetaInterval,
|
|
1250
1301
|
onArPluginResult,
|
|
1302
|
+
overlays,
|
|
1251
1303
|
engine = 'batch-keyframe',
|
|
1252
1304
|
// ── Panorama GUIDANCE (feature/pano-ux-guidance) ──────────────
|
|
1253
1305
|
panMode = 'vertical',
|
|
@@ -1511,6 +1563,22 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
1511
1563
|
const visionCameraRef = useRef<VisionCamera | null>(null);
|
|
1512
1564
|
const arViewRef = useRef<ARCameraViewHandle | null>(null);
|
|
1513
1565
|
|
|
1566
|
+
// v0.20.0 — AR overlay imperative handle. `<Camera>` itself renders no
|
|
1567
|
+
// overlay layer; the overlay methods forward to the mounted
|
|
1568
|
+
// `<ARCameraView>`'s handle (which owns the controller + native dispatch).
|
|
1569
|
+
// No-op when AR mode isn't mounted (`arViewRef.current === null`), matching
|
|
1570
|
+
// the CameraHandle docstring — the declarative `overlays` prop is the path
|
|
1571
|
+
// that survives AR↔non-AR transitions. The `overlays` prop is also threaded
|
|
1572
|
+
// straight to `<ARCameraView>` below, so a host can use either API.
|
|
1573
|
+
useImperativeHandle(ref, (): CameraHandle => ({
|
|
1574
|
+
setOverlays: (o) => arViewRef.current?.setOverlays(o),
|
|
1575
|
+
addOverlay: (o) => arViewRef.current?.addOverlay(o),
|
|
1576
|
+
updateOverlay: (id, patch) => arViewRef.current?.updateOverlay(id, patch),
|
|
1577
|
+
removeOverlay: (id) => arViewRef.current?.removeOverlay(id),
|
|
1578
|
+
clearOverlays: () => arViewRef.current?.clearOverlays(),
|
|
1579
|
+
raycast: () => arViewRef.current?.raycast() ?? Promise.resolve(null),
|
|
1580
|
+
}), []);
|
|
1581
|
+
|
|
1514
1582
|
// Effect that does the async transition work whenever the settled
|
|
1515
1583
|
// refs disagree with the current isAR/lens. Order matters:
|
|
1516
1584
|
// 1. Set the cameraTransitioning state so the gate stays closed
|
|
@@ -2519,6 +2587,7 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
2519
2587
|
onArFrame={onArFrame}
|
|
2520
2588
|
arFrameMetaInterval={arFrameMetaInterval}
|
|
2521
2589
|
onArPluginResult={onArPluginResult}
|
|
2590
|
+
overlays={overlays}
|
|
2522
2591
|
/>
|
|
2523
2592
|
) : (
|
|
2524
2593
|
<CameraView
|
|
@@ -3015,7 +3084,7 @@ export function Camera(props: CameraProps): React.JSX.Element {
|
|
|
3015
3084
|
/>
|
|
3016
3085
|
</View>
|
|
3017
3086
|
);
|
|
3018
|
-
}
|
|
3087
|
+
});
|
|
3019
3088
|
|
|
3020
3089
|
|
|
3021
3090
|
function noop(): void {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* v0.20.0 — shared JS→native plumbing for the AR overlay renderer.
|
|
5
|
+
*
|
|
6
|
+
* `<ARCameraView>` and `<Camera>` expose an IDENTICAL imperative overlay API
|
|
7
|
+
* (`setOverlays` / `addOverlay` / `updateOverlay` / `removeOverlay` /
|
|
8
|
+
* `clearOverlays`) plus a declarative `overlays` prop. Rather than duplicate
|
|
9
|
+
* the diff + native-dispatch logic in each component, both build their handle
|
|
10
|
+
* from {@link createAROverlayController} — DRY, single source of truth for the
|
|
11
|
+
* wire format and the merge-by-id semantics.
|
|
12
|
+
*
|
|
13
|
+
* ## Native mechanism (agreed cross-platform contract)
|
|
14
|
+
*
|
|
15
|
+
* Every AR-session setting in this SDK already flows through the
|
|
16
|
+
* `RNSARSession` native-module singleton (`setPlaneDetection`,
|
|
17
|
+
* `setArFrameMetaEnabled`, `setSceneReconstructionEnabled`) because
|
|
18
|
+
* `RNSARSession.shared` drives the single mounted `RNSARCameraView`. Overlays
|
|
19
|
+
* follow the same pattern: a single `setOverlays(overlays)` method on
|
|
20
|
+
* `RNSARSession` carries the FULL current JS-set overlay array each time it
|
|
21
|
+
* changes. Native replaces its JS-set overlay collection wholesale (the merge
|
|
22
|
+
* with the namespaced native-plugin set happens on the native side) and the
|
|
23
|
+
* overlay layer redraws every AR frame.
|
|
24
|
+
*
|
|
25
|
+
* The declarative `overlays` prop and the imperative methods both ultimately
|
|
26
|
+
* call this same `setOverlays` with the resolved array, so the two APIs are
|
|
27
|
+
* interchangeable and can't diverge.
|
|
28
|
+
*
|
|
29
|
+
* Why a module method (not a UIManager view command)? It matches every other
|
|
30
|
+
* AR setting in this codebase and there is only ever ONE `RNSARCameraView`
|
|
31
|
+
* mounted (ARKit/ARCore can't share the camera), so there's nothing to key by
|
|
32
|
+
* view tag. The equivalent UIManager view-command name, for native sides that
|
|
33
|
+
* prefer per-view dispatch, is documented as `RNSARCameraViewOverlays`.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { NativeModules } from 'react-native';
|
|
37
|
+
|
|
38
|
+
import type { AROverlay } from '../stitching/AROverlay';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The imperative overlay methods exposed on both `<ARCameraView>` and
|
|
42
|
+
* `<Camera>` refs. Identical shape on both so a host can swap components
|
|
43
|
+
* without rewriting overlay code.
|
|
44
|
+
*/
|
|
45
|
+
export interface AROverlayMethods {
|
|
46
|
+
/** Replace the entire JS-set overlay collection. */
|
|
47
|
+
setOverlays: (overlays: AROverlay[]) => void;
|
|
48
|
+
/** Add one overlay (replaces any existing overlay with the same `id`). */
|
|
49
|
+
addOverlay: (overlay: AROverlay) => void;
|
|
50
|
+
/**
|
|
51
|
+
* Shallow-merge a patch into the overlay with `id`. No-op if no overlay
|
|
52
|
+
* with that `id` is currently set.
|
|
53
|
+
*/
|
|
54
|
+
updateOverlay: (id: string, patch: Partial<AROverlay>) => void;
|
|
55
|
+
/** Remove the overlay with `id` (no-op if absent). */
|
|
56
|
+
removeOverlay: (id: string) => void;
|
|
57
|
+
/** Remove all JS-set overlays. */
|
|
58
|
+
clearOverlays: () => void;
|
|
59
|
+
/**
|
|
60
|
+
* Raycast from the screen centre (the crosshair) to the first real-world
|
|
61
|
+
* surface and resolve its world position `[x, y, z]` in metres (ARKit/ARCore
|
|
62
|
+
* world frame), or `null` when nothing is hit (e.g. a featureless wall before
|
|
63
|
+
* any plane is detected). Use it to place an overlay ON the aimed surface at
|
|
64
|
+
* the real distance — pass the result as a `worldPosition` to
|
|
65
|
+
* {@link setOverlays} / {@link addOverlay} — instead of guessing a distance.
|
|
66
|
+
* Resolves `null` (never throws) when the native module / method is absent.
|
|
67
|
+
*/
|
|
68
|
+
raycast: () => Promise<[number, number, number] | null>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface RNSARSessionOverlayModule {
|
|
72
|
+
// On iOS the native `setOverlays` is a Promise method (resolver/rejecter
|
|
73
|
+
// RN-injected); on Android it's `void`. We call it fire-and-forget but
|
|
74
|
+
// type it as possibly-thenable so the defensive `.catch` below compiles.
|
|
75
|
+
setOverlays?: (overlays: AROverlay[]) => void | Promise<unknown>;
|
|
76
|
+
// Raycast resolves `{ worldPosition: [x,y,z] }` on a hit, or `null`.
|
|
77
|
+
raycast?: () => Promise<{ worldPosition?: number[] } | null>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The `RNSARSession` native-module method name overlays dispatch through. */
|
|
81
|
+
export const AR_OVERLAY_SET_METHOD = 'setOverlays' as const;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The agreed UIManager view-command name for native sides that drive overlays
|
|
85
|
+
* via per-view command dispatch instead of the module method. The JS layer
|
|
86
|
+
* dispatches through the module method (there's only one AR view), but the
|
|
87
|
+
* name is pinned here so the native side can match if it chooses commands.
|
|
88
|
+
*/
|
|
89
|
+
export const AR_OVERLAY_VIEW_COMMAND = 'RNSARCameraViewOverlays' as const;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build an overlay controller backed by an in-memory ordered set keyed by
|
|
93
|
+
* `id`. Every mutating call resolves the new full array and pushes it to
|
|
94
|
+
* native via `RNSARSession.setOverlays`. The controller is the single source
|
|
95
|
+
* of truth for BOTH the imperative ref methods and the declarative `overlays`
|
|
96
|
+
* prop (the prop's effect calls `setOverlays` with the prop value).
|
|
97
|
+
*/
|
|
98
|
+
export function createAROverlayController(): AROverlayMethods & {
|
|
99
|
+
/** Current JS-set overlays in insertion order (used by tests / diffing). */
|
|
100
|
+
getOverlays: () => AROverlay[];
|
|
101
|
+
} {
|
|
102
|
+
// Insertion-ordered map: preserves the order overlays were added so the
|
|
103
|
+
// native render order is stable + predictable.
|
|
104
|
+
const overlaysById = new Map<string, AROverlay>();
|
|
105
|
+
|
|
106
|
+
const flush = (): void => {
|
|
107
|
+
const native = (NativeModules as Record<string, unknown>)
|
|
108
|
+
.RNSARSession as RNSARSessionOverlayModule | undefined;
|
|
109
|
+
// Native module / method unavailable (web, or a native build predating the
|
|
110
|
+
// overlay channel): no-op, no crash — mirrors the other AR setters.
|
|
111
|
+
const ret = native?.setOverlays?.(Array.from(overlaysById.values()));
|
|
112
|
+
// iOS returns a Promise (the native method is Promise-typed); swallow any
|
|
113
|
+
// rejection so a transient native error never surfaces as an unhandled
|
|
114
|
+
// rejection. Android returns void — the optional chain skips the catch.
|
|
115
|
+
(ret as Promise<unknown> | undefined)?.catch?.(() => undefined);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
getOverlays: () => Array.from(overlaysById.values()),
|
|
120
|
+
|
|
121
|
+
setOverlays: (overlays: AROverlay[]) => {
|
|
122
|
+
overlaysById.clear();
|
|
123
|
+
for (const o of overlays) {
|
|
124
|
+
// Last-writer-wins on duplicate ids in the incoming array.
|
|
125
|
+
overlaysById.set(o.id, o);
|
|
126
|
+
}
|
|
127
|
+
flush();
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
addOverlay: (overlay: AROverlay) => {
|
|
131
|
+
// Re-set to move an existing id to the end? No — preserve original slot
|
|
132
|
+
// by deleting first only when absent. Map#set keeps the existing slot
|
|
133
|
+
// when the key already exists, so a plain set is the right "replace in
|
|
134
|
+
// place" behaviour.
|
|
135
|
+
overlaysById.set(overlay.id, overlay);
|
|
136
|
+
flush();
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
updateOverlay: (id: string, patch: Partial<AROverlay>) => {
|
|
140
|
+
const existing = overlaysById.get(id);
|
|
141
|
+
if (existing == null) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Shallow-merge; `id` is preserved from the existing overlay regardless
|
|
145
|
+
// of what the patch carries (the map key must stay consistent).
|
|
146
|
+
overlaysById.set(id, { ...existing, ...patch, id });
|
|
147
|
+
flush();
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
removeOverlay: (id: string) => {
|
|
151
|
+
if (overlaysById.delete(id)) {
|
|
152
|
+
flush();
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
clearOverlays: () => {
|
|
157
|
+
if (overlaysById.size > 0) {
|
|
158
|
+
overlaysById.clear();
|
|
159
|
+
flush();
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
raycast: async (): Promise<[number, number, number] | null> => {
|
|
164
|
+
const native = (NativeModules as Record<string, unknown>)
|
|
165
|
+
.RNSARSession as RNSARSessionOverlayModule | undefined;
|
|
166
|
+
const fn = native?.raycast;
|
|
167
|
+
// Native module / method unavailable (web, or a native build predating
|
|
168
|
+
// the raycast channel): resolve null — the caller falls back.
|
|
169
|
+
if (typeof fn !== 'function') {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const res = await fn();
|
|
174
|
+
const wp = res?.worldPosition;
|
|
175
|
+
if (Array.isArray(wp) && wp.length >= 3) {
|
|
176
|
+
return [Number(wp[0]), Number(wp[1]), Number(wp[2])];
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
export { Camera, CameraError } from './camera/Camera';
|
|
28
28
|
export type {
|
|
29
29
|
CameraProps,
|
|
30
|
+
CameraHandle,
|
|
30
31
|
CameraCaptureResult,
|
|
31
32
|
PanoramaCaptureResult,
|
|
32
33
|
CameraErrorCode,
|
|
@@ -272,6 +273,20 @@ export type { ARFrameMeta } from './stitching/ARFrameMeta';
|
|
|
272
273
|
// `onArPluginResult` callback (a plugin's out-of-band `registry.emit(...)`
|
|
273
274
|
// result). The SDK ships only the generic framework — no built-in plugins.
|
|
274
275
|
export type { ARPluginResult } from './stitching/ARFrameMeta';
|
|
276
|
+
// v0.20.0 — AR OVERLAY / ANNOTATION renderer data model. A 2D shape anchored
|
|
277
|
+
// to a world point (or world quad) and reprojected to screen every AR frame.
|
|
278
|
+
// Drive it via the declarative `overlays` prop or the imperative ref methods
|
|
279
|
+
// (`setOverlays` / `addOverlay` / `updateOverlay` / `removeOverlay` /
|
|
280
|
+
// `clearOverlays`) on both `<Camera>` and `<ARCameraView>`.
|
|
281
|
+
export type { AROverlay } from './stitching/AROverlay';
|
|
282
|
+
// The shared imperative-overlay method signatures (the `<Camera>` /
|
|
283
|
+
// `<ARCameraView>` ref handles extend this). Plus the agreed native channel
|
|
284
|
+
// names, for hosts / native plugins matching the wire contract.
|
|
285
|
+
export type { AROverlayMethods } from './camera/arOverlayController';
|
|
286
|
+
export {
|
|
287
|
+
AR_OVERLAY_SET_METHOD,
|
|
288
|
+
AR_OVERLAY_VIEW_COMMAND,
|
|
289
|
+
} from './camera/arOverlayController';
|
|
275
290
|
// NOTE: the host-worklet / frame-stream hooks `useFrameProcessor`,
|
|
276
291
|
// `useThrottledFrameProcessor` and `useFrameStream` (v0.8–v0.9) were
|
|
277
292
|
// archived in the batch-keyframe cleanup — they drove the third-party
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* v0.20.0 — the AR OVERLAY / ANNOTATION renderer's data model.
|
|
5
|
+
*
|
|
6
|
+
* An {@link AROverlay} describes a 2D shape (a billboard marker/box or a
|
|
7
|
+
* world-anchored quad) that the native overlay layer draws ON TOP of the AR
|
|
8
|
+
* camera preview (`RNSARCameraView`). Each overlay is anchored to WORLD
|
|
9
|
+
* positions and REPROJECTED to screen on EVERY AR frame from the current
|
|
10
|
+
* camera pose + intrinsics — so it tracks the scene at display rate with no
|
|
11
|
+
* 3D-engine dependency.
|
|
12
|
+
*
|
|
13
|
+
* ## Two ways to anchor an overlay
|
|
14
|
+
*
|
|
15
|
+
* 1. **A single world point** (`worldPosition`) — drawn as a billboard
|
|
16
|
+
* marker/box facing the camera, sized by `sizeMeters` (default a small
|
|
17
|
+
* marker). Use this for a pin on a detected plane anchor, a label on a
|
|
18
|
+
* point of interest, etc.
|
|
19
|
+
* 2. **Explicit world corners** (`worldQuad`, 3–4 points) — drawn as the
|
|
20
|
+
* outline/box connecting the projected corners. Use this for a detected
|
|
21
|
+
* quad (a shelf face, a packet, a door) whose real-world shape you
|
|
22
|
+
* already know.
|
|
23
|
+
*
|
|
24
|
+
* Provide ONE of the two anchor forms. If both are present `worldQuad` wins
|
|
25
|
+
* (it's the more specific description); the native renderers read `worldQuad`
|
|
26
|
+
* first and fall back to `worldPosition` + `sizeMeters`.
|
|
27
|
+
*
|
|
28
|
+
* ## Rendering / reprojection (native)
|
|
29
|
+
*
|
|
30
|
+
* The native side reprojects each overlay's world point(s) to screen with the
|
|
31
|
+
* AR framework's BUILT-IN, correct projection — iOS
|
|
32
|
+
* `ARFrame.camera.projectPoint(_:orientation:viewportSize:)`, Android
|
|
33
|
+
* `viewMatrix · projectionMatrix` → clip → NDC → screen. Points behind the
|
|
34
|
+
* camera or off-screen are hidden. The layer redraws every frame so the
|
|
35
|
+
* outline/box + label stay pinned to the world as the camera moves.
|
|
36
|
+
*
|
|
37
|
+
* ## Where overlays come from
|
|
38
|
+
*
|
|
39
|
+
* Overlays reach the native renderer through two INDEPENDENT, merged sets:
|
|
40
|
+
*
|
|
41
|
+
* - **JS-set** — the declarative `overlays` prop or the imperative ref
|
|
42
|
+
* methods (`setOverlays` / `addOverlay` / `updateOverlay` / `removeOverlay`
|
|
43
|
+
* / `clearOverlays`) on `<Camera>` and `<ARCameraView>`.
|
|
44
|
+
* - **Native-plugin-set** — a registered AR plugin places overlays directly
|
|
45
|
+
* via the 0.19 registry (`RNISARPluginRegistry.setOverlays(...)` on iOS /
|
|
46
|
+
* `RNSARPluginRegistry.setOverlays(...)` on Android), with zero JS latency.
|
|
47
|
+
*
|
|
48
|
+
* The native renderer draws the UNION of both sets; the plugin set is
|
|
49
|
+
* namespaced so a JS `setOverlays(...)` never clobbers plugin overlays.
|
|
50
|
+
*/
|
|
51
|
+
export interface AROverlay {
|
|
52
|
+
/**
|
|
53
|
+
* Stable identifier. The declarative `overlays` prop diffs the incoming
|
|
54
|
+
* array against the current set BY `id` (add / update / remove); the
|
|
55
|
+
* imperative `updateOverlay` / `removeOverlay` methods key off it too. Must
|
|
56
|
+
* be unique within a set.
|
|
57
|
+
*/
|
|
58
|
+
id: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Anchor form 1 — a single world point in METRES (world space `[x, y, z]`).
|
|
62
|
+
* Drawn as a billboard marker/box of `sizeMeters` extent facing the camera.
|
|
63
|
+
* Ignored when `worldQuad` is provided.
|
|
64
|
+
*/
|
|
65
|
+
worldPosition?: [number, number, number];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Box extent in METRES `[width, height]` at `worldPosition`. Only meaningful
|
|
69
|
+
* with `worldPosition`. Defaults to a small marker on the native side when
|
|
70
|
+
* omitted.
|
|
71
|
+
*/
|
|
72
|
+
sizeMeters?: [number, number];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Anchor form 2 — 3 or 4 explicit world corners in METRES (e.g. a detected
|
|
76
|
+
* quad). Each corner is `[x, y, z]` in world space. Drawn as the
|
|
77
|
+
* outline/box connecting the projected corners. Takes precedence over
|
|
78
|
+
* `worldPosition` + `sizeMeters` when both are present.
|
|
79
|
+
*/
|
|
80
|
+
worldQuad?: Array<[number, number, number]>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Draw style. Default `'outline'` (stroked edges). `'box'` is a filled /
|
|
84
|
+
* boxed marker. Both render in 2D this release.
|
|
85
|
+
*/
|
|
86
|
+
shape?: 'box' | 'outline';
|
|
87
|
+
|
|
88
|
+
/** Optional text label drawn at the overlay's anchor point. */
|
|
89
|
+
label?: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Stroke / fill colour as a hex string (e.g. `'#00E5FF'`). Defaults to a
|
|
93
|
+
* theme colour on the native side when omitted.
|
|
94
|
+
*/
|
|
95
|
+
color?: string;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Render mode. Default `'2d'` — a flat shape reprojected to screen.
|
|
99
|
+
* `'3d'` is SCAFFOLD ONLY this release: the data-model field exists and the
|
|
100
|
+
* native renderers leave a marked hook for a future SceneKit (iOS) / Android
|
|
101
|
+
* 3D renderer, but v1 treats `'3d'` as `'2d'` (with a one-time native log
|
|
102
|
+
* warning). Document-only forward compatibility.
|
|
103
|
+
*/
|
|
104
|
+
mode?: '2d' | '3d';
|
|
105
|
+
}
|