react-native-cesium 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +77 -9
  2. package/android/src/main/cpp/CesiumBridgeAndroid.cpp +88 -1
  3. package/android/src/main/cpp/CesiumBridgeAndroid.h +11 -1
  4. package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +35 -0
  5. package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +55 -0
  6. package/cpp/engine/GlobeCamera.cpp +22 -0
  7. package/cpp/engine/GlobeCamera.hpp +3 -0
  8. package/ios/CesiumBridge.h +15 -0
  9. package/ios/CesiumBridge.mm +68 -7
  10. package/ios/HybridCesiumView.swift +56 -0
  11. package/lib/typescript/src/index.d.ts +1 -1
  12. package/lib/typescript/src/index.d.ts.map +1 -1
  13. package/lib/typescript/src/specs/cesium-view.nitro.d.ts +21 -1
  14. package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
  15. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +24 -0
  16. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +2 -0
  17. package/nitrogen/generated/android/c++/JQuaternion.hpp +69 -0
  18. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +8 -0
  19. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Quaternion.kt +47 -0
  20. package/nitrogen/generated/ios/ReactNativeCesium+autolinking.rb +2 -0
  21. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.cpp +8 -0
  22. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +46 -0
  23. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Umbrella.hpp +3 -0
  24. package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +17 -0
  25. package/nitrogen/generated/ios/swift/Func_void_Quaternion.swift +46 -0
  26. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +2 -0
  27. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +30 -0
  28. package/nitrogen/generated/ios/swift/Quaternion.swift +44 -0
  29. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +2 -0
  30. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +5 -0
  31. package/nitrogen/generated/shared/c++/Quaternion.hpp +95 -0
  32. package/package.json +1 -1
  33. package/src/index.ts +1 -0
  34. package/src/specs/cesium-view.nitro.ts +22 -1
package/README.md CHANGED
@@ -18,13 +18,26 @@ Experimental Cesium rendering for React Native using [Nitro Modules](https://git
18
18
  ## Requirements
19
19
 
20
20
  - React Native **new architecture**
21
+ - React Native Nitro Modules is a dependency
21
22
  - Follow the native build instructions below: additional setup is required by you.
22
23
  - A valid Cesium Ion access token and asset IDs for your content (get these from https://ion.cesium.com)
23
24
  - **Disk and time:** building Cesium Native pulls **vcpkg** dependencies, can use **several GB** of disk, and often takes a long time on first build.
24
25
 
25
26
  ## Installation
26
27
 
27
- Add the package to your app:
28
+ First install the Nitro Modules runtime dependency:
29
+
30
+ ```bash
31
+ yarn add react-native-nitro-modules
32
+ ```
33
+
34
+ or:
35
+
36
+ ```bash
37
+ npm install react-native-nitro-modules
38
+ ```
39
+
40
+ Then add `react-native-cesium`:
28
41
 
29
42
  ```bash
30
43
  yarn add react-native-cesium
@@ -123,22 +136,54 @@ The **post-install** helper is currently needed to:
123
136
  - prepend this package's `cpp/` headers before the locally built XCFramework headers
124
137
  - exclude `x86_64` iOS simulator builds, because the simulator slice is Apple Silicon `arm64` only
125
138
 
139
+ ### Android (Gradle automatic ensure-native)
140
+
141
+ Unlike iOS, you **do not** need to copy anything into your **application’s** `android/build.gradle` (or `settings.gradle`) so Cesium Native can be cloned and built automatically. React Native already includes this package as an Android library dependency; this repo’s **`android/build.gradle`** registers **`ensureCesiumNativeAndroid`** and makes **`preBuild`** depend on it.
142
+
143
+ What happens on **`./gradlew`** / Android Studio build:
144
+
145
+ 1. **`preBuild`** runs **`ensureCesiumNativeAndroid`** before CMake compiles the JNI/native code.
146
+ 2. That task checks for a marker file under the installed package:
147
+ `vendor/android/share/cesium-native/cmake/cesium-nativeConfig.cmake`.
148
+ 3. If the marker **exists**, the task does nothing.
149
+ 4. If the marker is **missing**, it runs (from the package root, e.g. `node_modules/react-native-cesium`):
150
+ `node scripts/cesium/ensure-native.mjs --android`
151
+ which may run **`update`** then a **`CESIUM_BUILD_ONLY=android`** **`build`**, same as a manual run (can take a long time).
152
+ 5. If **`REACT_NATIVE_CESIUM_SKIP_NATIVE_BUILD=1`** is set and artifacts are still missing, the build **fails fast** with an error instead of running the script.
153
+
154
+ You still need a discoverable **Android NDK** (expand **Additional Android specific requirements** under [System dependencies](#system-dependencies) above) so that build can succeed.
155
+
126
156
  ### Building
127
157
 
128
158
  **Automatic (default):** assuming you have done the above and have all the required dependencies in place, when the native output is missing, the package tries to build it for you:
129
159
 
130
160
  - **iOS:** add **`pre_install`** in your app **`Podfile`** (one-time; see [Required Podfile hooks](#required-podfile-hooks-ios)) so **`pod install`** runs **`scripts/cesium/ensure-native.mjs --ios`**, which runs **`npm run update`** / **`yarn run update`** (if needed) and then **`CESIUM_BUILD_ONLY=ios`** build. The first run can take **a long time** and needs the same **system dependencies** as a manual build (CMake, Ninja, Xcode, disk space, etc.).
131
- - **Android:** the library’s Gradle **`preBuild`** runs **`ensure-native.mjs --android`** if **`vendor/android`** is incomplete, which runs **`CESIUM_BUILD_ONLY=android`** build. Requires **ANDROID_NDK_HOME** (or an NDK the build script can find).
161
+ - **Android:** no extra hooks in your app; see [Android (Gradle automatic ensure-native)](#android-gradle-automatic-ensure-native). Needs a discoverable **NDK**.
162
+
163
+ **Manual (optional):** run the **`update`** and **`build`** scripts defined in **`react-native-cesium`’s `package.json`**. They are **not** available as top-level commands in your app; you must run them **in the context of the installed package**.
132
164
 
133
- **Manual (optional):** from the package directory (e.g. `node_modules/react-native-cesium`):
165
+ From your **application project root** (where your app’s `package.json` lives), after `yarn add` / `npm install`:
166
+
167
+ **Yarn:**
168
+
169
+ ```bash
170
+ yarn --cwd node_modules/react-native-cesium run update
171
+ yarn --cwd node_modules/react-native-cesium run build
172
+ ```
173
+
174
+ **npm:**
134
175
 
135
176
  ```bash
136
- yarn run update # fetches Cesium Native source locally from GitHub
137
- yarn run build # builds iOS and Android artifacts; can take a long time
177
+ npm run update --prefix node_modules/react-native-cesium
178
+ npm run build --prefix node_modules/react-native-cesium
138
179
  ```
139
180
 
140
- - `npm run update` / `yarn run update` checks out **Cesium Native `v0.59.0`** into `vendor/cesium-native` (created next to the package files; typically ignored in app repos).
141
- - `npm run build` / `yarn run build` runs `scripts/cesium/build.mjs` (**CMake**, **vcpkg** under `vendor/vcpkg` unless **`VCPKG_ROOT`** is set, **Ninja** on macOS) and writes **`vendor/ios/CesiumNative.xcframework`** and/or **`vendor/android`** depending on **`CESIUM_BUILD_ONLY`**.
181
+ **Alternative:** `cd node_modules/react-native-cesium` and run `yarn run update` / `yarn run build` (or the equivalent `npm run …`).
182
+
183
+ **Yarn/npm workspaces:** if `react-native-cesium` is a workspace package in your monorepo, use your tool’s workspace runner, e.g. `yarn workspace react-native-cesium run update` (exact syntax depends on your workspace layout).
184
+
185
+ - `update` checks out **Cesium Native `v0.59.0`** into `vendor/cesium-native` under the package (next to its other files; typically ignored in app repos).
186
+ - `build` runs `scripts/cesium/build.mjs` (**CMake**, **vcpkg** under `vendor/vcpkg` unless **`VCPKG_ROOT`** is set, **Ninja** on macOS) and writes **`vendor/ios/CesiumNative.xcframework`** and/or **`vendor/android`** depending on **`CESIUM_BUILD_ONLY`**.
142
187
 
143
188
  Then install pods (iOS):
144
189
 
@@ -152,6 +197,7 @@ The public JS surface is exported from `react-native-cesium`:
152
197
 
153
198
  - `CesiumView`
154
199
  - `CameraState`
200
+ - `Quaternion`
155
201
  - `CesiumMetrics`
156
202
  - `CesiumViewProps`
157
203
  - `CesiumViewMethods`
@@ -266,12 +312,34 @@ type CameraState = {
266
312
  | `roll` | `number` | Degrees | Bank/rotation around forward axis. Positive right bank, negative left bank. |
267
313
  | `verticalFovDeg` | `number` | `20..100` (clamped) | Vertical field of view. Example: `30` zooms in/narrows view; `90` gives wider peripheral view with more perspective distortion. |
268
314
 
315
+ ### `Quaternion`
316
+
317
+ Used for **camera-space view correction** with `setCameraQuaternion` (see below). Components are `w, x, y, z` (Hamilton convention). Non-unit values are normalized on the native side.
318
+
319
+ ```ts
320
+ type Quaternion = {
321
+ w: number
322
+ x: number
323
+ y: number
324
+ z: number
325
+ }
326
+ ```
327
+
269
328
  ### `CesiumViewMethods`
270
329
 
271
330
  | Method | Signature | Description |
272
331
  | --- | --- |-----------------------------------------------------------------------------------------------------------------------------------------------|
273
- | `setCamera` | `(camera: CameraState) => void` | Applies a new camera state at runtime. Example: animate route steps by calling `setCamera(...)` on each step. |
274
- | `getCameraState` | `() => Promise<CameraState>` | Returns the current native camera snapshot. |
332
+ | `setCamera` | `(camera: CameraState) => void` | Applies a new camera state at runtime (position, heading, pitch, roll, vertical FOV). Does **not** change the stored view-correction quaternion; use `setCameraQuaternion` when you need to update that. |
333
+ | `setCameraQuaternion` | `(camera: CameraState, viewCorrection: Quaternion) => void` | Same camera fields as `setCamera`, plus a rotation applied **in camera space after** heading/pitch/roll. Use this for screen-fixed adjustments (e.g. boresight calibration, aligning a synthetic horizon overlay) without expressing the offset as extra Euler angles. |
334
+ | `getCameraState` | `() => Promise<CameraState>` | Returns the current native camera snapshot (lat/lon/alt, HPR, VFOV). This is the underlying globe attitude; it does **not** encode the view correction into HPR. |
335
+ | `getViewCorrection` | `() => Promise<Quaternion>` | Returns the **smoothed** view-correction quaternion currently applied (identity `w=1,x=y=z=0` if you have never called `setCameraQuaternion`). |
336
+
337
+ #### `setCamera` vs `setCameraQuaternion`
338
+
339
+ - Use **`setCamera`** alone when you only need the classic globe camera.
340
+ - Use **`setCameraQuaternion`** when you need both the usual `CameraState` **and** a small rotation relative to the uncorrected camera (HUD alignment, horizon line offset in screen space, lens/display calibration, etc.).
341
+ - You may **mix** the two: calling `setCamera` updates position and HPR but leaves the last view-correction target unchanged, so a calibration quaternion set earlier continues to apply until you change it with `setCameraQuaternion`.
342
+ - Prefer **`setCameraQuaternion`** on frames where you need to drive both the globe camera and the correction together so the native target stays consistent.
275
343
 
276
344
  ### `CesiumMetrics`
277
345
 
@@ -6,6 +6,8 @@
6
6
  #include <android/native_window_jni.h>
7
7
  #include <android/log.h>
8
8
 
9
+ #include <glm/gtc/quaternion.hpp>
10
+
9
11
  #include <cmath>
10
12
  #include <regex>
11
13
  #include <sstream>
@@ -19,9 +21,15 @@ static const double kSmoothAlt = 25.0;
19
21
  static const double kSmoothHdg = 30.0;
20
22
  static const double kSmoothRoll = 50.0;
21
23
  static const double kSmoothPitch = 50.0;
24
+ static const double kSmoothViewCorr = 50.0;
22
25
  static const double kEpsLatLon = 1e-7;
23
26
  static const double kEpsAlt = 0.1;
24
27
  static const double kEpsAngleDeg = 0.05;
28
+ static const double kEpsQuatDot = 1e-8;
29
+
30
+ static double quatDotAbs(const glm::dquat& a, const glm::dquat& b) {
31
+ return std::abs(glm::dot(glm::normalize(a), glm::normalize(b)));
32
+ }
25
33
 
26
34
  static double lerpAngleDeg(double a, double b, double t) {
27
35
  double diff = std::fmod(b - a + 540.0, 360.0) - 180.0;
@@ -89,6 +97,10 @@ void CesiumBridgeAndroid::buildEngine() {
89
97
  camTargetHeading_ = params.heading;
90
98
  camTargetPitch_ = params.pitch;
91
99
  camTargetRoll_ = params.roll;
100
+ camTargetViewQw_ = params.viewCorrection.w;
101
+ camTargetViewQx_ = params.viewCorrection.x;
102
+ camTargetViewQy_ = params.viewCorrection.y;
103
+ camTargetViewQz_ = params.viewCorrection.z;
92
104
 
93
105
  auto* backendPtr = vulkanBackend_.get();
94
106
  engine_->getResourcePreparer()->setGPUTextureCreator(
@@ -155,6 +167,32 @@ void CesiumBridgeAndroid::updateCamera(double lat, double lon, double alt,
155
167
  camTargetHeading_ = heading;
156
168
  camTargetPitch_ = pitch;
157
169
  camTargetRoll_ = roll;
170
+ // Does not change camTargetViewQ* (view correction target).
171
+ forceRenderNextFrame_ = true;
172
+ }
173
+
174
+ void CesiumBridgeAndroid::updateCameraQuaternion(double lat, double lon, double alt,
175
+ double heading, double pitch, double roll,
176
+ double qw, double qx, double qy, double qz) {
177
+ if (!initialized_) return;
178
+ camTargetLat_ = lat;
179
+ camTargetLon_ = lon;
180
+ camTargetAlt_ = alt;
181
+ camTargetHeading_ = heading;
182
+ camTargetPitch_ = pitch;
183
+ camTargetRoll_ = roll;
184
+ const double ql2 = qw * qw + qx * qx + qy * qy + qz * qz;
185
+ glm::dquat q;
186
+ if (ql2 < 1e-20) {
187
+ q = glm::dquat(1.0, 0.0, 0.0, 0.0);
188
+ } else {
189
+ const double inv = 1.0 / std::sqrt(ql2);
190
+ q = glm::dquat(qw * inv, qx * inv, qy * inv, qz * inv);
191
+ }
192
+ camTargetViewQw_ = q.w;
193
+ camTargetViewQx_ = q.x;
194
+ camTargetViewQy_ = q.y;
195
+ camTargetViewQz_ = q.z;
158
196
  forceRenderNextFrame_ = true;
159
197
  }
160
198
 
@@ -204,13 +242,15 @@ bool CesiumBridgeAndroid::shouldRenderNextFrame() {
204
242
  if (!initialized_ || !engine_) return false;
205
243
 
206
244
  const auto cur = engine_->camera().getParams();
245
+ glm::dquat tgt(camTargetViewQw_, camTargetViewQx_, camTargetViewQy_, camTargetViewQz_);
207
246
  const bool cameraDirty =
208
247
  std::abs(camTargetLat_ - cur.latitude) > kEpsLatLon ||
209
248
  std::abs(camTargetLon_ - cur.longitude) > kEpsLatLon ||
210
249
  std::abs(camTargetAlt_ - cur.altitude) > kEpsAlt ||
211
250
  angleDeltaAbsDeg(cur.heading, camTargetHeading_) > kEpsAngleDeg ||
212
251
  angleDeltaAbsDeg(cur.pitch, camTargetPitch_) > kEpsAngleDeg ||
213
- angleDeltaAbsDeg(cur.roll, camTargetRoll_) > kEpsAngleDeg;
252
+ angleDeltaAbsDeg(cur.roll, camTargetRoll_) > kEpsAngleDeg ||
253
+ (1.0 - quatDotAbs(cur.viewCorrection, tgt)) > kEpsQuatDot;
214
254
 
215
255
  return forceRenderNextFrame_ ||
216
256
  frameResult_->tilesLoading > 0 ||
@@ -231,12 +271,19 @@ void CesiumBridgeAndroid::renderFrame(double dt) {
231
271
  const double aHdg = 1.0 - std::exp(-kSmoothHdg * dt);
232
272
  const double aPitch = 1.0 - std::exp(-kSmoothPitch * dt);
233
273
  const double aRoll = 1.0 - std::exp(-kSmoothRoll * dt);
274
+ const double aViewQ = 1.0 - std::exp(-kSmoothViewCorr * dt);
234
275
 
235
276
  cur.altitude = cur.altitude + aAlt * (camTargetAlt_ - cur.altitude);
236
277
  cur.heading = lerpAngleDeg(cur.heading, camTargetHeading_, aHdg);
237
278
  cur.pitch = lerpAngleDeg(cur.pitch, camTargetPitch_, aPitch);
238
279
  cur.roll = lerpAngleDeg(cur.roll, camTargetRoll_, aRoll);
239
280
 
281
+ glm::dquat cq = glm::normalize(cur.viewCorrection);
282
+ glm::dquat tq = glm::normalize(glm::dquat(camTargetViewQw_, camTargetViewQx_, camTargetViewQy_, camTargetViewQz_));
283
+ if (glm::dot(cq, tq) < 0.0)
284
+ tq = -tq;
285
+ cur.viewCorrection = glm::normalize(glm::slerp(cq, tq, aViewQ));
286
+
240
287
  if (std::abs(camTargetAlt_ - cur.altitude) <= kEpsAlt)
241
288
  cur.altitude = camTargetAlt_;
242
289
  if (angleDeltaAbsDeg(cur.heading, camTargetHeading_) <= kEpsAngleDeg)
@@ -245,6 +292,8 @@ void CesiumBridgeAndroid::renderFrame(double dt) {
245
292
  cur.pitch = camTargetPitch_;
246
293
  if (angleDeltaAbsDeg(cur.roll, camTargetRoll_) <= kEpsAngleDeg)
247
294
  cur.roll = camTargetRoll_;
295
+ if ((1.0 - quatDotAbs(cur.viewCorrection, tq)) <= kEpsQuatDot)
296
+ cur.viewCorrection = tq;
248
297
 
249
298
  engine_->camera().setParams(cur);
250
299
  engine_->updateFrame(viewportWidth_, viewportHeight_, *frameResult_);
@@ -313,6 +362,19 @@ double CesiumBridgeAndroid::readVerticalFovDeg() {
313
362
  return engine_ ? engine_->camera().getVerticalFovDegrees() : 60.0;
314
363
  }
315
364
 
365
+ double CesiumBridgeAndroid::readViewCorrectionW() {
366
+ return engine_ ? engine_->camera().getParams().viewCorrection.w : 1.0;
367
+ }
368
+ double CesiumBridgeAndroid::readViewCorrectionX() {
369
+ return engine_ ? engine_->camera().getParams().viewCorrection.x : 0.0;
370
+ }
371
+ double CesiumBridgeAndroid::readViewCorrectionY() {
372
+ return engine_ ? engine_->camera().getParams().viewCorrection.y : 0.0;
373
+ }
374
+ double CesiumBridgeAndroid::readViewCorrectionZ() {
375
+ return engine_ ? engine_->camera().getParams().viewCorrection.z : 0.0;
376
+ }
377
+
316
378
  // ── JNI native method implementations ──────────────────────────────────────────
317
379
 
318
380
  static CesiumBridgeAndroid* getBridge(jlong ptr) {
@@ -373,6 +435,14 @@ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeUpdateCamera(
373
435
  getBridge(ptr)->updateCamera(lat, lon, alt, heading, pitch, roll);
374
436
  }
375
437
 
438
+ JNIEXPORT void JNICALL
439
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeUpdateCameraQuaternion(
440
+ JNIEnv*, jobject, jlong ptr, jdouble lat, jdouble lon, jdouble alt,
441
+ jdouble heading, jdouble pitch, jdouble roll,
442
+ jdouble qw, jdouble qx, jdouble qy, jdouble qz) {
443
+ getBridge(ptr)->updateCameraQuaternion(lat, lon, alt, heading, pitch, roll, qw, qx, qy, qz);
444
+ }
445
+
376
446
  JNIEXPORT void JNICALL
377
447
  Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetVerticalFovDeg(
378
448
  JNIEnv*, jobject, jlong ptr, jdouble deg) {
@@ -449,6 +519,23 @@ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeGetVerticalFovDeg
449
519
  return getBridge(ptr)->readVerticalFovDeg();
450
520
  }
451
521
 
522
+ JNIEXPORT jdouble JNICALL
523
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeGetViewCorrectionW(JNIEnv*, jobject, jlong ptr) {
524
+ return getBridge(ptr)->readViewCorrectionW();
525
+ }
526
+ JNIEXPORT jdouble JNICALL
527
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeGetViewCorrectionX(JNIEnv*, jobject, jlong ptr) {
528
+ return getBridge(ptr)->readViewCorrectionX();
529
+ }
530
+ JNIEXPORT jdouble JNICALL
531
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeGetViewCorrectionY(JNIEnv*, jobject, jlong ptr) {
532
+ return getBridge(ptr)->readViewCorrectionY();
533
+ }
534
+ JNIEXPORT jdouble JNICALL
535
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeGetViewCorrectionZ(JNIEnv*, jobject, jlong ptr) {
536
+ return getBridge(ptr)->readViewCorrectionZ();
537
+ }
538
+
452
539
  JNIEXPORT jdouble JNICALL
453
540
  Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeGetMetricsFps(JNIEnv*, jobject, jlong ptr) {
454
541
  return getBridge(ptr)->metricsFps();
@@ -22,6 +22,9 @@ struct CesiumBridgeAndroid {
22
22
  void updateImageryAssetId(int64_t assetId);
23
23
  void updateCamera(double lat, double lon, double alt,
24
24
  double heading, double pitch, double roll);
25
+ void updateCameraQuaternion(double lat, double lon, double alt,
26
+ double heading, double pitch, double roll,
27
+ double qw, double qx, double qy, double qz);
25
28
  void setVerticalFovDeg(double degrees);
26
29
  void setMaximumScreenSpaceError(double v);
27
30
  void setMaximumSimultaneousTileLoads(int v);
@@ -40,6 +43,10 @@ struct CesiumBridgeAndroid {
40
43
  double readCameraPitch();
41
44
  double readCameraRoll();
42
45
  double readVerticalFovDeg();
46
+ double readViewCorrectionW();
47
+ double readViewCorrectionX();
48
+ double readViewCorrectionY();
49
+ double readViewCorrectionZ();
43
50
 
44
51
  // Metrics
45
52
  double metricsFps() const { return metricsFps_; }
@@ -52,7 +59,6 @@ struct CesiumBridgeAndroid {
52
59
 
53
60
  private:
54
61
  void buildEngine();
55
- reactnativecesium::CameraParams makeCamTarget();
56
62
 
57
63
  std::unique_ptr<reactnativecesium::VulkanBackend> vulkanBackend_;
58
64
  std::unique_ptr<reactnativecesium::CesiumEngine> engine_;
@@ -78,6 +84,10 @@ private:
78
84
  double camTargetHeading_ = 129.0;
79
85
  double camTargetPitch_ = -45.0;
80
86
  double camTargetRoll_ = 0.0;
87
+ double camTargetViewQw_ = 1.0;
88
+ double camTargetViewQx_ = 0.0;
89
+ double camTargetViewQy_ = 0.0;
90
+ double camTargetViewQz_ = 0.0;
81
91
  bool forceRenderNextFrame_ = true;
82
92
 
83
93
  // Metrics
@@ -25,6 +25,19 @@ class CesiumBridgeJNI {
25
25
  fun updateImageryAssetId(assetId: Long) = nativeUpdateImageryAssetId(nativePtr, assetId)
26
26
  fun updateCamera(lat: Double, lon: Double, alt: Double, heading: Double, pitch: Double, roll: Double) =
27
27
  nativeUpdateCamera(nativePtr, lat, lon, alt, heading, pitch, roll)
28
+
29
+ fun updateCameraQuaternion(
30
+ lat: Double,
31
+ lon: Double,
32
+ alt: Double,
33
+ heading: Double,
34
+ pitch: Double,
35
+ roll: Double,
36
+ qw: Double,
37
+ qx: Double,
38
+ qy: Double,
39
+ qz: Double,
40
+ ) = nativeUpdateCameraQuaternion(nativePtr, lat, lon, alt, heading, pitch, roll, qw, qx, qy, qz)
28
41
  fun setVerticalFovDeg(deg: Double) = nativeSetVerticalFovDeg(nativePtr, deg)
29
42
  fun setMaxSSE(v: Double) = nativeSetMaxSSE(nativePtr, v)
30
43
  fun setMaxSimLoads(v: Int) = nativeSetMaxSimLoads(nativePtr, v)
@@ -43,6 +56,11 @@ class CesiumBridgeJNI {
43
56
  fun getCameraRoll(): Double = nativeGetCameraRoll(nativePtr)
44
57
  fun getVerticalFovDeg(): Double = nativeGetVerticalFovDeg(nativePtr)
45
58
 
59
+ fun getViewCorrectionW(): Double = nativeGetViewCorrectionW(nativePtr)
60
+ fun getViewCorrectionX(): Double = nativeGetViewCorrectionX(nativePtr)
61
+ fun getViewCorrectionY(): Double = nativeGetViewCorrectionY(nativePtr)
62
+ fun getViewCorrectionZ(): Double = nativeGetViewCorrectionZ(nativePtr)
63
+
46
64
  fun getMetricsFps(): Double = nativeGetMetricsFps(nativePtr)
47
65
  fun getMetricsTilesRendered(): Int = nativeGetMetricsTilesRendered(nativePtr)
48
66
  fun getMetricsTilesLoading(): Int = nativeGetMetricsTilesLoading(nativePtr)
@@ -59,6 +77,19 @@ class CesiumBridgeJNI {
59
77
  private external fun nativeUpdateIonAccessToken(ptr: Long, token: String, assetId: Long)
60
78
  private external fun nativeUpdateImageryAssetId(ptr: Long, assetId: Long)
61
79
  private external fun nativeUpdateCamera(ptr: Long, lat: Double, lon: Double, alt: Double, heading: Double, pitch: Double, roll: Double)
80
+ private external fun nativeUpdateCameraQuaternion(
81
+ ptr: Long,
82
+ lat: Double,
83
+ lon: Double,
84
+ alt: Double,
85
+ heading: Double,
86
+ pitch: Double,
87
+ roll: Double,
88
+ qw: Double,
89
+ qx: Double,
90
+ qy: Double,
91
+ qz: Double,
92
+ )
62
93
  private external fun nativeSetVerticalFovDeg(ptr: Long, deg: Double)
63
94
  private external fun nativeSetMaxSSE(ptr: Long, v: Double)
64
95
  private external fun nativeSetMaxSimLoads(ptr: Long, v: Int)
@@ -74,6 +105,10 @@ class CesiumBridgeJNI {
74
105
  private external fun nativeGetCameraPitch(ptr: Long): Double
75
106
  private external fun nativeGetCameraRoll(ptr: Long): Double
76
107
  private external fun nativeGetVerticalFovDeg(ptr: Long): Double
108
+ private external fun nativeGetViewCorrectionW(ptr: Long): Double
109
+ private external fun nativeGetViewCorrectionX(ptr: Long): Double
110
+ private external fun nativeGetViewCorrectionY(ptr: Long): Double
111
+ private external fun nativeGetViewCorrectionZ(ptr: Long): Double
77
112
  private external fun nativeGetMetricsFps(ptr: Long): Double
78
113
  private external fun nativeGetMetricsTilesRendered(ptr: Long): Int
79
114
  private external fun nativeGetMetricsTilesLoading(ptr: Long): Int
@@ -17,6 +17,7 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
17
17
  private var usingLowRefreshRate = false
18
18
  private var lastFrameTimeNanos = 0L
19
19
  private var lastPushedCamera: CameraState? = null
20
+ private var lastPushedViewCorrection: Quaternion? = null
20
21
  private var renderLoopActive = false
21
22
 
22
23
  override var ionAccessToken: String = ""
@@ -174,6 +175,24 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
174
175
  pushCameraIfChanged(camera)
175
176
  }
176
177
 
178
+ override fun setCameraQuaternion(camera: CameraState, viewCorrection: Quaternion) {
179
+ initialCamera = camera
180
+ pushCameraQuaternionIfChanged(camera, viewCorrection)
181
+ }
182
+
183
+ override fun getViewCorrection(): Promise<Quaternion> {
184
+ val b = bridge
185
+ ?: return Promise.resolved(Quaternion(w = 1.0, x = 0.0, y = 0.0, z = 0.0))
186
+ return Promise.resolved(
187
+ Quaternion(
188
+ w = b.getViewCorrectionW(),
189
+ x = b.getViewCorrectionX(),
190
+ y = b.getViewCorrectionY(),
191
+ z = b.getViewCorrectionZ(),
192
+ ),
193
+ )
194
+ }
195
+
177
196
  private fun pushCameraIfChanged(camera: CameraState) {
178
197
  val b = bridge ?: return
179
198
  val last = lastPushedCamera
@@ -193,6 +212,42 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
193
212
  b.setVerticalFovDeg(camera.verticalFovDeg)
194
213
  }
195
214
 
215
+ private fun pushCameraQuaternionIfChanged(camera: CameraState, viewCorrection: Quaternion) {
216
+ val b = bridge ?: return
217
+ val last = lastPushedCamera
218
+ val lastQ = lastPushedViewCorrection
219
+ if (last != null && lastQ != null &&
220
+ last.latitude == camera.latitude &&
221
+ last.longitude == camera.longitude &&
222
+ last.altitude == camera.altitude &&
223
+ last.heading == camera.heading &&
224
+ last.pitch == camera.pitch &&
225
+ last.roll == camera.roll &&
226
+ last.verticalFovDeg == camera.verticalFovDeg &&
227
+ lastQ.w == viewCorrection.w &&
228
+ lastQ.x == viewCorrection.x &&
229
+ lastQ.y == viewCorrection.y &&
230
+ lastQ.z == viewCorrection.z
231
+ ) {
232
+ return
233
+ }
234
+ lastPushedCamera = camera
235
+ lastPushedViewCorrection = viewCorrection
236
+ b.updateCameraQuaternion(
237
+ camera.latitude,
238
+ camera.longitude,
239
+ camera.altitude,
240
+ camera.heading,
241
+ camera.pitch,
242
+ camera.roll,
243
+ viewCorrection.w,
244
+ viewCorrection.x,
245
+ viewCorrection.y,
246
+ viewCorrection.z,
247
+ )
248
+ b.setVerticalFovDeg(camera.verticalFovDeg)
249
+ }
250
+
196
251
  private fun syncPropsTobridge() {
197
252
  bridge?.setMaxSSE(maximumScreenSpaceError)
198
253
  bridge?.setMaxSimLoads(maximumSimultaneousTileLoads.toInt())
@@ -3,6 +3,7 @@
3
3
  #include <CesiumGeospatial/GlobeTransforms.h>
4
4
 
5
5
  #include <glm/gtc/matrix_transform.hpp>
6
+ #include <glm/gtc/quaternion.hpp>
6
7
 
7
8
  #include <algorithm>
8
9
  #include <cmath>
@@ -75,6 +76,27 @@ void GlobeCamera::recompute() const {
75
76
  right = glm::normalize(rolledRight);
76
77
  }
77
78
 
79
+ // 4. Optional camera-space correction (columns: right, camUp, forward).
80
+ const glm::dquat& qIn = params_.viewCorrection;
81
+ const double ql2 =
82
+ qIn.w * qIn.w + qIn.x * qIn.x + qIn.y * qIn.y + qIn.z * qIn.z;
83
+ glm::dquat qCorr;
84
+ if (ql2 < 1e-20) {
85
+ qCorr = glm::dquat(1.0, 0.0, 0.0, 0.0);
86
+ } else {
87
+ const double inv = 1.0 / std::sqrt(ql2);
88
+ qCorr = glm::dquat(qIn.w * inv, qIn.x * inv, qIn.y * inv, qIn.z * inv);
89
+ }
90
+ glm::dmat3 R_basis(right, camUp, fwd);
91
+ glm::dmat3 R_corr = glm::mat3_cast(qCorr);
92
+ glm::dmat3 R_final = R_basis * R_corr;
93
+ glm::dvec3 r0 = glm::normalize(glm::dvec3(R_final[0]));
94
+ glm::dvec3 r2 = glm::normalize(glm::dvec3(R_final[2]));
95
+ // Gram–Schmidt: preserve forward (column 2), rebuild right/up (matches right × fwd = camUp).
96
+ fwd = r2;
97
+ right = glm::normalize(r0 - glm::dot(r0, fwd) * fwd);
98
+ camUp = glm::normalize(glm::cross(right, fwd));
99
+
78
100
  direction_ = fwd;
79
101
  up_ = camUp;
80
102
  }
@@ -5,6 +5,7 @@
5
5
  #include <Cesium3DTilesSelection/ViewState.h>
6
6
 
7
7
  #include <glm/glm.hpp>
8
+ #include <glm/gtc/quaternion.hpp>
8
9
  #include <mutex>
9
10
 
10
11
  namespace reactnativecesium {
@@ -16,6 +17,8 @@ struct CameraParams {
16
17
  double heading = 220.0;
17
18
  double pitch = -10.0;
18
19
  double roll = 0.0;
20
+ /// Camera-space rotation applied after HPR (unit quaternion; w,x,y,z).
21
+ glm::dquat viewCorrection{1.0, 0.0, 0.0, 0.0};
19
22
  };
20
23
 
21
24
  class GlobeCamera {
@@ -18,6 +18,17 @@ NS_ASSUME_NONNULL_BEGIN
18
18
  heading:(double)heading
19
19
  pitch:(double)pitch
20
20
  roll:(double)roll;
21
+ /// Same as `updateCameraLatitude:...` plus camera-space view correction (w,x,y,z). Does not replace HPR; applied after HPR in the engine.
22
+ - (void)updateCameraQuaternionLatitude:(double)lat
23
+ longitude:(double)lon
24
+ altitude:(double)alt
25
+ heading:(double)heading
26
+ pitch:(double)pitch
27
+ roll:(double)roll
28
+ viewCorrectionW:(double)qw
29
+ x:(double)qx
30
+ y:(double)qy
31
+ z:(double)qz;
21
32
  - (void)resize:(int)width height:(int)height;
22
33
  - (BOOL)shouldRenderNextFrame;
23
34
  - (void)markNeedsRender;
@@ -49,6 +60,10 @@ NS_ASSUME_NONNULL_BEGIN
49
60
  - (double)readCameraPitch;
50
61
  - (double)readCameraRoll;
51
62
  - (double)readVerticalFovDeg;
63
+ - (double)readViewCorrectionW;
64
+ - (double)readViewCorrectionX;
65
+ - (double)readViewCorrectionY;
66
+ - (double)readViewCorrectionZ;
52
67
 
53
68
  @end
54
69