react-native-cesium 1.1.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.
- package/README.md +16 -0
- package/android/src/main/cpp/CesiumBridgeAndroid.cpp +26 -1
- package/android/src/main/cpp/CesiumBridgeAndroid.h +1 -0
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +2 -0
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +3 -0
- package/cpp/engine/CameraSmoother.cpp +35 -4
- package/cpp/engine/CameraSmoother.hpp +9 -4
- package/cpp/engine/CameraTargetState.cpp +21 -0
- package/cpp/engine/CameraTargetState.hpp +24 -0
- package/cpp/engine/CesiumEngine.cpp +32 -0
- package/cpp/engine/CesiumEngine.hpp +16 -0
- package/cpp/engine/EngineTunables.hpp +15 -0
- package/ios/CesiumBridge.h +1 -0
- package/ios/CesiumBridge.mm +25 -1
- package/ios/HybridCesiumView.swift +4 -0
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts +7 -0
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +9 -0
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +2 -0
- package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +6 -0
- package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +7 -0
- package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +5 -0
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +24 -0
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +2 -0
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +11 -0
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +1 -0
- package/nitrogen/generated/shared/json/CesiumViewConfig.json +1 -0
- package/package.json +1 -1
- package/scripts/cesium/clone.mjs +1 -1
- package/scripts/cesium/config.mjs +8 -0
- package/scripts/cesium/ensure-native.mjs +163 -17
- package/src/specs/cesium-view.nitro.ts +7 -0
package/README.md
CHANGED
|
@@ -397,6 +397,22 @@ Avoid assuming **main-thread-only** vs **JS-thread-only** labels beyond the abov
|
|
|
397
397
|
- 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`.
|
|
398
398
|
- Prefer **`setCameraQuaternion`** on frames where you need to drive both the globe camera and the correction together so the native target stays consistent.
|
|
399
399
|
|
|
400
|
+
#### Smoothing of incoming camera updates
|
|
401
|
+
|
|
402
|
+
Every value you pass to `setCamera` / `setCameraQuaternion` is treated as a **demand** target — what you would *like* the camera to be — rather than a per-frame command. The native render thread runs an exponential filter every frame that converges the displayed camera onto your demand target, which means a coarse 1 Hz position feed (e.g. external GPS) does **not** teleport the view once a second. Instead it eases between samples.
|
|
403
|
+
|
|
404
|
+
| DoF | Smoothing |
|
|
405
|
+
| --- | --- |
|
|
406
|
+
| `latitude` / `longitude` | **Adaptive** exponential filter. Time constant `τ = clamp(α · ewmaInterval, τmin, τmax)` where `ewmaInterval` tracks the cadence of your `setCamera` calls. ~60 Hz gestures collapse `τ` to the floor (≤1 frame of trail, indistinguishable from the previous "snap" behaviour); ~1 Hz GPS feeds expand `τ` so each sample is ~95 % converged before the next arrives. Cross-planet jumps (Δ > ~0.5°) bypass the smoother and snap, so a deliberate teleport stays instant. Antimeridian crossings always rotate the short way. |
|
|
407
|
+
| `altitude` | Exponential, fixed `τ ≈ 40 ms`. |
|
|
408
|
+
| `heading` / `pitch` / `roll` | Exponential on the shortest-arc angular delta, fixed `τ ≈ 20–33 ms`. |
|
|
409
|
+
| `viewCorrection` (quaternion) | SLERP-based exponential, fixed `τ ≈ 20 ms`, hemisphere-corrected so it always takes the short rotation. |
|
|
410
|
+
| `verticalFovDeg` | Applied directly (not smoothed). |
|
|
411
|
+
|
|
412
|
+
You don't have to do anything special to take advantage of this — the smoother already runs, it's the same code path whether your driver is a Reanimated worklet at 60 Hz, a `setInterval` push at 10 Hz, or a 1 Hz GPS subscription. Just call `setCamera` whenever you have a new value.
|
|
413
|
+
|
|
414
|
+
If the smoothing ever feels off for your data source, the relevant constants live in [`cpp/engine/EngineTunables.hpp`](cpp/engine/EngineTunables.hpp) (`kSmoothPositionAlpha`, `kSmoothPositionTauMin`, `kSmoothPositionTauMax`, `kPosSnapThroughDeg`, `kEwmaIntervalAlpha`, plus the per-DoF `kSmooth*` rates).
|
|
415
|
+
|
|
400
416
|
### `CesiumMetrics`
|
|
401
417
|
|
|
402
418
|
| Field | Type | Description |
|
|
@@ -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,7 +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
|
-
|
|
247
|
+
auto smooth = reactnativecesium::CameraSmoother::step(
|
|
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
|
+
|
|
243
264
|
engine_->camera().setParams(smooth);
|
|
244
265
|
|
|
245
266
|
// If we converged, clear the dirty flag so the next idle vsync can early-out.
|
|
@@ -539,5 +560,9 @@ JNIEXPORT void JNICALL
|
|
|
539
560
|
Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetTaskProcessorThreads(JNIEnv*, jobject, jlong ptr, jint v) {
|
|
540
561
|
getBridge(ptr)->setTaskProcessorThreads(v);
|
|
541
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
|
+
}
|
|
542
567
|
|
|
543
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
|
/**
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include "EngineTunables.hpp"
|
|
4
4
|
|
|
5
|
+
#include <algorithm>
|
|
5
6
|
#include <cmath>
|
|
6
7
|
|
|
7
8
|
namespace reactnativecesium {
|
|
@@ -23,15 +24,40 @@ inline double quatDotAbs(const glm::dquat& a, const glm::dquat& b) {
|
|
|
23
24
|
return std::abs(glm::dot(glm::normalize(a), glm::normalize(b)));
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
inline double wrapLonDeltaDeg(double a, double b) {
|
|
28
|
+
return std::fmod(b - a + 540.0, 360.0) - 180.0;
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
} // namespace
|
|
27
32
|
|
|
28
33
|
CameraParams CameraSmoother::step(
|
|
29
|
-
const CameraParams& current, const CameraParams& target, double dt
|
|
34
|
+
const CameraParams& current, const CameraParams& target, double dt,
|
|
35
|
+
double recentIntervalSec) {
|
|
30
36
|
CameraParams next = current;
|
|
31
37
|
|
|
32
|
-
// lat/lon:
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
// ── lat/lon: adaptive exponential smoothing ─────────────────────────────
|
|
39
|
+
// τ scales with the cadence of incoming setCamera calls so gestures feel
|
|
40
|
+
// instant (τ at the floor) while sparse GPS-rate updates ease across each
|
|
41
|
+
// interval (τ near the ceiling).
|
|
42
|
+
const double tau = std::clamp(
|
|
43
|
+
tunables::kSmoothPositionAlpha * recentIntervalSec,
|
|
44
|
+
tunables::kSmoothPositionTauMin,
|
|
45
|
+
tunables::kSmoothPositionTauMax);
|
|
46
|
+
const double aPos = 1.0 - std::exp(-dt / tau);
|
|
47
|
+
|
|
48
|
+
const double dLat = target.latitude - current.latitude;
|
|
49
|
+
const double dLon = wrapLonDeltaDeg(current.longitude, target.longitude);
|
|
50
|
+
const bool snapThrough =
|
|
51
|
+
std::abs(dLat) > tunables::kPosSnapThroughDeg ||
|
|
52
|
+
std::abs(dLon) > tunables::kPosSnapThroughDeg;
|
|
53
|
+
|
|
54
|
+
if (snapThrough) {
|
|
55
|
+
next.latitude = target.latitude;
|
|
56
|
+
next.longitude = target.longitude;
|
|
57
|
+
} else {
|
|
58
|
+
next.latitude = current.latitude + aPos * dLat;
|
|
59
|
+
next.longitude = lerpAngleDeg(current.longitude, target.longitude, aPos);
|
|
60
|
+
}
|
|
35
61
|
|
|
36
62
|
const double aAlt = 1.0 - std::exp(-tunables::kSmoothAltitude * dt);
|
|
37
63
|
const double aHdg = 1.0 - std::exp(-tunables::kSmoothHeading * dt);
|
|
@@ -50,6 +76,11 @@ CameraParams CameraSmoother::step(
|
|
|
50
76
|
next.viewCorrection = glm::normalize(glm::slerp(cq, tq, aViewQ));
|
|
51
77
|
|
|
52
78
|
// Eventually-exact convergence: snap once the residual is sub-epsilon.
|
|
79
|
+
if (std::abs(target.latitude - next.latitude) <= tunables::kEpsLatLon)
|
|
80
|
+
next.latitude = target.latitude;
|
|
81
|
+
if (std::abs(wrapLonDeltaDeg(next.longitude, target.longitude))
|
|
82
|
+
<= tunables::kEpsLatLon)
|
|
83
|
+
next.longitude = target.longitude;
|
|
53
84
|
if (std::abs(target.altitude - next.altitude) <= tunables::kEpsAltitudeMeters)
|
|
54
85
|
next.altitude = target.altitude;
|
|
55
86
|
if (angleDeltaAbsDeg(next.heading, target.heading) <= tunables::kEpsAngleDeg)
|
|
@@ -7,9 +7,13 @@ namespace reactnativecesium {
|
|
|
7
7
|
// Stateless helper. Given a current camera state and a target camera state,
|
|
8
8
|
// returns the next camera state for a frame of duration `dt` seconds.
|
|
9
9
|
//
|
|
10
|
-
// Per-DoF smoothing rules
|
|
11
|
-
// - lat / lon:
|
|
12
|
-
//
|
|
10
|
+
// Per-DoF smoothing rules:
|
|
11
|
+
// - lat / lon: adaptive exponential smoothing `1 - exp(-dt/τ)` where
|
|
12
|
+
// τ = clamp(α · recentIntervalSec, τmin, τmax). High update rates (60 Hz
|
|
13
|
+
// gestures) drive τ to the floor so the camera tracks the finger without
|
|
14
|
+
// perceptible lag; sparse updates (1 Hz GPS) widen τ so each sample
|
|
15
|
+
// interpolates smoothly across its interval. Snap-through guard for
|
|
16
|
+
// genuine teleports keeps cross-planet `setCamera` jumps instant.
|
|
13
17
|
// - altitude / heading / pitch / roll / viewCorrection: exponential decay
|
|
14
18
|
// `1 - exp(-k * dt)` per DoF with k taken from EngineTunables.
|
|
15
19
|
// - When the delta drops below the matching epsilon, the value is snapped
|
|
@@ -18,7 +22,8 @@ class CameraSmoother {
|
|
|
18
22
|
public:
|
|
19
23
|
static CameraParams step(const CameraParams& current,
|
|
20
24
|
const CameraParams& target,
|
|
21
|
-
double dt
|
|
25
|
+
double dt,
|
|
26
|
+
double recentIntervalSec);
|
|
22
27
|
};
|
|
23
28
|
|
|
24
29
|
} // namespace reactnativecesium
|
|
@@ -25,4 +25,25 @@ bool CameraTargetState::deltaExceedsEpsilon(
|
|
|
25
25
|
> tunables::kEpsQuatDot;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
void CameraTargetState::noteUpdateCadence() noexcept {
|
|
29
|
+
const auto now = std::chrono::steady_clock::now();
|
|
30
|
+
if (!hasLastUpdate_) {
|
|
31
|
+
// Seed the EWMA with τmax so the first sample after a quiet period uses
|
|
32
|
+
// the full smoothing budget rather than the floor (5 ms ≈ snap).
|
|
33
|
+
ewmaIntervalSec_.store(tunables::kSmoothPositionTauMax / tunables::kSmoothPositionAlpha,
|
|
34
|
+
std::memory_order_release);
|
|
35
|
+
lastUpdateTime_ = now;
|
|
36
|
+
hasLastUpdate_ = true;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const double dt = std::chrono::duration<double>(now - lastUpdateTime_).count();
|
|
41
|
+
lastUpdateTime_ = now;
|
|
42
|
+
if (dt <= 0.0) return; // back-to-back writes from the same frame: ignore.
|
|
43
|
+
|
|
44
|
+
const double prev = ewmaIntervalSec_.load(std::memory_order_relaxed);
|
|
45
|
+
const double next = prev + tunables::kEwmaIntervalAlpha * (dt - prev);
|
|
46
|
+
ewmaIntervalSec_.store(next, std::memory_order_release);
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
} // namespace reactnativecesium
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "GlobeCamera.hpp"
|
|
5
5
|
|
|
6
6
|
#include <atomic>
|
|
7
|
+
#include <chrono>
|
|
7
8
|
#include <mutex>
|
|
8
9
|
|
|
9
10
|
namespace reactnativecesium {
|
|
@@ -31,6 +32,7 @@ public:
|
|
|
31
32
|
std::lock_guard<std::mutex> g(mutex_);
|
|
32
33
|
target_ = target;
|
|
33
34
|
}
|
|
35
|
+
noteUpdateCadence();
|
|
34
36
|
dirty_.store(true, std::memory_order_release);
|
|
35
37
|
forceRender_.store(true, std::memory_order_release);
|
|
36
38
|
}
|
|
@@ -47,6 +49,7 @@ public:
|
|
|
47
49
|
target_.pitch = pitch;
|
|
48
50
|
target_.roll = roll;
|
|
49
51
|
}
|
|
52
|
+
noteUpdateCadence();
|
|
50
53
|
dirty_.store(true, std::memory_order_release);
|
|
51
54
|
forceRender_.store(true, std::memory_order_release);
|
|
52
55
|
}
|
|
@@ -56,6 +59,7 @@ public:
|
|
|
56
59
|
std::lock_guard<std::mutex> g(mutex_);
|
|
57
60
|
target_.viewCorrection = q;
|
|
58
61
|
}
|
|
62
|
+
noteUpdateCadence();
|
|
59
63
|
dirty_.store(true, std::memory_order_release);
|
|
60
64
|
forceRender_.store(true, std::memory_order_release);
|
|
61
65
|
}
|
|
@@ -87,11 +91,31 @@ public:
|
|
|
87
91
|
// whether to render this frame.
|
|
88
92
|
static bool deltaExceedsEpsilon(const CameraParams& a, const CameraParams& b);
|
|
89
93
|
|
|
94
|
+
// EWMA of the inter-arrival interval (seconds) of demand updates. Used by
|
|
95
|
+
// CameraSmoother to scale the lat/lon time constant: high update rate ⇒
|
|
96
|
+
// tiny τ (snappy gestures); low update rate ⇒ larger τ (smooth GPS catch-up).
|
|
97
|
+
// Lock-free read on the render-thread hot path.
|
|
98
|
+
double recentIntervalSec() const noexcept {
|
|
99
|
+
return ewmaIntervalSec_.load(std::memory_order_acquire);
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
private:
|
|
103
|
+
// Folds the time since the previous demand update into ewmaIntervalSec_.
|
|
104
|
+
// Called under (or after) the target_ mutex; the EWMA itself is atomic so
|
|
105
|
+
// the read side never blocks. Writes are uncontended (only JS-thread).
|
|
106
|
+
void noteUpdateCadence() noexcept;
|
|
107
|
+
|
|
91
108
|
mutable std::mutex mutex_;
|
|
92
109
|
CameraParams target_;
|
|
93
110
|
std::atomic<bool> dirty_{false};
|
|
94
111
|
std::atomic<bool> forceRender_{true};
|
|
112
|
+
|
|
113
|
+
// Cadence tracker. lastUpdateTime_ is only touched on the writer (JS)
|
|
114
|
+
// thread, so it does not need synchronisation; ewmaIntervalSec_ is read
|
|
115
|
+
// by the render thread without taking the mutex.
|
|
116
|
+
std::chrono::steady_clock::time_point lastUpdateTime_{};
|
|
117
|
+
bool hasLastUpdate_{false};
|
|
118
|
+
std::atomic<double> ewmaIntervalSec_{0.0};
|
|
95
119
|
};
|
|
96
120
|
|
|
97
121
|
} // namespace reactnativecesium
|
|
@@ -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_;
|
|
@@ -53,6 +53,21 @@ inline constexpr double kEpsAltitudeMeters = 0.1;
|
|
|
53
53
|
inline constexpr double kEpsAngleDeg = 0.05;
|
|
54
54
|
inline constexpr double kEpsQuatDot = 1e-8;
|
|
55
55
|
|
|
56
|
+
// Lat/lon are smoothed adaptively: τ = clamp(α · ewmaInterval, τmin, τmax),
|
|
57
|
+
// then per frame we apply 1 - exp(-dt/τ). α<1 means the camera converges
|
|
58
|
+
// before the next sample lands. With ~60 Hz gestures the EWMA interval is
|
|
59
|
+
// ~16 ms ⇒ τ at the floor (5 ms) ⇒ ≤1-frame trail (indistinguishable from
|
|
60
|
+
// the previous snap). With a 1 Hz GPS feed τ ≈ 450 ms ⇒ ~95 % converged
|
|
61
|
+
// before the next sample arrives, removing the per-second teleport.
|
|
62
|
+
inline constexpr double kSmoothPositionAlpha = 0.45;
|
|
63
|
+
inline constexpr double kSmoothPositionTauMin = 0.005;
|
|
64
|
+
inline constexpr double kSmoothPositionTauMax = 0.6;
|
|
65
|
+
// Genuine teleports (e.g. setCamera(NewYork) → setCamera(Tokyo)) skip the
|
|
66
|
+
// smoother — easing across the planet over 600 ms looks worse than a snap.
|
|
67
|
+
inline constexpr double kPosSnapThroughDeg = 0.5;
|
|
68
|
+
// EWMA mixing factor for the inter-arrival interval of setCamera calls.
|
|
69
|
+
inline constexpr double kEwmaIntervalAlpha = 0.3;
|
|
70
|
+
|
|
56
71
|
// Metrics throttle: emit telemetry every N rendered frames.
|
|
57
72
|
inline constexpr int kMetricsEmitEveryFrames = 20;
|
|
58
73
|
|
package/ios/CesiumBridge.h
CHANGED
|
@@ -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;
|
package/ios/CesiumBridge.mm
CHANGED
|
@@ -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,7 +277,25 @@
|
|
|
271
277
|
|
|
272
278
|
const auto cur = _engine->camera().getParams();
|
|
273
279
|
const auto tgt = _target.snapshot();
|
|
274
|
-
|
|
280
|
+
auto smooth = reactnativecesium::CameraSmoother::step(
|
|
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
|
+
|
|
275
299
|
_engine->camera().setParams(smooth);
|
|
276
300
|
|
|
277
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;
|
|
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
|
|
package/package.json
CHANGED
package/scripts/cesium/clone.mjs
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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 =
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
|
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
|
-
|
|
102
|
-
|
|
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(
|
|
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
|
}
|