react-native-cesium 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/android/src/main/cpp/CesiumBridgeAndroid.cpp +25 -1
  2. package/android/src/main/cpp/CesiumBridgeAndroid.h +1 -0
  3. package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +2 -0
  4. package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +3 -0
  5. package/cpp/engine/CesiumEngine.cpp +32 -0
  6. package/cpp/engine/CesiumEngine.hpp +16 -0
  7. package/ios/CesiumBridge.h +1 -0
  8. package/ios/CesiumBridge.mm +24 -1
  9. package/ios/HybridCesiumView.swift +4 -0
  10. package/lib/typescript/src/specs/cesium-view.nitro.d.ts +7 -0
  11. package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
  12. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +9 -0
  13. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +2 -0
  14. package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +4 -0
  15. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +6 -0
  16. package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +7 -0
  17. package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +5 -0
  18. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +1 -0
  19. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +24 -0
  20. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +2 -0
  21. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +2 -0
  22. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +11 -0
  23. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +1 -0
  24. package/nitrogen/generated/shared/json/CesiumViewConfig.json +1 -0
  25. package/package.json +1 -1
  26. package/scripts/cesium/clone.mjs +1 -1
  27. package/scripts/cesium/config.mjs +8 -0
  28. package/scripts/cesium/ensure-native.mjs +163 -17
  29. package/src/specs/cesium-view.nitro.ts +7 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include "engine/CameraSmoother.hpp"
4
4
  #include "engine/EngineTunables.hpp"
5
+ #include "engine/GeoidConverter.hpp"
5
6
  #include "vulkan/VulkanBackend.h"
6
7
 
7
8
  #include <android/native_window_jni.h>
@@ -198,6 +199,10 @@ void CesiumBridgeAndroid::setTaskProcessorThreads(int32_t v) {
198
199
  // initialize().
199
200
  config_.taskProcessorThreads = std::max(0, v);
200
201
  }
202
+ void CesiumBridgeAndroid::setMinAltitudeAboveTerrain(float v) {
203
+ config_.minAltitudeAboveTerrain = std::max(0.0f, v);
204
+ if (initialized_) applyEngineConfig();
205
+ }
201
206
 
202
207
  void CesiumBridgeAndroid::markNeedsRender() { target_.requestForceRender(); }
203
208
 
@@ -239,8 +244,23 @@ void CesiumBridgeAndroid::renderFrame(double dt) {
239
244
 
240
245
  const auto cur = engine_->camera().getParams();
241
246
  const auto tgt = target_.snapshot();
242
- const auto smooth = reactnativecesium::CameraSmoother::step(
247
+ auto smooth = reactnativecesium::CameraSmoother::step(
243
248
  cur, tgt, dt, target_.recentIntervalSec());
249
+
250
+ // ── Terrain floor clamp ───────────────────────────────────────────────
251
+ const float minAbove = engine_->getConfig().minAltitudeAboveTerrain;
252
+ if (minAbove > 0.0f) {
253
+ const double geoidOffset =
254
+ reactnativecesium::mslToEllipsoidMeters(smooth.latitude, smooth.longitude, 0.0);
255
+ const double camEllipsoid =
256
+ reactnativecesium::mslToEllipsoidMeters(smooth.latitude, smooth.longitude, smooth.altitude);
257
+ const double minEllipsoid =
258
+ static_cast<double>(engine_->terrainFloorEllipsoidMeters()) + minAbove;
259
+ if (camEllipsoid < minEllipsoid) {
260
+ smooth.altitude = minEllipsoid - geoidOffset;
261
+ }
262
+ }
263
+
244
264
  engine_->camera().setParams(smooth);
245
265
 
246
266
  // If we converged, clear the dirty flag so the next idle vsync can early-out.
@@ -540,5 +560,9 @@ JNIEXPORT void JNICALL
540
560
  Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetTaskProcessorThreads(JNIEnv*, jobject, jlong ptr, jint v) {
541
561
  getBridge(ptr)->setTaskProcessorThreads(v);
542
562
  }
563
+ JNIEXPORT void JNICALL
564
+ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetMinAltitudeAboveTerrain(JNIEnv*, jobject, jlong ptr, jfloat v) {
565
+ getBridge(ptr)->setMinAltitudeAboveTerrain(v);
566
+ }
543
567
 
544
568
  } // extern "C"
@@ -52,6 +52,7 @@ struct CesiumBridgeAndroid {
52
52
  void setLodTransitionLength(double v);
53
53
  void setSqliteCacheMaxRows(int32_t v);
54
54
  void setTaskProcessorThreads(int32_t v);
55
+ void setMinAltitudeAboveTerrain(float v);
55
56
 
56
57
  void markNeedsRender();
57
58
  bool shouldRenderNextFrame();
@@ -82,6 +82,7 @@ class CesiumBridgeJNI {
82
82
  fun setLodTransitionLength(v: Double) = nativeSetLodTransitionLength(nativePtr, v)
83
83
  fun setSqliteCacheMaxRows(v: Int) = nativeSetSqliteCacheMaxRows(nativePtr, v)
84
84
  fun setTaskProcessorThreads(v: Int) = nativeSetTaskProcessorThreads(nativePtr, v)
85
+ fun setMinAltitudeAboveTerrain(v: Float) = nativeSetMinAltitudeAboveTerrain(nativePtr, v)
85
86
 
86
87
  private external fun nativeCreate(): Long
87
88
  private external fun nativeInit(ptr: Long, surface: Surface, w: Int, h: Int, cacheDir: String, cacertPath: String)
@@ -144,4 +145,5 @@ class CesiumBridgeJNI {
144
145
  private external fun nativeSetLodTransitionLength(ptr: Long, v: Double)
145
146
  private external fun nativeSetSqliteCacheMaxRows(ptr: Long, v: Int)
146
147
  private external fun nativeSetTaskProcessorThreads(ptr: Long, v: Int)
148
+ private external fun nativeSetMinAltitudeAboveTerrain(ptr: Long, v: Float)
147
149
  }
@@ -120,6 +120,8 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
120
120
  set(value) { field = value; if (value != null) postToRender { bridge?.setSqliteCacheMaxRows(value.toInt()) } }
121
121
  override var taskProcessorThreads: Double? = null
122
122
  set(value) { field = value; if (value != null) postToRender { bridge?.setTaskProcessorThreads(value.toInt()) } }
123
+ override var minAltitudeAboveTerrain: Double? = null
124
+ set(value) { field = value; if (value != null) postToRender { bridge?.setMinAltitudeAboveTerrain(value.toFloat()) } }
123
125
 
124
126
  override var onMetrics: ((metrics: CesiumMetrics) -> Unit)? = null
125
127
 
@@ -476,6 +478,7 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
476
478
  lodTransitionLength?.let { b.setLodTransitionLength(it) }
477
479
  sqliteCacheMaxRows?.let { b.setSqliteCacheMaxRows(it.toInt()) }
478
480
  taskProcessorThreads?.let { b.setTaskProcessorThreads(it.toInt()) }
481
+ minAltitudeAboveTerrain?.let { b.setMinAltitudeAboveTerrain(it.toFloat()) }
479
482
  }
480
483
 
481
484
  /**
@@ -455,6 +455,16 @@ void CesiumEngine::updateFrame(double w, double h, FrameResult& result) {
455
455
  }
456
456
  }
457
457
 
458
+ // ── Terrain floor tracking (for minAltitudeAboveTerrain clamp) ───────────
459
+ // Computed once before the tile loop and updated per-tile inside it.
460
+ // Only active when the feature is enabled to avoid any overhead otherwise.
461
+ const bool trackTerrainFloor = (config_.minAltitudeAboveTerrain > 0.0f);
462
+ const CameraParams camParams = trackTerrainFloor ? camera_.getParams() : CameraParams{};
463
+ const double camLatRad = glm::radians(camParams.latitude);
464
+ const double camLonRad = glm::radians(camParams.longitude);
465
+ float bestTerrainFloor = terrainFloorEllipsoidM_; // carry previous value if no tile matches
466
+ double bestTerrainDist = std::numeric_limits<double>::max();
467
+
458
468
  for (const auto& tile : updateResult.tilesToRenderThisFrame) {
459
469
  if (!tile) continue;
460
470
 
@@ -478,6 +488,24 @@ void CesiumEngine::updateFrame(double w, double h, FrameResult& result) {
478
488
  static_cast<float>(rect.getSouth()),
479
489
  static_cast<float>(rect.getEast()),
480
490
  static_cast<float>(rect.getNorth()));
491
+
492
+ // ── Terrain floor: find nearest vertex to camera nadir ────────────
493
+ if (trackTerrainFloor &&
494
+ rect.contains(CesiumGeospatial::Cartographic(camLonRad, camLatRad, 0.0))) {
495
+ for (const auto& prim : res->primitives) {
496
+ if (prim.altitudes.empty() || prim.localPositions.empty()) continue;
497
+ const size_t vertexCount = prim.localPositions.size();
498
+ for (size_t vi = 0; vi < vertexCount; ++vi) {
499
+ const glm::dvec3 vECEF =
500
+ glm::dvec3(prim.localPositions[vi]) + prim.rtcCenter;
501
+ const double dist = glm::length(vECEF - cameraPos);
502
+ if (dist < bestTerrainDist) {
503
+ bestTerrainDist = dist;
504
+ bestTerrainFloor = prim.altitudes[vi];
505
+ }
506
+ }
507
+ }
508
+ }
481
509
  }
482
510
 
483
511
  for (const auto& prim : res->primitives) {
@@ -553,6 +581,10 @@ void CesiumEngine::updateFrame(double w, double h, FrameResult& result) {
553
581
  result.cameraLon = params.longitude;
554
582
  result.cameraAlt = params.altitude;
555
583
 
584
+ if (trackTerrainFloor) {
585
+ terrainFloorEllipsoidM_ = bestTerrainFloor;
586
+ }
587
+
556
588
  lifecycle_.advanceFrame();
557
589
  }
558
590
 
@@ -78,6 +78,11 @@ struct EngineConfig {
78
78
  // 0 means "auto-detect" via std::thread::hardware_concurrency() clamped to
79
79
  // [2, 8]. Override only for benchmarking / regression testing.
80
80
  int32_t taskProcessorThreads = 0;
81
+
82
+ // ── Camera constraints ───────────────────────────────────────────────
83
+ // Minimum altitude above the loaded terrain surface (metres MSL).
84
+ // 0 = no constraint (default). Typical use: 2.0 to prevent going underground.
85
+ float minAltitudeAboveTerrain = 0.0f;
81
86
  };
82
87
 
83
88
  class CesiumEngine {
@@ -112,6 +117,13 @@ public:
112
117
  bool tilesetReady() const { return tileset_ != nullptr; }
113
118
  int64_t currentImageryAssetId() const { return currentImageryAssetId_; }
114
119
 
120
+ const EngineConfig& getConfig() const { return config_; }
121
+
122
+ // Ellipsoid height (metres) of the terrain vertex nearest to the camera nadir,
123
+ // updated each frame from the loaded tile mesh. 0.0 until the first terrain
124
+ // tile covering the camera position has been rendered.
125
+ float terrainFloorEllipsoidMeters() const { return terrainFloorEllipsoidM_; }
126
+
115
127
  private:
116
128
  void createTileset();
117
129
  void destroyTileset();
@@ -149,6 +161,10 @@ private:
149
161
  GlobeCamera camera_;
150
162
  TileLifecycleManager lifecycle_;
151
163
 
164
+ // Ellipsoid height of the terrain surface at the camera nadir, updated each
165
+ // frame. Initialised to 0 (sea level) until a terrain tile is first loaded.
166
+ float terrainFloorEllipsoidM_ = 0.0f;
167
+
152
168
  // Pre-computed fallback ellipsoid geometry (ECEF, absolute).
153
169
  std::vector<glm::dvec3> ellipsoidPositions_;
154
170
  std::vector<uint32_t> ellipsoidIndices_;
@@ -61,6 +61,7 @@ NS_ASSUME_NONNULL_BEGIN
61
61
  - (void)setLodTransitionLength:(double)v;
62
62
  - (void)setSqliteCacheMaxRows:(int32_t)v;
63
63
  - (void)setTaskProcessorThreads:(int32_t)v;
64
+ - (void)setMinAltitudeAboveTerrain:(float)v;
64
65
 
65
66
  /// Throttled metrics consumed by the Swift hybrid view.
66
67
  @property (nonatomic, readonly) double metricsFps;
@@ -5,6 +5,7 @@
5
5
  #include "engine/CameraTargetState.hpp"
6
6
  #include "engine/CesiumEngine.hpp"
7
7
  #include "engine/EngineTunables.hpp"
8
+ #include "engine/GeoidConverter.hpp"
8
9
  #include "engine/MetricsAggregator.hpp"
9
10
  #include "metal/MetalBackend.h"
10
11
 
@@ -237,6 +238,11 @@
237
238
  - (void)setTaskProcessorThreads:(int32_t)v {
238
239
  _config.taskProcessorThreads = std::max<int32_t>(0, v);
239
240
  }
241
+ - (void)setMinAltitudeAboveTerrain:(float)v {
242
+ _config.minAltitudeAboveTerrain = std::max(0.0f, v);
243
+ if (_engine) _engine->updateConfig(_config);
244
+ _target.requestForceRender();
245
+ }
240
246
 
241
247
  - (void)setMsaaSampleCount:(int)samples {
242
248
  if (_metalBackend) _metalBackend->setMsaaSampleCount(samples);
@@ -271,8 +277,25 @@
271
277
 
272
278
  const auto cur = _engine->camera().getParams();
273
279
  const auto tgt = _target.snapshot();
274
- const auto smooth = reactnativecesium::CameraSmoother::step(
280
+ auto smooth = reactnativecesium::CameraSmoother::step(
275
281
  cur, tgt, dt, _target.recentIntervalSec());
282
+
283
+ // ── Terrain floor clamp ───────────────────────────────────────────────
284
+ // Uses terrain floor from the previous frame (one-frame lag is
285
+ // imperceptible at 60 fps). Skipped when minAltitudeAboveTerrain == 0.
286
+ const float minAbove = _engine->getConfig().minAltitudeAboveTerrain;
287
+ if (minAbove > 0.0f) {
288
+ const double geoidOffset =
289
+ reactnativecesium::mslToEllipsoidMeters(smooth.latitude, smooth.longitude, 0.0);
290
+ const double camEllipsoid =
291
+ reactnativecesium::mslToEllipsoidMeters(smooth.latitude, smooth.longitude, smooth.altitude);
292
+ const double minEllipsoid =
293
+ static_cast<double>(_engine->terrainFloorEllipsoidMeters()) + minAbove;
294
+ if (camEllipsoid < minEllipsoid) {
295
+ smooth.altitude = minEllipsoid - geoidOffset;
296
+ }
297
+ }
298
+
276
299
  _engine->camera().setParams(smooth);
277
300
 
278
301
  if (!reactnativecesium::CameraTargetState::deltaExceedsEpsilon(smooth, tgt)) {
@@ -101,6 +101,9 @@ class HybridCesiumView: HybridCesiumViewSpec {
101
101
  var taskProcessorThreads: Double? {
102
102
  didSet { if let v = taskProcessorThreads { postToRender { [weak self] in self?.bridge?.setTaskProcessorThreads(Int32(v)) } } }
103
103
  }
104
+ var minAltitudeAboveTerrain: Double? {
105
+ didSet { if let v = minAltitudeAboveTerrain { postToRender { [weak self] in self?.bridge?.setMinAltitudeAboveTerrain(Float(v)) } } }
106
+ }
104
107
 
105
108
  // MARK: - Render-thread dispatch helper
106
109
 
@@ -319,6 +322,7 @@ class HybridCesiumView: HybridCesiumViewSpec {
319
322
  if let v = lodTransitionLength { bridge?.setLodTransitionLength(v) }
320
323
  if let v = sqliteCacheMaxRows { bridge?.setSqliteCacheMaxRows(Int32(v)) }
321
324
  if let v = taskProcessorThreads { bridge?.setTaskProcessorThreads(Int32(v)) }
325
+ if let v = minAltitudeAboveTerrain { bridge?.setMinAltitudeAboveTerrain(Float(v)) }
322
326
  }
323
327
 
324
328
  /// Pushes camera state into native bridge only when values changed.
@@ -67,6 +67,13 @@ export interface CesiumViewProps extends HybridViewProps {
67
67
  sqliteCacheMaxRows?: number;
68
68
  /** Worker thread count. 0 = auto (hardware_concurrency-1, clamped 2..8). */
69
69
  taskProcessorThreads?: number;
70
+ /**
71
+ * Minimum altitude above the loaded terrain surface, in metres MSL.
72
+ * Default 0 (no constraint). Set to e.g. 2 to prevent the camera from going underground.
73
+ * The clamp is applied every frame in native, based on the altitude of the nearest
74
+ * terrain mesh vertex to the camera nadir.
75
+ */
76
+ minAltitudeAboveTerrain?: number;
70
77
  onMetrics?: (metrics: CesiumMetrics) => void;
71
78
  }
72
79
  export interface CesiumViewMethods extends HybridViewMethods {
@@ -1 +1 @@
1
- {"version":3,"file":"cesium-view.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/cesium-view.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,iBAAiB,EAClB,MAAM,4BAA4B,CAAA;AAEnC,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,+DAA+D;IAC/D,aAAa,EAAE,OAAO,CAAA;IACtB,YAAY,EAAE,OAAO,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,aAAa,EAAE,WAAW,CAAA;IAC1B,cAAc,EAAE,OAAO,CAAA;IACvB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,4BAA4B,EAAE,MAAM,CAAA;IACpC,sBAAsB,EAAE,MAAM,CAAA;IAC9B,6CAA6C;IAC7C,eAAe,EAAE,MAAM,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IAGzB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,mGAAmG;IACnG,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,mEAAmE;IACnE,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,gFAAgF;IAChF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,oEAAoE;IACpE,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,2EAA2E;IAC3E,6BAA6B,CAAC,EAAE,OAAO,CAAA;IACvC,4FAA4F;IAC5F,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,8EAA8E;IAC9E,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,mDAAmD;IACnD,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,mDAAmD;IACnD,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,4EAA4E;IAC5E,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAE7B,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;CAC7C;AAED,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,+CAA+C;IAC/C,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC,CAAA;IACtC;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC;;;OAGG;IACH,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,GAAG,IAAI,CAAA;IAC1E,4FAA4F;IAC5F,iBAAiB,IAAI,OAAO,CAAC,UAAU,CAAC,CAAA;CACzC;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAA"}
1
+ {"version":3,"file":"cesium-view.nitro.d.ts","sourceRoot":"","sources":["../../../../src/specs/cesium-view.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,eAAe,EACf,iBAAiB,EAClB,MAAM,4BAA4B,CAAA;AAEnC,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,kBAAkB,EAAE,OAAO,CAAA;IAC3B,+DAA+D;IAC/D,aAAa,EAAE,OAAO,CAAA;IACtB,YAAY,EAAE,OAAO,CAAA;IACrB,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,cAAc,EAAE,MAAM,CAAA;IACtB,UAAU,EAAE,MAAM,CAAA;IAClB,4EAA4E;IAC5E,aAAa,EAAE,WAAW,CAAA;IAC1B,cAAc,EAAE,OAAO,CAAA;IACvB,uBAAuB,EAAE,MAAM,CAAA;IAC/B,4BAA4B,EAAE,MAAM,CAAA;IACpC,sBAAsB,EAAE,MAAM,CAAA;IAC9B,6CAA6C;IAC7C,eAAe,EAAE,MAAM,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IAGzB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,mGAAmG;IACnG,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,mEAAmE;IACnE,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,gFAAgF;IAChF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,oEAAoE;IACpE,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,2EAA2E;IAC3E,6BAA6B,CAAC,EAAE,OAAO,CAAA;IACvC,4FAA4F;IAC5F,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,8EAA8E;IAC9E,yBAAyB,CAAC,EAAE,OAAO,CAAA;IACnC,mDAAmD;IACnD,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,mDAAmD;IACnD,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,4EAA4E;IAC5E,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAEhC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;CAC7C;AAED,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,+CAA+C;IAC/C,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC,CAAA;IACtC;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;IACpC;;;OAGG;IACH,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,GAAG,IAAI,CAAA;IAC1E,4FAA4F;IAC5F,iBAAiB,IAAI,OAAO,CAAC,UAAU,CAAC,CAAA;CACzC;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,eAAe,EAAE,iBAAiB,CAAC,CAAA"}
@@ -247,6 +247,15 @@ namespace margelo::nitro::reactnativecesium {
247
247
  static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JDouble> /* taskProcessorThreads */)>("setTaskProcessorThreads");
248
248
  method(_javaPart, taskProcessorThreads.has_value() ? jni::JDouble::valueOf(taskProcessorThreads.value()) : nullptr);
249
249
  }
250
+ std::optional<double> JHybridCesiumViewSpec::getMinAltitudeAboveTerrain() {
251
+ static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<jni::JDouble>()>("getMinAltitudeAboveTerrain");
252
+ auto __result = method(_javaPart);
253
+ return __result != nullptr ? std::make_optional(__result->value()) : std::nullopt;
254
+ }
255
+ void JHybridCesiumViewSpec::setMinAltitudeAboveTerrain(std::optional<double> minAltitudeAboveTerrain) {
256
+ static const auto method = _javaPart->javaClassStatic()->getMethod<void(jni::alias_ref<jni::JDouble> /* minAltitudeAboveTerrain */)>("setMinAltitudeAboveTerrain");
257
+ method(_javaPart, minAltitudeAboveTerrain.has_value() ? jni::JDouble::valueOf(minAltitudeAboveTerrain.value()) : nullptr);
258
+ }
250
259
  std::optional<std::function<void(const CesiumMetrics& /* metrics */)>> JHybridCesiumViewSpec::getOnMetrics() {
251
260
  static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JFunc_void_CesiumMetrics::javaobject>()>("getOnMetrics_cxx");
252
261
  auto __result = method(_javaPart);
@@ -92,6 +92,8 @@ namespace margelo::nitro::reactnativecesium {
92
92
  void setSqliteCacheMaxRows(std::optional<double> sqliteCacheMaxRows) override;
93
93
  std::optional<double> getTaskProcessorThreads() override;
94
94
  void setTaskProcessorThreads(std::optional<double> taskProcessorThreads) override;
95
+ std::optional<double> getMinAltitudeAboveTerrain() override;
96
+ void setMinAltitudeAboveTerrain(std::optional<double> minAltitudeAboveTerrain) override;
95
97
  std::optional<std::function<void(const CesiumMetrics& /* metrics */)>> getOnMetrics() override;
96
98
  void setOnMetrics(const std::optional<std::function<void(const CesiumMetrics& /* metrics */)>>& onMetrics) override;
97
99
 
@@ -121,6 +121,10 @@ void JHybridCesiumViewStateUpdater::updateViewProps(jni::alias_ref<jni::JClass>
121
121
  hybridView->setTaskProcessorThreads(props->taskProcessorThreads.value);
122
122
  props->taskProcessorThreads.isDirty = false;
123
123
  }
124
+ if (props->minAltitudeAboveTerrain.isDirty) {
125
+ hybridView->setMinAltitudeAboveTerrain(props->minAltitudeAboveTerrain.value);
126
+ props->minAltitudeAboveTerrain.isDirty = false;
127
+ }
124
128
  if (props->onMetrics.isDirty) {
125
129
  hybridView->setOnMetrics(props->onMetrics.value);
126
130
  props->onMetrics.isDirty = false;
@@ -153,6 +153,12 @@ abstract class HybridCesiumViewSpec: HybridView() {
153
153
  @set:Keep
154
154
  abstract var taskProcessorThreads: Double?
155
155
 
156
+ @get:DoNotStrip
157
+ @get:Keep
158
+ @set:DoNotStrip
159
+ @set:Keep
160
+ abstract var minAltitudeAboveTerrain: Double?
161
+
156
162
  abstract var onMetrics: ((metrics: CesiumMetrics) -> Unit)?
157
163
 
158
164
  private var onMetrics_cxx: Func_void_CesiumMetrics?
@@ -212,6 +212,13 @@ namespace margelo::nitro::reactnativecesium {
212
212
  inline void setTaskProcessorThreads(std::optional<double> taskProcessorThreads) noexcept override {
213
213
  _swiftPart.setTaskProcessorThreads(taskProcessorThreads);
214
214
  }
215
+ inline std::optional<double> getMinAltitudeAboveTerrain() noexcept override {
216
+ auto __result = _swiftPart.getMinAltitudeAboveTerrain();
217
+ return __result;
218
+ }
219
+ inline void setMinAltitudeAboveTerrain(std::optional<double> minAltitudeAboveTerrain) noexcept override {
220
+ _swiftPart.setMinAltitudeAboveTerrain(minAltitudeAboveTerrain);
221
+ }
215
222
  inline std::optional<std::function<void(const CesiumMetrics& /* metrics */)>> getOnMetrics() noexcept override {
216
223
  auto __result = _swiftPart.getOnMetrics();
217
224
  return __result;
@@ -184,6 +184,11 @@ using namespace margelo::nitro::reactnativecesium::views;
184
184
  swiftPart.setTaskProcessorThreads(newViewProps.taskProcessorThreads.value);
185
185
  newViewProps.taskProcessorThreads.isDirty = false;
186
186
  }
187
+ // minAltitudeAboveTerrain: optional
188
+ if (newViewProps.minAltitudeAboveTerrain.isDirty) {
189
+ swiftPart.setMinAltitudeAboveTerrain(newViewProps.minAltitudeAboveTerrain.value);
190
+ newViewProps.minAltitudeAboveTerrain.isDirty = false;
191
+ }
187
192
  // onMetrics: optional
188
193
  if (newViewProps.onMetrics.isDirty) {
189
194
  swiftPart.setOnMetrics(newViewProps.onMetrics.value);
@@ -31,6 +31,7 @@ public protocol HybridCesiumViewSpec_protocol: HybridObject, HybridView {
31
31
  var lodTransitionLength: Double? { get set }
32
32
  var sqliteCacheMaxRows: Double? { get set }
33
33
  var taskProcessorThreads: Double? { get set }
34
+ var minAltitudeAboveTerrain: Double? { get set }
34
35
  var onMetrics: ((_ metrics: CesiumMetrics) -> Void)? { get set }
35
36
 
36
37
  // Methods
@@ -508,6 +508,30 @@ open class HybridCesiumViewSpec_cxx {
508
508
  }
509
509
  }
510
510
 
511
+ public final var minAltitudeAboveTerrain: bridge.std__optional_double_ {
512
+ @inline(__always)
513
+ get {
514
+ return { () -> bridge.std__optional_double_ in
515
+ if let __unwrappedValue = self.__implementation.minAltitudeAboveTerrain {
516
+ return bridge.create_std__optional_double_(__unwrappedValue)
517
+ } else {
518
+ return .init()
519
+ }
520
+ }()
521
+ }
522
+ @inline(__always)
523
+ set {
524
+ self.__implementation.minAltitudeAboveTerrain = { () -> Double? in
525
+ if bridge.has_value_std__optional_double_(newValue) {
526
+ let __unwrapped = bridge.get_std__optional_double_(newValue)
527
+ return __unwrapped
528
+ } else {
529
+ return nil
530
+ }
531
+ }()
532
+ }
533
+ }
534
+
511
535
  public final var onMetrics: bridge.std__optional_std__function_void_const_CesiumMetrics_____metrics______ {
512
536
  @inline(__always)
513
537
  get {
@@ -56,6 +56,8 @@ namespace margelo::nitro::reactnativecesium {
56
56
  prototype.registerHybridSetter("sqliteCacheMaxRows", &HybridCesiumViewSpec::setSqliteCacheMaxRows);
57
57
  prototype.registerHybridGetter("taskProcessorThreads", &HybridCesiumViewSpec::getTaskProcessorThreads);
58
58
  prototype.registerHybridSetter("taskProcessorThreads", &HybridCesiumViewSpec::setTaskProcessorThreads);
59
+ prototype.registerHybridGetter("minAltitudeAboveTerrain", &HybridCesiumViewSpec::getMinAltitudeAboveTerrain);
60
+ prototype.registerHybridSetter("minAltitudeAboveTerrain", &HybridCesiumViewSpec::setMinAltitudeAboveTerrain);
59
61
  prototype.registerHybridGetter("onMetrics", &HybridCesiumViewSpec::getOnMetrics);
60
62
  prototype.registerHybridSetter("onMetrics", &HybridCesiumViewSpec::setOnMetrics);
61
63
  prototype.registerHybridMethod("getCameraState", &HybridCesiumViewSpec::getCameraState);
@@ -97,6 +97,8 @@ namespace margelo::nitro::reactnativecesium {
97
97
  virtual void setSqliteCacheMaxRows(std::optional<double> sqliteCacheMaxRows) = 0;
98
98
  virtual std::optional<double> getTaskProcessorThreads() = 0;
99
99
  virtual void setTaskProcessorThreads(std::optional<double> taskProcessorThreads) = 0;
100
+ virtual std::optional<double> getMinAltitudeAboveTerrain() = 0;
101
+ virtual void setMinAltitudeAboveTerrain(std::optional<double> minAltitudeAboveTerrain) = 0;
100
102
  virtual std::optional<std::function<void(const CesiumMetrics& /* metrics */)>> getOnMetrics() = 0;
101
103
  virtual void setOnMetrics(const std::optional<std::function<void(const CesiumMetrics& /* metrics */)>>& onMetrics) = 0;
102
104
 
@@ -236,6 +236,16 @@ namespace margelo::nitro::reactnativecesium::views {
236
236
  throw std::runtime_error(std::string("CesiumView.taskProcessorThreads: ") + exc.what());
237
237
  }
238
238
  }()),
239
+ minAltitudeAboveTerrain([&]() -> CachedProp<std::optional<double>> {
240
+ try {
241
+ const react::RawValue* rawValue = rawProps.at("minAltitudeAboveTerrain", nullptr, nullptr);
242
+ if (rawValue == nullptr) return sourceProps.minAltitudeAboveTerrain;
243
+ const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
244
+ return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.minAltitudeAboveTerrain);
245
+ } catch (const std::exception& exc) {
246
+ throw std::runtime_error(std::string("CesiumView.minAltitudeAboveTerrain: ") + exc.what());
247
+ }
248
+ }()),
239
249
  onMetrics([&]() -> CachedProp<std::optional<std::function<void(const CesiumMetrics& /* metrics */)>>> {
240
250
  try {
241
251
  const react::RawValue* rawValue = rawProps.at("onMetrics", nullptr, nullptr);
@@ -280,6 +290,7 @@ namespace margelo::nitro::reactnativecesium::views {
280
290
  case hashString("lodTransitionLength"): return true;
281
291
  case hashString("sqliteCacheMaxRows"): return true;
282
292
  case hashString("taskProcessorThreads"): return true;
293
+ case hashString("minAltitudeAboveTerrain"): return true;
283
294
  case hashString("onMetrics"): return true;
284
295
  case hashString("hybridRef"): return true;
285
296
  default: return false;
@@ -65,6 +65,7 @@ namespace margelo::nitro::reactnativecesium::views {
65
65
  CachedProp<std::optional<double>> lodTransitionLength;
66
66
  CachedProp<std::optional<double>> sqliteCacheMaxRows;
67
67
  CachedProp<std::optional<double>> taskProcessorThreads;
68
+ CachedProp<std::optional<double>> minAltitudeAboveTerrain;
68
69
  CachedProp<std::optional<std::function<void(const CesiumMetrics& /* metrics */)>>> onMetrics;
69
70
  CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridCesiumViewSpec>& /* ref */)>>> hybridRef;
70
71
 
@@ -25,6 +25,7 @@
25
25
  "lodTransitionLength": true,
26
26
  "sqliteCacheMaxRows": true,
27
27
  "taskProcessorThreads": true,
28
+ "minAltitudeAboveTerrain": true,
28
29
  "onMetrics": true,
29
30
  "hybridRef": true
30
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-cesium",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Experimental Cesium rendering for React Native via Nitro (iOS/Metal today; Android planned)",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/module/index.js",
@@ -6,13 +6,13 @@ import { execFileSync } from 'node:child_process'
6
6
  import { existsSync, mkdirSync } from 'node:fs'
7
7
  import { dirname, join } from 'node:path'
8
8
  import { fileURLToPath } from 'node:url'
9
+ import { CESIUM_NATIVE_REF as ref } from './config.mjs'
9
10
 
10
11
  const __dirname = dirname(fileURLToPath(import.meta.url))
11
12
  const root = join(__dirname, '..', '..')
12
13
  const vendor = join(root, 'vendor')
13
14
  const dest = join(vendor, 'cesium-native')
14
15
  const repo = 'https://github.com/CesiumGS/cesium-native.git'
15
- const ref = 'v0.60.0'
16
16
 
17
17
  function run(cmd, args, opts = {}) {
18
18
  execFileSync(cmd, args, { stdio: 'inherit', ...opts })
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Shared configuration for Cesium Native build scripts.
4
+ * Update CESIUM_NATIVE_REF here when bumping the pinned cesium-native version.
5
+ */
6
+
7
+ /** Git tag of CesiumGS/cesium-native to clone and build. */
8
+ export const CESIUM_NATIVE_REF = 'v0.60.0'
@@ -1,20 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * If Cesium Native vendor output is missing for the requested platform(s), run
4
- * `npm|yarn run update` then `CESIUM_BUILD_ONLY=… npm|yarn run build`.
3
+ * Ensure Cesium Native vendor output is present and built for the pinned ref.
5
4
  *
6
- * Invoked from CocoaPods (podspec prepare_command) and Android Gradle (preBuild).
5
+ * Fast-path (in order):
6
+ * 1. Vendor dir already exists and was built for the current ref → nothing to do.
7
+ * 2. Global cache (~/.cache/react-native-cesium/<ref>/<platform>/) has a matching
8
+ * build → restore from cache without recompiling.
9
+ * 3. Otherwise → clone source and build, then populate the global cache.
7
10
  *
8
- * Opt out (CI without toolchains): REACT_NATIVE_CESIUM_SKIP_NATIVE_BUILD=1 — fails fast if artifacts are still missing.
11
+ * Invoked from CocoaPods (react_native_cesium_ensure_native in Podfile pre_install)
12
+ * and Android Gradle (preBuild task).
13
+ *
14
+ * Opt out (CI without toolchains): REACT_NATIVE_CESIUM_SKIP_NATIVE_BUILD=1 —
15
+ * fails fast if artifacts are still missing.
9
16
  */
10
17
  import { spawnSync } from 'node:child_process'
11
- import { existsSync } from 'node:fs'
18
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
19
+ import { homedir } from 'node:os'
12
20
  import { dirname, join, resolve } from 'node:path'
13
21
  import { fileURLToPath } from 'node:url'
22
+ import { CESIUM_NATIVE_REF } from './config.mjs'
14
23
 
15
24
  const __dirname = dirname(fileURLToPath(import.meta.url))
16
25
  const root = resolve(join(__dirname, '..', '..'))
17
26
 
27
+ // Marker files whose existence confirms a successful build.
18
28
  const MARKER_IOS = join(root, 'vendor', 'ios', 'CesiumNative.xcframework', 'Info.plist')
19
29
  const MARKER_ANDROID = join(
20
30
  root,
@@ -26,6 +36,100 @@ const MARKER_ANDROID = join(
26
36
  'cesium-nativeConfig.cmake'
27
37
  )
28
38
 
39
+ // Stamp files that record which cesium-native ref the vendor dir was built from.
40
+ const REF_STAMP_IOS = join(root, 'vendor', 'ios', '.cesium-native-ref')
41
+ const REF_STAMP_ANDROID = join(root, 'vendor', 'android', '.cesium-native-ref')
42
+
43
+ // ─── Global cache ────────────────────────────────────────────────────────────
44
+ // Keyed by cesium-native ref so builds for different versions coexist.
45
+ // Survives node_modules being cleared between pod installs.
46
+
47
+ function globalCacheBase() {
48
+ const xdg = process.env.XDG_CACHE_HOME?.trim()
49
+ return xdg
50
+ ? join(xdg, 'react-native-cesium')
51
+ : join(homedir(), '.cache', 'react-native-cesium')
52
+ }
53
+
54
+ function cacheDir(platform, ref) {
55
+ return join(globalCacheBase(), ref, platform)
56
+ }
57
+
58
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
59
+
60
+ function readStamp(stampPath) {
61
+ try {
62
+ return readFileSync(stampPath, 'utf8').trim()
63
+ } catch {
64
+ return null
65
+ }
66
+ }
67
+
68
+ function isStale(markerPath, stampPath, ref) {
69
+ if (!existsSync(markerPath)) return true
70
+ const built = readStamp(stampPath)
71
+ if (built !== ref) {
72
+ console.log(
73
+ `react-native-cesium: vendor built for ${built ?? 'unknown ref'}, current ref is ${ref} — will refresh.`
74
+ )
75
+ return true
76
+ }
77
+ return false
78
+ }
79
+
80
+ /**
81
+ * Try to restore a platform's vendor dir from the global cache.
82
+ * Returns true if the cache hit and the restore succeeded.
83
+ */
84
+ function tryRestoreFromCache(platform, ref, vendorDir, stampPath, markerPath) {
85
+ const src = cacheDir(platform, ref)
86
+ if (!existsSync(src)) return false
87
+
88
+ console.log(
89
+ `react-native-cesium: cache hit for ${platform} ${ref} — restoring from ${src} (skipping build)`
90
+ )
91
+ try {
92
+ rmSync(vendorDir, { recursive: true, force: true })
93
+ mkdirSync(vendorDir, { recursive: true })
94
+ cpSync(src, vendorDir, { recursive: true })
95
+ // Ensure stamp is present even if the cached dir predates stamp support.
96
+ writeFileSync(stampPath, ref)
97
+ if (!existsSync(markerPath)) {
98
+ console.warn(
99
+ `react-native-cesium: cache restore for ${platform} ${ref} succeeded but marker is missing: ${markerPath}\n` +
100
+ 'The cache entry may be corrupt; delete it and retry:\n' +
101
+ ` rm -rf "${src}"`
102
+ )
103
+ return false
104
+ }
105
+ return true
106
+ } catch (e) {
107
+ console.warn(
108
+ `react-native-cesium: cache restore failed (${e.message}) — will rebuild.`
109
+ )
110
+ return false
111
+ }
112
+ }
113
+
114
+ /** Populate the global cache from a freshly built vendor dir. */
115
+ function saveToCache(platform, ref, vendorDir) {
116
+ const dst = cacheDir(platform, ref)
117
+ try {
118
+ rmSync(dst, { recursive: true, force: true })
119
+ mkdirSync(dst, { recursive: true })
120
+ cpSync(vendorDir, dst, { recursive: true })
121
+ console.log(
122
+ `react-native-cesium: saved ${platform} ${ref} build to cache (${dst})`
123
+ )
124
+ } catch (e) {
125
+ console.warn(
126
+ `react-native-cesium: could not write to cache (${e.message}) — build succeeded but next run may rebuild.`
127
+ )
128
+ }
129
+ }
130
+
131
+ // ─── Script runner ───────────────────────────────────────────────────────────
132
+
29
133
  function parseArgs(argv) {
30
134
  const want = { ios: false, android: false }
31
135
  for (const a of argv) {
@@ -46,7 +150,7 @@ function useYarn() {
46
150
  function runScript(name, extraEnv) {
47
151
  const yarn = useYarn()
48
152
  const cmd = yarn ? 'yarn' : 'npm'
49
- const args = yarn ? ['run', name] : ['run', name]
153
+ const args = ['run', name]
50
154
  const env = { ...process.env, ...extraEnv }
51
155
  const r = spawnSync(cmd, args, { cwd: root, env, stdio: 'inherit' })
52
156
  if (r.error) {
@@ -59,48 +163,88 @@ function runScript(name, extraEnv) {
59
163
  }
60
164
  }
61
165
 
166
+ // ─── Main ────────────────────────────────────────────────────────────────────
167
+
62
168
  function main() {
63
169
  const skip = process.env.REACT_NATIVE_CESIUM_SKIP_NATIVE_BUILD === '1'
64
170
  const want = parseArgs(process.argv.slice(2))
171
+ const ref = CESIUM_NATIVE_REF
172
+
173
+ const vendorIos = join(root, 'vendor', 'ios')
174
+ const vendorAndroid = join(root, 'vendor', 'android')
65
175
 
66
- const needIos = want.ios && !existsSync(MARKER_IOS)
67
- const needAndroid = want.android && !existsSync(MARKER_ANDROID)
176
+ let needIos = want.ios && isStale(MARKER_IOS, REF_STAMP_IOS, ref)
177
+ let needAndroid = want.android && isStale(MARKER_ANDROID, REF_STAMP_ANDROID, ref)
68
178
 
69
179
  if (!needIos && !needAndroid) {
180
+ // Vendor is already up to date. Back-fill the global cache if it is missing so
181
+ // that future runs (after node_modules is cleared) can restore without rebuilding.
182
+ if (want.ios && !existsSync(cacheDir('ios', ref))) {
183
+ saveToCache('ios', ref, vendorIos)
184
+ }
185
+ if (want.android && !existsSync(cacheDir('android', ref))) {
186
+ saveToCache('android', ref, vendorAndroid)
187
+ }
70
188
  return
71
189
  }
72
190
 
73
191
  if (skip) {
192
+ const missing = [
193
+ needIos && ` iOS: ${MARKER_IOS}`,
194
+ needAndroid && ` Android: ${MARKER_ANDROID}`,
195
+ ]
196
+ .filter(Boolean)
197
+ .join('\n')
74
198
  console.error(
75
- 'react-native-cesium: Cesium Native vendor output is missing, but REACT_NATIVE_CESIUM_SKIP_NATIVE_BUILD=1.\n' +
199
+ 'react-native-cesium: Cesium Native vendor output is missing or stale, but REACT_NATIVE_CESIUM_SKIP_NATIVE_BUILD=1.\n' +
76
200
  'Build the native libraries on a machine with the required toolchains, or unset the variable and retry.\n' +
77
- `Expected iOS: ${MARKER_IOS}\n` +
78
- `Expected Android: ${MARKER_ANDROID}\n` +
79
- 'See the package README (Build Cesium Native locally).'
201
+ missing +
202
+ '\nSee the package README (Build Cesium Native locally).'
80
203
  )
81
204
  process.exit(1)
82
205
  }
83
206
 
84
207
  if (needIos && process.platform !== 'darwin') {
85
208
  console.error(
86
- 'react-native-cesium: iOS Cesium Native output is missing; building it requires macOS with Xcode.\n' +
209
+ 'react-native-cesium: iOS Cesium Native output is missing or stale; building it requires macOS with Xcode.\n' +
87
210
  `Expected: ${MARKER_IOS}`
88
211
  )
89
212
  process.exit(1)
90
213
  }
91
214
 
215
+ // Fast path: restore from global cache if available.
216
+ if (needIos && tryRestoreFromCache('ios', ref, vendorIos, REF_STAMP_IOS, MARKER_IOS)) {
217
+ needIos = false
218
+ }
219
+ if (
220
+ needAndroid &&
221
+ tryRestoreFromCache('android', ref, vendorAndroid, REF_STAMP_ANDROID, MARKER_ANDROID)
222
+ ) {
223
+ needAndroid = false
224
+ }
225
+
226
+ if (!needIos && !needAndroid) return
227
+
228
+ // Clone cesium-native source if not already present.
92
229
  if (!existsSync(join(root, 'vendor', 'cesium-native'))) {
93
230
  console.log('react-native-cesium: running package update (clone Cesium Native / vcpkg prep)…')
94
231
  runScript('update', {})
95
232
  }
96
233
 
97
- if (needIos && !existsSync(MARKER_IOS)) {
234
+ if (needIos) {
98
235
  console.log('react-native-cesium: building Cesium Native for iOS (this can take a long time)…')
99
236
  runScript('build', { CESIUM_BUILD_ONLY: 'ios' })
237
+ writeFileSync(REF_STAMP_IOS, ref)
238
+ saveToCache('ios', ref, vendorIos)
100
239
  }
101
- if (needAndroid && !existsSync(MARKER_ANDROID)) {
102
- console.log('react-native-cesium: building Cesium Native for Android (this can take a long time)…')
240
+
241
+ if (needAndroid) {
242
+ console.log(
243
+ 'react-native-cesium: building Cesium Native for Android (this can take a long time)…'
244
+ )
103
245
  runScript('build', { CESIUM_BUILD_ONLY: 'android' })
246
+ writeFileSync(REF_STAMP_ANDROID, ref)
247
+ saveToCache('android', ref, vendorAndroid)
104
248
  }
105
249
 
106
250
  if (needIos && !existsSync(MARKER_IOS)) {
@@ -108,7 +252,9 @@ function main() {
108
252
  process.exit(1)
109
253
  }
110
254
  if (needAndroid && !existsSync(MARKER_ANDROID)) {
111
- console.error(`react-native-cesium: Android build finished but marker missing: ${MARKER_ANDROID}`)
255
+ console.error(
256
+ `react-native-cesium: Android build finished but marker missing: ${MARKER_ANDROID}`
257
+ )
112
258
  process.exit(1)
113
259
  }
114
260
  }
@@ -77,6 +77,13 @@ export interface CesiumViewProps extends HybridViewProps {
77
77
  sqliteCacheMaxRows?: number
78
78
  /** Worker thread count. 0 = auto (hardware_concurrency-1, clamped 2..8). */
79
79
  taskProcessorThreads?: number
80
+ /**
81
+ * Minimum altitude above the loaded terrain surface, in metres MSL.
82
+ * Default 0 (no constraint). Set to e.g. 2 to prevent the camera from going underground.
83
+ * The clamp is applied every frame in native, based on the altitude of the nearest
84
+ * terrain mesh vertex to the camera nadir.
85
+ */
86
+ minAltitudeAboveTerrain?: number
80
87
 
81
88
  onMetrics?: (metrics: CesiumMetrics) => void
82
89
  }