react-native-cesium 1.0.5 → 1.1.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.
Files changed (62) hide show
  1. package/README.md +48 -11
  2. package/ReactNativeCesium.podspec +1 -1
  3. package/android/CMakeLists.txt +5 -3
  4. package/android/src/main/cpp/CesiumBridgeAndroid.cpp +196 -227
  5. package/android/src/main/cpp/CesiumBridgeAndroid.h +48 -49
  6. package/android/src/main/cpp/cpp-adapter.cpp +2 -2
  7. package/android/src/main/cpp/vulkan/VulkanBackend.cpp +1010 -569
  8. package/android/src/main/cpp/vulkan/VulkanBackend.h +121 -12
  9. package/android/src/main/cpp/vulkan/shaders/sky.frag +20 -10
  10. package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +28 -0
  11. package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +295 -58
  12. package/cpp/engine/CameraSmoother.cpp +68 -0
  13. package/cpp/engine/CameraSmoother.hpp +24 -0
  14. package/cpp/engine/CameraTargetState.cpp +28 -0
  15. package/cpp/engine/CameraTargetState.hpp +97 -0
  16. package/cpp/engine/CesiumEngine.cpp +206 -60
  17. package/cpp/engine/CesiumEngine.hpp +77 -10
  18. package/cpp/engine/EngineTunables.hpp +64 -0
  19. package/cpp/engine/GeoidConverter.cpp +36 -0
  20. package/cpp/engine/GeoidConverter.hpp +10 -0
  21. package/cpp/engine/GlobeCamera.cpp +7 -2
  22. package/cpp/engine/MetricsAggregator.cpp +71 -0
  23. package/cpp/engine/MetricsAggregator.hpp +54 -0
  24. package/cpp/engine/TileLifecycleManager.cpp +0 -12
  25. package/cpp/engine/TileLifecycleManager.hpp +0 -3
  26. package/cpp/third_party/egm96/EGM96.c +289 -0
  27. package/cpp/third_party/egm96/EGM96.h +45 -0
  28. package/cpp/third_party/egm96/EGM96_data.h +65372 -0
  29. package/ios/CesiumBridge.h +24 -5
  30. package/ios/CesiumBridge.mm +148 -271
  31. package/ios/HybridCesiumView.swift +197 -40
  32. package/ios/metal/MetalBackend.mm +72 -23
  33. package/lib/typescript/src/index.d.ts +1 -1
  34. package/lib/typescript/src/index.d.ts.map +1 -1
  35. package/lib/typescript/src/specs/cesium-view.nitro.d.ts +26 -0
  36. package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
  37. package/nitrogen/generated/android/c++/JCesiumMetrics.hpp +5 -1
  38. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +109 -1
  39. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +24 -0
  40. package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +48 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CameraState.kt +25 -0
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CesiumMetrics.kt +32 -2
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +72 -0
  44. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Quaternion.kt +19 -0
  45. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +30 -0
  46. package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +85 -1
  47. package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +60 -0
  48. package/nitrogen/generated/ios/swift/CesiumMetrics.swift +7 -2
  49. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +12 -0
  50. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +288 -0
  51. package/nitrogen/generated/shared/c++/CesiumMetrics.hpp +5 -1
  52. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +24 -0
  53. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +25 -1
  54. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +132 -0
  55. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +13 -1
  56. package/nitrogen/generated/shared/json/CesiumViewConfig.json +12 -0
  57. package/package.json +4 -5
  58. package/scripts/cesium/clone.mjs +1 -1
  59. package/src/index.ts +1 -0
  60. package/src/specs/cesium-view.nitro.ts +29 -0
  61. package/cpp/engine/DeletionQueue.cpp +0 -28
  62. package/cpp/engine/DeletionQueue.hpp +0 -28
package/README.md CHANGED
@@ -242,8 +242,14 @@ export function GlobeScreen() {
242
242
  maximumScreenSpaceError={16}
243
243
  maximumSimultaneousTileLoads={8}
244
244
  loadingDescendantLimit={20}
245
- msaaSampleCount={1}
245
+ msaaSampleCount={4}
246
246
  ionImageryAssetId={2}
247
+ // Optional perf / quality knobs (see "Optional performance / quality knobs" below).
248
+ // Omit to use the built-in defaults; values shown here are typical for a high-end phone.
249
+ maximumCachedMiB={384}
250
+ preloadAncestors
251
+ forbidHoles
252
+ enableLodTransitionPeriod
247
253
  onMetrics={callback((metrics: CesiumMetrics) => {
248
254
  console.log('Cesium FPS:', metrics.fps)
249
255
  })}
@@ -279,7 +285,7 @@ When you pass a callback ref to `CesiumView`, wrap it with `callback(...)` from
279
285
  | `maximumScreenSpaceError` | `number` | `32` | Quality/performance trade-off for tile refinement. Lower values are sharper (more work); higher values are faster/blurrier. |
280
286
  | `maximumSimultaneousTileLoads` | `number` | `12` | Max concurrent tile fetch/decode operations. Raising `8 -> 16` can improve fast camera moves on good networks but may increase memory/bandwidth spikes. |
281
287
  | `loadingDescendantLimit` | `number` | `20` | Caps descendant tile fan-out during traversal. Lower values like `10` smooth bursts on low-end devices; higher values like `40` can fill detail faster. |
282
- | `msaaSampleCount` | `number` | `1` | Anti-aliasing sample count. iOS uses `1`, `2`, or `4` (`>=4 -> 4`, `>=2 -> 2`, otherwise `1`); Android currently renders at `1` (MSAA setting is currently ignored in Vulkan backend). |
288
+ | `msaaSampleCount` | `number` | `1` | Anti-aliasing sample count. Both backends support `1`, `2`, `4`, or `8`; the value is clamped to the largest sample count the physical device can frame-buffer (Metal: `device.supportsTextureSampleCount`, Vulkan: `framebufferColorSampleCounts & framebufferDepthSampleCounts`). On tiled mobile GPUs the multisample colour buffer is allocated `LAZILY_ALLOCATED` where supported, so MSAA's memory cost is largely on-chip. |
283
289
  | `ionImageryAssetId` | `number` | `1` | Imagery layer to drape over terrain/tiles. Use a satellite imagery asset for a photoreal look, or switch to a streets/map layer for legibility. See your Cesium Ion Asset IDs. |
284
290
 
285
291
  **Consumer-optional props (TypeScript)**
@@ -288,6 +294,35 @@ When you pass a callback ref to `CesiumView`, wrap it with `callback(...)` from
288
294
  | --- | --- | --- | --- |
289
295
  | `onMetrics` | `(metrics: CesiumMetrics) => void` | `undefined` | Receives throttled runtime stats and credits text. |
290
296
 
297
+ **Optional performance / quality knobs**
298
+
299
+ These props are all optional. Leave them unset to use the built-in defaults; set them to override per-screen for fine-grained control. They are runtime-mutable: changing them at any time after the view has mounted re-applies to the live tileset (no engine rebuild) except where noted.
300
+
301
+ | Prop | Type | Default | Description |
302
+ | --- | --- | --- | --- |
303
+ | `maximumCachedMiB` | `number` | `256` | RAM budget (mebibytes) for decoded geometry / textures held by the live tileset. Lower values (e.g. `128`) reduce peak memory on entry-level devices; higher values (`512+`) keep more tiles resident across long pans. |
304
+ | `preloadAncestors` | `boolean` | `true` | Pre-load ancestor tiles around the visible set. Smoother panning at higher load pressure. |
305
+ | `preloadSiblings` | `boolean` | `true` | Pre-load sibling tiles around the visible set. Same trade-off as `preloadAncestors`. |
306
+ | `forbidHoles` | `boolean` | `true` | When `true`, Cesium keeps coarser ancestor tiles visible until all children load. Setting to `false` during fast pans reduces upload pressure at the cost of brief visible "holes". |
307
+ | `enableWaterMask` | `boolean` | `true` | Decode the per-tile water-mask texture used for coastline shading and the ocean-vs-land hypsometric branch. Disable to save bandwidth on memory-constrained devices. |
308
+ | `enableFogCulling` | `boolean` | `false` | When `true`, tiles fully inside the atmospheric fog volume are culled before refinement. |
309
+ | `enforceCulledScreenSpaceError` | `boolean` | `true` | When `enableFogCulling` is `true`, allow fogged tiles to refine to a coarser SSE — saves work on tiles that will never be visually scrutinised. |
310
+ | `culledScreenSpaceError` | `number` | `64` | The relaxed SSE applied to fogged tiles when `enforceCulledScreenSpaceError` is `true`. |
311
+ | `enableLodTransitionPeriod` | `boolean` | `false` | Cross-fade between LODs as tiles refine instead of hard-popping. Smoother visually; very small per-frame cost. |
312
+ | `lodTransitionLength` | `number` | `1.0` | Length in seconds of the LOD cross-fade when `enableLodTransitionPeriod` is `true`. |
313
+ | `sqliteCacheMaxRows` | `number` | `4096` | Maximum number of cached HTTP responses Cesium keeps in `cesium_cache.db` between sessions. Tablets can comfortably go to `16384`. **Re-applied only on the next view init** — runtime changes take effect after the next mount. |
314
+ | `taskProcessorThreads` | `number` | `0` (auto) | Worker pool size for tile parsing / texture decode. `0` (default) auto-sizes to `clamp(2, std::thread::hardware_concurrency() - 1, 8)`. Override for benchmarking or to throttle background CPU on shared-render apps. **Re-applied only on the next view init.** |
315
+
316
+ #### Suggested presets
317
+
318
+ The defaults are tuned for current-generation phones (e.g. iPhone 13/Pixel 6 and newer). Useful starting points for other classes of device:
319
+
320
+ | Profile | `maximumScreenSpaceError` | `maximumSimultaneousTileLoads` | `maximumCachedMiB` | `forbidHoles` | `enableLodTransitionPeriod` |
321
+ | --- | --- | --- | --- | --- | --- |
322
+ | **High-end / tablet** | `16` | `16` | `512` | `true` | `true` |
323
+ | **Default / phone** | `32` | `12` | `256` | `true` | `false` |
324
+ | **Battery-saver / low-end** | `48` | `6` | `128` | `false` | `false` |
325
+
291
326
  ### `CameraState`
292
327
 
293
328
  ```ts
@@ -302,15 +337,15 @@ type CameraState = {
302
337
  }
303
338
  ```
304
339
 
305
- | Field | Type | Valid range / format | Description |
306
- | --- | --- | --- |-------------------------------------------------------------------------------------------------------|
307
- | `latitude` | `number` | `-90..90` | Camera latitude in degrees. |
308
- | `longitude` | `number` | `-180..180` | Camera longitude in degrees. |
309
- | `altitude` | `number` | Meters above ellipsoid | Camera height in meters. |
310
- | `heading` | `number` | Degrees | Compass direction the camera faces. Example: `0` faces north, `90` faces east. |
311
- | `pitch` | `number` | Degrees | Tilt angle. Negative values look downward toward terrain; positive values tilt up toward horizon/sky. |
312
- | `roll` | `number` | Degrees | Bank/rotation around forward axis. Positive right bank, negative left bank. |
313
- | `verticalFovDeg` | `number` | `20..100` (clamped) | Vertical field of view in degrees; see **Field of view** (next section). |
340
+ | Field | Type | Valid range / format | Description |
341
+ | --- | --- |------------------------------|-------------------------------------------------------------------------------------------------------|
342
+ | `latitude` | `number` | `-90..90` | Camera latitude in degrees. |
343
+ | `longitude` | `number` | `-180..180` | Camera longitude in degrees. |
344
+ | `altitude` | `number` | Meters above sea level (MSL) | Camera height in meters. |
345
+ | `heading` | `number` | Degrees | Compass direction the camera faces. Example: `0` faces north, `90` faces east. |
346
+ | `pitch` | `number` | Degrees | Tilt angle. Negative values look downward toward terrain; positive values tilt up toward horizon/sky. |
347
+ | `roll` | `number` | Degrees | Bank/rotation around forward axis. Positive right bank, negative left bank. |
348
+ | `verticalFovDeg` | `number` | `20..100` (clamped) | Vertical field of view in degrees; see **Field of view** (next section). |
314
349
 
315
350
  ### Field of view: vertical vs horizontal, full vs half, and Cesium alignment
316
351
 
@@ -371,6 +406,7 @@ Avoid assuming **main-thread-only** vs **JS-thread-only** labels beyond the abov
371
406
  | `tilesLoading` | `number` | Number of tiles still loading. If this stays high for long periods, reduce load pressure (`maximumSimultaneousTileLoads`) or check network. |
372
407
  | `tilesVisited` | `number` | Number of tiles visited during traversal/culling. |
373
408
  | `ionTokenConfigured` | `boolean` | Whether a non-empty Ion token is configured natively. `false` is a quick signal to check `ionAccessToken`. |
409
+ | `tlsConfigured` | `boolean` | Whether libcurl resolved a CA bundle for HTTPS. `false` means TLS to `api.cesium.com` may fail; on iOS verify `cacert.pem` is in the main bundle, on Android that the asset is bundled (the package ships one for you). |
374
410
  | `tilesetReady` | `boolean` | Whether the primary tileset is initialized and ready. |
375
411
  | `creditsPlainText` | `string` | Plain-text attribution/credits from Cesium data sources. Display this in your app footer to satisfy attribution requirements. |
376
412
 
@@ -393,6 +429,7 @@ The example project currently links this library locally via `link:..`.
393
429
 
394
430
  - Native rendering is built on [Cesium Native](https://github.com/CesiumGS/cesium-native)
395
431
  - Cesium Native is licensed under Apache 2.0; see [`NOTICE`](./NOTICE)
432
+ - EGM96 altitude offset calculations using [EGM96](https://github.com/emericg/EGM96)
396
433
  - React Native integration is built with [Nitro Modules](https://github.com/mrousavy/nitro)
397
434
 
398
435
 
@@ -32,7 +32,7 @@ Pod::Spec.new do |s|
32
32
  s.source_files = [
33
33
  "ios/**/*.{swift}",
34
34
  "ios/**/*.{h,m,mm}",
35
- "cpp/**/*.{hpp,cpp,h,mm}",
35
+ "cpp/**/*.{hpp,cpp,h,mm,c}",
36
36
  ]
37
37
 
38
38
  s.public_header_files = [
@@ -69,11 +69,13 @@ add_library(${PACKAGE_NAME} SHARED
69
69
  ../cpp/engine/ResourcePreparer.cpp
70
70
  ../cpp/engine/GltfToMesh.cpp
71
71
  ../cpp/engine/GlobeCamera.cpp
72
- ../cpp/engine/DeletionQueue.cpp
72
+ ../cpp/engine/GeoidConverter.cpp
73
73
  ../cpp/engine/TileLifecycleManager.cpp
74
- # Vulkan backend
74
+ ../cpp/engine/CameraSmoother.cpp
75
+ ../cpp/engine/CameraTargetState.cpp
76
+ ../cpp/engine/MetricsAggregator.cpp
77
+ ../cpp/third_party/egm96/EGM96.c
75
78
  src/main/cpp/vulkan/VulkanBackend.cpp
76
- # JNI bridge
77
79
  src/main/cpp/CesiumBridgeAndroid.cpp
78
80
  )
79
81
 
@@ -1,6 +1,7 @@
1
1
  #include "CesiumBridgeAndroid.h"
2
2
 
3
- #include "engine/CesiumEngine.hpp"
3
+ #include "engine/CameraSmoother.hpp"
4
+ #include "engine/EngineTunables.hpp"
4
5
  #include "vulkan/VulkanBackend.h"
5
6
 
6
7
  #include <android/native_window_jni.h>
@@ -8,55 +9,18 @@
8
9
 
9
10
  #include <glm/gtc/quaternion.hpp>
10
11
 
12
+ #include <algorithm>
11
13
  #include <cmath>
12
- #include <regex>
13
- #include <sstream>
14
14
 
15
15
  #define LOG_TAG "CesiumBridge"
16
16
  #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
17
17
  #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
18
18
 
19
- // Per-DOF smoothing constants (match iOS CesiumBridge.mm)
20
- static const double kSmoothAlt = 25.0;
21
- static const double kSmoothHdg = 30.0;
22
- static const double kSmoothRoll = 50.0;
23
- static const double kSmoothPitch = 50.0;
24
- static const double kSmoothViewCorr = 50.0;
25
- static const double kEpsLatLon = 1e-7;
26
- static const double kEpsAlt = 0.1;
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
- }
33
-
34
- static double lerpAngleDeg(double a, double b, double t) {
35
- double diff = std::fmod(b - a + 540.0, 360.0) - 180.0;
36
- return a + diff * t;
37
- }
38
-
39
- static double angleDeltaAbsDeg(double a, double b) {
40
- return std::abs(std::fmod(b - a + 540.0, 360.0) - 180.0);
41
- }
42
-
43
- static std::string stripHtmlToPlain(const std::string& html) {
44
- if (html.empty()) return "";
45
- static const std::regex tagRx("<[^>]+>");
46
- std::string plain = std::regex_replace(html, tagRx, " ");
47
- static const std::regex wsRx("\\s+");
48
- plain = std::regex_replace(plain, wsRx, " ");
49
- // Trim
50
- size_t start = plain.find_first_not_of(" \t\n\r");
51
- size_t end = plain.find_last_not_of(" \t\n\r");
52
- if (start == std::string::npos) return "";
53
- return plain.substr(start, end - start + 1);
54
- }
55
-
56
19
  void CesiumBridgeAndroid::init(JNIEnv* env, jobject surface, int width, int height,
57
- const std::string& cacheDir, const std::string& cacertPath) {
58
- cacheDir_ = cacheDir;
59
- cacertPath_ = cacertPath;
20
+ const std::string& cacheDir,
21
+ const std::string& cacertPath) {
22
+ config_.cacheDatabasePath = cacheDir.empty() ? "" : cacheDir + "/cesium_cache.db";
23
+ config_.tlsCaBundlePath = cacertPath;
60
24
  viewportWidth_ = width;
61
25
  viewportHeight_ = height;
62
26
 
@@ -67,40 +31,22 @@ void CesiumBridgeAndroid::init(JNIEnv* env, jobject surface, int width, int heig
67
31
  }
68
32
 
69
33
  vulkanBackend_ = std::make_unique<reactnativecesium::VulkanBackend>();
34
+ vulkanBackend_->setCacheDir(cacheDir);
70
35
  vulkanBackend_->initialize(window, width, height);
71
36
 
72
37
  frameResult_ = std::make_unique<reactnativecesium::FrameResult>();
73
38
  buildEngine();
74
39
  initialized_ = true;
75
- forceRenderNextFrame_ = true;
76
-
77
- LOGI("CesiumBridgeAndroid initialized %dx%d", width, height);
40
+ target_.requestForceRender();
78
41
  }
79
42
 
80
43
  void CesiumBridgeAndroid::buildEngine() {
81
- reactnativecesium::EngineConfig cfg;
82
- cfg.ionAccessToken = ionAccessToken_;
83
- cfg.ionAssetId = ionTilesetAssetId_;
84
- cfg.cacheDatabasePath = cacheDir_.empty() ? "" : cacheDir_ + "/cesium_cache.db";
85
- cfg.tlsCaBundlePath = cacertPath_;
86
- cfg.maximumScreenSpaceError = std::max(1.0, maxSSE_);
87
- cfg.maximumSimultaneousTileLoads = static_cast<int32_t>(std::max(0.0, std::round(maxSimLoads_)));
88
- cfg.loadingDescendantLimit = static_cast<int32_t>(std::max(1.0, std::round(loadDescLim_)));
89
-
90
44
  engine_ = std::make_unique<reactnativecesium::CesiumEngine>();
91
- engine_->initialize(cfg);
92
-
93
- auto params = engine_->camera().getParams();
94
- camTargetLat_ = params.latitude;
95
- camTargetLon_ = params.longitude;
96
- camTargetAlt_ = params.altitude;
97
- camTargetHeading_ = params.heading;
98
- camTargetPitch_ = params.pitch;
99
- camTargetRoll_ = params.roll;
100
- camTargetViewQw_ = params.viewCorrection.w;
101
- camTargetViewQx_ = params.viewCorrection.x;
102
- camTargetViewQy_ = params.viewCorrection.y;
103
- camTargetViewQz_ = params.viewCorrection.z;
45
+ engine_->initialize(config_);
46
+
47
+ // Seed the demand target from the engine's initial camera so the first
48
+ // frame is not a "snap from default" jump.
49
+ target_.setAll(engine_->camera().getParams());
104
50
 
105
51
  auto* backendPtr = vulkanBackend_.get();
106
52
  engine_->getResourcePreparer()->setGPUTextureCreator(
@@ -121,6 +67,12 @@ void CesiumBridgeAndroid::buildEngine() {
121
67
  });
122
68
  }
123
69
 
70
+ void CesiumBridgeAndroid::applyEngineConfig() {
71
+ if (!engine_) return;
72
+ engine_->updateConfig(config_);
73
+ target_.requestForceRender();
74
+ }
75
+
124
76
  void CesiumBridgeAndroid::shutdown() {
125
77
  if (engine_) engine_->shutdown();
126
78
  if (vulkanBackend_) vulkanBackend_->shutdown();
@@ -131,213 +83,177 @@ void CesiumBridgeAndroid::resize(int width, int height) {
131
83
  viewportWidth_ = width;
132
84
  viewportHeight_ = height;
133
85
  if (vulkanBackend_) vulkanBackend_->resize(width, height);
134
- forceRenderNextFrame_ = true;
86
+ target_.requestForceRender();
135
87
  }
136
88
 
137
- void CesiumBridgeAndroid::updateIonAccessToken(const std::string& token, int64_t assetId) {
138
- if (!initialized_) return;
139
- ionAccessToken_ = token;
140
- ionTilesetAssetId_ = assetId;
141
-
142
- reactnativecesium::EngineConfig cfg;
143
- cfg.ionAccessToken = ionAccessToken_;
144
- cfg.ionAssetId = ionTilesetAssetId_;
145
- cfg.cacheDatabasePath = cacheDir_.empty() ? "" : cacheDir_ + "/cesium_cache.db";
146
- cfg.tlsCaBundlePath = cacertPath_;
147
- cfg.maximumScreenSpaceError = std::max(1.0, maxSSE_);
148
- cfg.maximumSimultaneousTileLoads = static_cast<int32_t>(std::max(0.0, std::round(maxSimLoads_)));
149
- cfg.loadingDescendantLimit = static_cast<int32_t>(std::max(1.0, std::round(loadDescLim_)));
150
-
151
- engine_->updateConfig(cfg);
152
- forceRenderNextFrame_ = true;
89
+ void CesiumBridgeAndroid::updateIonAccessToken(const std::string& token,
90
+ int64_t assetId) {
91
+ config_.ionAccessToken = token;
92
+ config_.ionAssetId = assetId;
93
+ if (initialized_) applyEngineConfig();
153
94
  }
154
95
 
155
96
  void CesiumBridgeAndroid::updateImageryAssetId(int64_t assetId) {
156
- if (!initialized_) return;
157
- engine_->setImageryAssetId(assetId);
158
- forceRenderNextFrame_ = true;
97
+ config_.ionImageryAssetId = (assetId <= 0) ? 1 : assetId;
98
+ if (initialized_ && engine_) {
99
+ engine_->setImageryAssetId(config_.ionImageryAssetId);
100
+ target_.requestForceRender();
101
+ }
159
102
  }
160
103
 
161
104
  void CesiumBridgeAndroid::updateCamera(double lat, double lon, double alt,
162
- double heading, double pitch, double roll) {
105
+ double heading, double pitch, double roll) {
163
106
  if (!initialized_) return;
164
- camTargetLat_ = lat;
165
- camTargetLon_ = lon;
166
- camTargetAlt_ = alt;
167
- camTargetHeading_ = heading;
168
- camTargetPitch_ = pitch;
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) {
107
+ target_.setHpr(lat, lon, alt, heading, pitch, roll);
108
+ }
109
+
110
+ void CesiumBridgeAndroid::updateCameraQuaternion(
111
+ double lat, double lon, double alt, double heading, double pitch,
112
+ double roll, double qw, double qx, double qy, double qz) {
177
113
  if (!initialized_) return;
178
- camTargetLat_ = lat;
179
- camTargetLon_ = lon;
180
- camTargetAlt_ = alt;
181
- camTargetHeading_ = heading;
182
- camTargetPitch_ = pitch;
183
- camTargetRoll_ = roll;
114
+ target_.setHpr(lat, lon, alt, heading, pitch, roll);
184
115
  const double ql2 = qw * qw + qx * qx + qy * qy + qz * qz;
185
- glm::dquat q;
116
+ glm::dquat q;
186
117
  if (ql2 < 1e-20) {
187
118
  q = glm::dquat(1.0, 0.0, 0.0, 0.0);
188
119
  } else {
189
120
  const double inv = 1.0 / std::sqrt(ql2);
190
121
  q = glm::dquat(qw * inv, qx * inv, qy * inv, qz * inv);
191
122
  }
192
- camTargetViewQw_ = q.w;
193
- camTargetViewQx_ = q.x;
194
- camTargetViewQy_ = q.y;
195
- camTargetViewQz_ = q.z;
196
- forceRenderNextFrame_ = true;
123
+ target_.setViewCorrection(q);
197
124
  }
198
125
 
199
126
  void CesiumBridgeAndroid::setVerticalFovDeg(double degrees) {
200
127
  if (!initialized_) return;
201
128
  engine_->camera().setVerticalFovDegrees(degrees);
202
- forceRenderNextFrame_ = true;
129
+ target_.requestForceRender();
203
130
  }
204
131
 
205
132
  void CesiumBridgeAndroid::setMaximumScreenSpaceError(double v) {
206
- maxSSE_ = v;
207
- if (!initialized_) return;
208
- reactnativecesium::EngineConfig cfg;
209
- cfg.ionAccessToken = ionAccessToken_;
210
- cfg.ionAssetId = ionTilesetAssetId_;
211
- cfg.cacheDatabasePath = cacheDir_.empty() ? "" : cacheDir_ + "/cesium_cache.db";
212
- cfg.tlsCaBundlePath = cacertPath_;
213
- cfg.maximumScreenSpaceError = std::max(1.0, maxSSE_);
214
- cfg.maximumSimultaneousTileLoads = static_cast<int32_t>(std::max(0.0, std::round(maxSimLoads_)));
215
- cfg.loadingDescendantLimit = static_cast<int32_t>(std::max(1.0, std::round(loadDescLim_)));
216
- engine_->updateConfig(cfg);
217
- forceRenderNextFrame_ = true;
218
- }
219
-
220
- void CesiumBridgeAndroid::setMaximumSimultaneousTileLoads(int v) {
221
- maxSimLoads_ = static_cast<double>(v);
222
- if (!initialized_) return;
223
- setMaximumScreenSpaceError(maxSSE_); // reuses config rebuild path
133
+ config_.maximumScreenSpaceError = v;
134
+ if (initialized_) applyEngineConfig();
224
135
  }
225
-
226
- void CesiumBridgeAndroid::setLoadingDescendantLimit(int v) {
227
- loadDescLim_ = static_cast<double>(v);
228
- if (!initialized_) return;
229
- setMaximumScreenSpaceError(maxSSE_);
136
+ void CesiumBridgeAndroid::setMaximumSimultaneousTileLoads(int32_t v) {
137
+ config_.maximumSimultaneousTileLoads = v;
138
+ if (initialized_) applyEngineConfig();
139
+ }
140
+ void CesiumBridgeAndroid::setLoadingDescendantLimit(int32_t v) {
141
+ config_.loadingDescendantLimit = v;
142
+ if (initialized_) applyEngineConfig();
230
143
  }
231
144
 
232
145
  void CesiumBridgeAndroid::setMsaaSampleCount(int samples) {
233
146
  if (vulkanBackend_) vulkanBackend_->setMsaaSampleCount(samples);
234
- forceRenderNextFrame_ = true;
147
+ target_.requestForceRender();
235
148
  }
236
149
 
237
- void CesiumBridgeAndroid::markNeedsRender() {
238
- forceRenderNextFrame_ = true;
150
+ void CesiumBridgeAndroid::setMaximumCachedMiB(int32_t v) {
151
+ config_.maximumCachedBytes =
152
+ static_cast<int64_t>(std::max(16, v)) * 1024LL * 1024LL;
153
+ if (initialized_) applyEngineConfig();
154
+ }
155
+ void CesiumBridgeAndroid::setPreloadAncestors(bool v) {
156
+ config_.preloadAncestors = v;
157
+ if (initialized_) applyEngineConfig();
158
+ }
159
+ void CesiumBridgeAndroid::setPreloadSiblings(bool v) {
160
+ config_.preloadSiblings = v;
161
+ if (initialized_) applyEngineConfig();
162
+ }
163
+ void CesiumBridgeAndroid::setForbidHoles(bool v) {
164
+ config_.forbidHoles = v;
165
+ if (initialized_) applyEngineConfig();
166
+ }
167
+ void CesiumBridgeAndroid::setEnableWaterMask(bool v) {
168
+ config_.enableWaterMask = v;
169
+ if (initialized_) applyEngineConfig();
170
+ }
171
+ void CesiumBridgeAndroid::setEnableFogCulling(bool v) {
172
+ config_.enableFogCulling = v;
173
+ if (initialized_) applyEngineConfig();
174
+ }
175
+ void CesiumBridgeAndroid::setEnforceCulledScreenSpaceError(bool v) {
176
+ config_.enforceCulledScreenSpaceError = v;
177
+ if (initialized_) applyEngineConfig();
178
+ }
179
+ void CesiumBridgeAndroid::setCulledScreenSpaceError(double v) {
180
+ config_.culledScreenSpaceError = v;
181
+ if (initialized_) applyEngineConfig();
182
+ }
183
+ void CesiumBridgeAndroid::setEnableLodTransitionPeriod(bool v) {
184
+ config_.enableLodTransitionPeriod = v;
185
+ if (initialized_) applyEngineConfig();
186
+ }
187
+ void CesiumBridgeAndroid::setLodTransitionLength(double v) {
188
+ config_.lodTransitionLength = v;
189
+ if (initialized_) applyEngineConfig();
190
+ }
191
+ void CesiumBridgeAndroid::setSqliteCacheMaxRows(int32_t v) {
192
+ // Re-creating SqliteCache requires a tileset rebuild, so we only honour the
193
+ // value on next init.
194
+ config_.sqliteCacheMaxRows = std::max(64, v);
195
+ }
196
+ void CesiumBridgeAndroid::setTaskProcessorThreads(int32_t v) {
197
+ // Worker pool is sized once per CesiumEngine. We mirror it for the next
198
+ // initialize().
199
+ config_.taskProcessorThreads = std::max(0, v);
239
200
  }
240
201
 
202
+ void CesiumBridgeAndroid::markNeedsRender() { target_.requestForceRender(); }
203
+
241
204
  bool CesiumBridgeAndroid::shouldRenderNextFrame() {
242
205
  if (!initialized_ || !engine_) return false;
243
206
 
244
- const auto cur = engine_->camera().getParams();
245
- glm::dquat tgt(camTargetViewQw_, camTargetViewQx_, camTargetViewQy_, camTargetViewQz_);
246
- const bool cameraDirty =
247
- std::abs(camTargetLat_ - cur.latitude) > kEpsLatLon ||
248
- std::abs(camTargetLon_ - cur.longitude) > kEpsLatLon ||
249
- std::abs(camTargetAlt_ - cur.altitude) > kEpsAlt ||
250
- angleDeltaAbsDeg(cur.heading, camTargetHeading_) > kEpsAngleDeg ||
251
- angleDeltaAbsDeg(cur.pitch, camTargetPitch_) > kEpsAngleDeg ||
252
- angleDeltaAbsDeg(cur.roll, camTargetRoll_) > kEpsAngleDeg ||
253
- (1.0 - quatDotAbs(cur.viewCorrection, tgt)) > kEpsQuatDot;
254
-
255
- return forceRenderNextFrame_ ||
256
- frameResult_->tilesLoading > 0 ||
257
- !frameResult_->tilesetActive ||
258
- cameraDirty;
207
+ if (target_.consumeForceRender()) {
208
+ // Force-render request observed: keep it sticky for one frame so we make
209
+ // it through to the actual GPU encode below.
210
+ target_.requestForceRender();
211
+ return true;
212
+ }
213
+ if (frameResult_->tilesLoading > 0 || !frameResult_->tilesetActive) {
214
+ return true;
215
+ }
216
+ // Keep rendering at full speed until we have actual terrain tiles loaded.
217
+ // This ensures tiles start loading immediately instead of falling into idle
218
+ // mode (250ms intervals) before any content appears.
219
+ if (frameResult_->tilesRendered < 5) {
220
+ return true;
221
+ }
222
+ if (target_.isDirty()) {
223
+ const auto cur = engine_->camera().getParams();
224
+ const auto tgt = target_.snapshot();
225
+ if (reactnativecesium::CameraTargetState::deltaExceedsEpsilon(cur, tgt)) {
226
+ return true;
227
+ }
228
+ target_.clearDirty();
229
+ }
230
+ return false;
259
231
  }
260
232
 
261
233
  void CesiumBridgeAndroid::renderFrame(double dt) {
262
234
  if (!initialized_) return;
263
235
 
264
- auto cur = engine_->camera().getParams();
265
-
266
- // Smooth follower: track camera target per DOF (same as iOS)
267
- cur.latitude = camTargetLat_;
268
- cur.longitude = camTargetLon_;
269
-
270
- const double aAlt = 1.0 - std::exp(-kSmoothAlt * dt);
271
- const double aHdg = 1.0 - std::exp(-kSmoothHdg * dt);
272
- const double aPitch = 1.0 - std::exp(-kSmoothPitch * dt);
273
- const double aRoll = 1.0 - std::exp(-kSmoothRoll * dt);
274
- const double aViewQ = 1.0 - std::exp(-kSmoothViewCorr * dt);
275
-
276
- cur.altitude = cur.altitude + aAlt * (camTargetAlt_ - cur.altitude);
277
- cur.heading = lerpAngleDeg(cur.heading, camTargetHeading_, aHdg);
278
- cur.pitch = lerpAngleDeg(cur.pitch, camTargetPitch_, aPitch);
279
- cur.roll = lerpAngleDeg(cur.roll, camTargetRoll_, aRoll);
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
-
287
- if (std::abs(camTargetAlt_ - cur.altitude) <= kEpsAlt)
288
- cur.altitude = camTargetAlt_;
289
- if (angleDeltaAbsDeg(cur.heading, camTargetHeading_) <= kEpsAngleDeg)
290
- cur.heading = camTargetHeading_;
291
- if (angleDeltaAbsDeg(cur.pitch, camTargetPitch_) <= kEpsAngleDeg)
292
- cur.pitch = camTargetPitch_;
293
- if (angleDeltaAbsDeg(cur.roll, camTargetRoll_) <= kEpsAngleDeg)
294
- cur.roll = camTargetRoll_;
295
- if ((1.0 - quatDotAbs(cur.viewCorrection, tq)) <= kEpsQuatDot)
296
- cur.viewCorrection = tq;
297
-
298
- engine_->camera().setParams(cur);
299
- engine_->updateFrame(viewportWidth_, viewportHeight_, *frameResult_);
236
+ // Consume the force-render flag here (it may have been re-armed by
237
+ // shouldRenderNextFrame; either way we're rendering this frame).
238
+ (void)target_.consumeForceRender();
300
239
 
301
- const double instFps = (dt > 1e-6) ? (1.0 / dt) : 0.0;
302
- fpsEma_ = (fpsEma_ <= 1e-6) ? instFps : (fpsEma_ * 0.85 + instFps * 0.15);
303
-
304
- if (++metricsTick_ >= 20) {
305
- metricsTick_ = 0;
306
- metricsFps_ = fpsEma_;
307
- metricsTilesRendered_ = frameResult_->tilesRendered;
308
- metricsTilesLoading_ = frameResult_->tilesLoading;
309
- metricsTilesVisited_ = frameResult_->tilesVisited;
310
- metricsIonTokenConfigured_ = frameResult_->ionTokenConfigured;
311
- metricsTilesetReady_ = frameResult_->tilesetActive;
312
-
313
- if (!frameResult_->creditHtmlLines.empty()) {
314
- std::string joined;
315
- for (const auto& html : frameResult_->creditHtmlLines) {
316
- if (!joined.empty()) joined += "|";
317
- joined += html;
318
- }
319
- if (joined != lastCreditHtmlJoined_) {
320
- lastCreditHtmlJoined_ = joined;
321
- std::string credits;
322
- for (const auto& html : frameResult_->creditHtmlLines) {
323
- std::string chunk = stripHtmlToPlain(html);
324
- if (chunk.empty()) continue;
325
- if (!credits.empty()) credits += " · ";
326
- credits += chunk;
327
- }
328
- metricsCreditsPlainText_ = credits;
329
- }
330
- } else {
331
- lastCreditHtmlJoined_.clear();
332
- metricsCreditsPlainText_.clear();
333
- }
240
+ const auto cur = engine_->camera().getParams();
241
+ const auto tgt = target_.snapshot();
242
+ const auto smooth = reactnativecesium::CameraSmoother::step(cur, tgt, dt);
243
+ engine_->camera().setParams(smooth);
244
+
245
+ // If we converged, clear the dirty flag so the next idle vsync can early-out.
246
+ if (!reactnativecesium::CameraTargetState::deltaExceedsEpsilon(smooth, tgt)) {
247
+ target_.clearDirty();
334
248
  }
335
249
 
250
+ engine_->updateFrame(viewportWidth_, viewportHeight_, *frameResult_);
251
+ metrics_.tick(dt, *frameResult_, !config_.tlsCaBundlePath.empty());
252
+
336
253
  reactnativecesium::FrameParams params;
337
254
  vulkanBackend_->beginFrame(params);
338
255
  vulkanBackend_->drawScene(*frameResult_);
339
256
  vulkanBackend_->endFrame();
340
- forceRenderNextFrame_ = false;
341
257
  }
342
258
 
343
259
  double CesiumBridgeAndroid::readCameraLatitude() {
@@ -361,7 +277,6 @@ double CesiumBridgeAndroid::readCameraRoll() {
361
277
  double CesiumBridgeAndroid::readVerticalFovDeg() {
362
278
  return engine_ ? engine_->camera().getVerticalFovDegrees() : 60.0;
363
279
  }
364
-
365
280
  double CesiumBridgeAndroid::readViewCorrectionW() {
366
281
  return engine_ ? engine_->camera().getParams().viewCorrection.w : 1.0;
367
282
  }
@@ -566,9 +481,63 @@ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeGetMetricsTileset
566
481
  return getBridge(ptr)->metricsTilesetReady() ? JNI_TRUE : JNI_FALSE;
567
482
  }
568
483
 
484
+ JNIEXPORT jboolean JNICALL
485
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeGetMetricsTlsConfigured(JNIEnv*, jobject, jlong ptr) {
486
+ return getBridge(ptr)->metricsTlsConfigured() ? JNI_TRUE : JNI_FALSE;
487
+ }
488
+
569
489
  JNIEXPORT jstring JNICALL
570
490
  Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeGetMetricsCreditsPlainText(JNIEnv* env, jobject, jlong ptr) {
571
491
  return env->NewStringUTF(getBridge(ptr)->metricsCreditsPlainText().c_str());
572
492
  }
573
493
 
494
+ JNIEXPORT void JNICALL
495
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetMaximumCachedMiB(JNIEnv*, jobject, jlong ptr, jint v) {
496
+ getBridge(ptr)->setMaximumCachedMiB(v);
497
+ }
498
+ JNIEXPORT void JNICALL
499
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetPreloadAncestors(JNIEnv*, jobject, jlong ptr, jboolean v) {
500
+ getBridge(ptr)->setPreloadAncestors(v == JNI_TRUE);
501
+ }
502
+ JNIEXPORT void JNICALL
503
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetPreloadSiblings(JNIEnv*, jobject, jlong ptr, jboolean v) {
504
+ getBridge(ptr)->setPreloadSiblings(v == JNI_TRUE);
505
+ }
506
+ JNIEXPORT void JNICALL
507
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetForbidHoles(JNIEnv*, jobject, jlong ptr, jboolean v) {
508
+ getBridge(ptr)->setForbidHoles(v == JNI_TRUE);
509
+ }
510
+ JNIEXPORT void JNICALL
511
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetEnableWaterMask(JNIEnv*, jobject, jlong ptr, jboolean v) {
512
+ getBridge(ptr)->setEnableWaterMask(v == JNI_TRUE);
513
+ }
514
+ JNIEXPORT void JNICALL
515
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetEnableFogCulling(JNIEnv*, jobject, jlong ptr, jboolean v) {
516
+ getBridge(ptr)->setEnableFogCulling(v == JNI_TRUE);
517
+ }
518
+ JNIEXPORT void JNICALL
519
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetEnforceCulledScreenSpaceError(JNIEnv*, jobject, jlong ptr, jboolean v) {
520
+ getBridge(ptr)->setEnforceCulledScreenSpaceError(v == JNI_TRUE);
521
+ }
522
+ JNIEXPORT void JNICALL
523
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetCulledScreenSpaceError(JNIEnv*, jobject, jlong ptr, jdouble v) {
524
+ getBridge(ptr)->setCulledScreenSpaceError(v);
525
+ }
526
+ JNIEXPORT void JNICALL
527
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetEnableLodTransitionPeriod(JNIEnv*, jobject, jlong ptr, jboolean v) {
528
+ getBridge(ptr)->setEnableLodTransitionPeriod(v == JNI_TRUE);
529
+ }
530
+ JNIEXPORT void JNICALL
531
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetLodTransitionLength(JNIEnv*, jobject, jlong ptr, jdouble v) {
532
+ getBridge(ptr)->setLodTransitionLength(v);
533
+ }
534
+ JNIEXPORT void JNICALL
535
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetSqliteCacheMaxRows(JNIEnv*, jobject, jlong ptr, jint v) {
536
+ getBridge(ptr)->setSqliteCacheMaxRows(v);
537
+ }
538
+ JNIEXPORT void JNICALL
539
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetTaskProcessorThreads(JNIEnv*, jobject, jlong ptr, jint v) {
540
+ getBridge(ptr)->setTaskProcessorThreads(v);
541
+ }
542
+
574
543
  } // extern "C"