react-native-cesium 1.2.18 → 1.2.20
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 +11 -8
- package/android/CMakeLists.txt +1 -0
- package/android/build.gradle +1 -1
- package/android/gradle.properties +4 -4
- package/android/src/main/cpp/CesiumBridgeAndroid.cpp +44 -18
- package/android/src/main/cpp/CesiumBridgeAndroid.h +9 -0
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +3 -0
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +9 -0
- package/cpp/engine/CameraIntegrator.cpp +82 -47
- package/cpp/engine/CameraIntegrator.hpp +27 -8
- package/cpp/engine/CesiumEngine.cpp +183 -312
- package/cpp/engine/CesiumEngine.hpp +41 -28
- package/cpp/engine/EngineTunables.hpp +6 -5
- package/cpp/engine/GeoidConverter.cpp +32 -11
- package/cpp/engine/GeoidConverter.hpp +41 -0
- package/cpp/engine/GltfToMesh.cpp +14 -5
- package/cpp/engine/ResourcePreparer.cpp +3 -3
- package/cpp/engine/ResourcePreparer.hpp +1 -1
- package/cpp/engine/TerrainFloor.hpp +70 -0
- package/cpp/engine/TerrainHeight.hpp +113 -0
- package/cpp/engine/test/CameraIntegratorTest.cpp +264 -0
- package/cpp/engine/test/GeoidConverterTest.cpp +77 -0
- package/cpp/engine/test/build_and_run.sh +20 -6
- package/ios/CesiumBridge.h +5 -0
- package/ios/CesiumBridge.mm +61 -32
- package/ios/HybridCesiumView.swift +11 -0
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts +34 -9
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JCameraState.hpp +1 -1
- package/nitrogen/generated/android/c++/JCesiumMetrics.hpp +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++/JQuaternion.hpp +1 -1
- package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +182 -131
- package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.hpp +6 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CameraState.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CesiumMetrics.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Func_void_CameraState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Func_void_CesiumMetrics.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +8 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Quaternion.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewManager.kt +26 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewStateUpdater.kt +2 -2
- package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +15 -0
- package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +7 -0
- package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +217 -161
- 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 +36 -335
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +110 -77
- package/nitrogen/generated/shared/json/CesiumViewConfig.json +1 -0
- package/package.json +12 -12
- package/scripts/cesium/config.mjs +1 -1
- package/scripts/cesium/vcpkg-triplets/arm64-ios-simulator.cmake +3 -0
- package/scripts/cesium/vcpkg-triplets/arm64-ios.cmake +4 -0
- package/src/specs/cesium-view.nitro.ts +34 -9
package/README.md
CHANGED
|
@@ -184,7 +184,7 @@ npm run build --prefix node_modules/react-native-cesium
|
|
|
184
184
|
|
|
185
185
|
**Yarn/npm workspaces:** if `react-native-cesium` is a workspace package in your monorepo, use your tool’s workspace runner, e.g. `yarn workspace react-native-cesium run update` (exact syntax depends on your workspace layout).
|
|
186
186
|
|
|
187
|
-
- `update` checks out **Cesium Native `v0.
|
|
187
|
+
- `update` checks out **Cesium Native `v0.65.0`** into `vendor/cesium-native` under the package (next to its other files; typically ignored in app repos).
|
|
188
188
|
- `build` runs `scripts/cesium/build.mjs` (**CMake**, **vcpkg** under `vendor/vcpkg` unless **`VCPKG_ROOT`** is set, **Ninja** on macOS) and writes **`vendor/ios/CesiumNative.xcframework`** and/or **`vendor/android`** depending on **`CESIUM_BUILD_ONLY`**.
|
|
189
189
|
|
|
190
190
|
Then install pods (iOS):
|
|
@@ -294,6 +294,7 @@ When you pass a callback ref to `CesiumView`, wrap it with `callback(...)` from
|
|
|
294
294
|
|
|
295
295
|
| Prop | Type | Default | Description |
|
|
296
296
|
| --- | --- | --- | --- |
|
|
297
|
+
| `altitudeDatum` | `"msl"` \| `"ellipsoid"` | `"msl"` | Vertical datum of every public altitude: `initialCamera`, `setAltitude`, `teleport`, and the altitude returned by `getActualCamera`, `getDemandCamera`, and `onActualCamera`. `"msl"` is metres above mean sea level. `"ellipsoid"` is WGS84 ellipsoidal height (Android `Location.getAltitude()`). Ellipsoidal heights are converted to mean sea level with EGM96 before the camera integrator, and converted back on read. Switching the prop does not move the camera: later reads use the new unit, and the next altitude or position write is interpreted in that unit. Leave this at `"msl"` for gesture and scripted altitudes. Use `"ellipsoid"` for a GPS ellipsoidal height, and call `setPosition` and `setAltitude` one after the other on the same thread. |
|
|
297
298
|
| `onMetrics` | `(metrics: CesiumMetrics) => void` | `undefined` | Receives throttled runtime stats and credits text. |
|
|
298
299
|
|
|
299
300
|
**Optional performance / quality knobs**
|
|
@@ -348,7 +349,7 @@ type CameraState = {
|
|
|
348
349
|
| --- | --- |------------------------------|-------------------------------------------------------------------------------------------------------|
|
|
349
350
|
| `latitude` | `number` | `-90..90` | Camera latitude in degrees. |
|
|
350
351
|
| `longitude` | `number` | `-180..180` | Camera longitude in degrees. |
|
|
351
|
-
| `altitude` | `number` |
|
|
352
|
+
| `altitude` | `number` | Metres | Camera height. Mean sea level unless `altitudeDatum` is `"ellipsoid"`, in which case this is WGS84 ellipsoidal height. Writes and reads use the same unit. |
|
|
352
353
|
| `heading` | `number` | Degrees | Compass direction the camera faces. Example: `0` faces north, `90` faces east. |
|
|
353
354
|
| `pitch` | `number` | Degrees | Tilt angle. Negative values look downward toward terrain; positive values tilt up toward horizon/sky. |
|
|
354
355
|
| `roll` | `number` | Degrees | Bank/rotation around forward axis. Positive right bank, negative left bank. |
|
|
@@ -385,15 +386,15 @@ Each axis (position, altitude, heading, attitude, viewCorrection, verticalFov) h
|
|
|
385
386
|
|
|
386
387
|
| Method | Signature | Description |
|
|
387
388
|
| --- | --- | --- |
|
|
388
|
-
| `setPosition` | `(latitude: number, longitude: number) => void` | New geographic position **demand** (degrees). Each call stamps the measurement with the native steady-clock time of arrival and runs one α-β update against the predicted state at that instant. Latitude is clamped to ±90 on extrapolation; longitude is continuous (no wrap normalisation) so shortest-arc residual maths stays correct across the antimeridian. |
|
|
389
|
-
| `setAltitude` | `(altitudeMeters: number) => void` | New altitude demand
|
|
389
|
+
| `setPosition` | `(latitude: number, longitude: number) => void` | New geographic position **demand** (degrees). Each call stamps the measurement with the native steady-clock time of arrival and runs one α-β update against the predicted state at that instant. Latitude is clamped to ±90 on extrapolation; longitude is continuous (no wrap normalisation) so shortest-arc residual maths stays correct across the antimeridian. When `altitudeDatum` is `"ellipsoid"`, a horizontal move keeps the demanded ellipsoidal height and recomputes mean sea level at the new point. `"msl"` leaves altitude unchanged. |
|
|
390
|
+
| `setAltitude` | `(altitudeMeters: number) => void` | New altitude demand, in metres. Mean sea level unless `altitudeDatum` is `"ellipsoid"`, in which case it is WGS84 height and is converted to mean sea level before the integrator. A later read reports the same sample back in this unit. |
|
|
390
391
|
| `setHeading` | `(headingDeg: number) => void` | New heading demand (degrees; `0` = north, increasing clockwise). The integrator computes the shortest-arc residual, so feeding it `359 → 1` rotates 2° clockwise, not 358° counter-clockwise. |
|
|
391
392
|
| `setAttitude` | `(pitchDeg: number, rollDeg: number) => void` | New pitch and roll demand (degrees). Bundled because almost every IMU emits them together — but you can pass the previous value for either axis if only one of them has changed. |
|
|
392
393
|
| `setViewCorrection` | `(q: Quaternion) => void` | New camera-space rotation applied **after** HPR. SLERPed toward the latest demand each frame. Use for boresight calibration, screen-fixed HUD alignment, etc. Non-unit values are normalised on the native side. |
|
|
393
394
|
| `setVerticalFov` | `(deg: number) => void` | New vertical FOV demand (degrees). Clamped to `20..100` on the native side. |
|
|
394
|
-
| `teleport` | `(camera: CameraState) => void` | Hard scene jump — atomically resets every DoF (value, velocity, demand) to `camera`. Bypasses the integrator entirely. Use for `Fly to coordinate` actions or seeding from saved state. |
|
|
395
|
-
| `getActualCamera` | `() => Promise<CameraState>` | Most recently rendered camera (post-integration). This is what the user is currently looking at and what a HUD overlay should mirror. Differs from `getDemandCamera()` while a glide is in progress or when the terrain-floor clamp has raised the altitude. |
|
|
396
|
-
| `getDemandCamera` | `() => Promise<CameraState>` | What the consumer last asked for (per-DoF demand). Useful for diagnostics, or for showing the requested camera alongside the rendered one. |
|
|
395
|
+
| `teleport` | `(camera: CameraState) => void` | Hard scene jump — atomically resets every DoF (value, velocity, demand) to `camera`. Bypasses the integrator entirely. Use for `Fly to coordinate` actions or seeding from saved state. `camera.altitude` uses the unit selected by `altitudeDatum`. |
|
|
396
|
+
| `getActualCamera` | `() => Promise<CameraState>` | Most recently rendered camera (post-integration). This is what the user is currently looking at and what a HUD overlay should mirror. Differs from `getDemandCamera()` while a glide is in progress or when the terrain-floor clamp has raised the altitude. Altitude is in the unit selected by `altitudeDatum`. |
|
|
397
|
+
| `getDemandCamera` | `() => Promise<CameraState>` | What the consumer last asked for (per-DoF demand). Useful for diagnostics, or for showing the requested camera alongside the rendered one. Altitude is in the unit selected by `altitudeDatum`. |
|
|
397
398
|
| `getViewCorrection` | `() => Promise<Quaternion>` | Current view-correction quaternion (smoothed toward the latest demand). Identity (`w=1, x=y=z=0`) if you have never called `setViewCorrection`. |
|
|
398
399
|
|
|
399
400
|
#### Threading: setters vs getters
|
|
@@ -415,7 +416,7 @@ Avoid assuming **main-thread-only** vs **JS-thread-only** labels beyond the abov
|
|
|
415
416
|
|
|
416
417
|
The integrator tracks two camera states simultaneously:
|
|
417
418
|
|
|
418
|
-
- **Demand** — what the consumer asked for
|
|
419
|
+
- **Demand** — what the consumer asked for. `getDemandCamera()` returns this, with altitude in the unit selected by `altitudeDatum`.
|
|
419
420
|
- **Actual** — what the integrator extrapolated to and what was passed to the GPU on the last frame. `getActualCamera()` returns this.
|
|
420
421
|
|
|
421
422
|
For a steady GPS feed with no terrain clamp the two values converge within a few frames. They diverge when:
|
|
@@ -461,6 +462,8 @@ imuSubscription.on('attitude', (att) => {
|
|
|
461
462
|
})
|
|
462
463
|
```
|
|
463
464
|
|
|
465
|
+
`fix.altitudeMsl` matches the default `altitudeDatum="msl"`. Android `Location.getAltitude()` is a WGS84 ellipsoidal height: set `altitudeDatum="ellipsoid"` and pass that value to `setAltitude` instead. Call `setPosition` and `setAltitude` one after the other on the same thread so the geoid is applied at that fix.
|
|
466
|
+
|
|
464
467
|
Each setter is synchronous, takes the integrator's mutex briefly, and is safe to call at any rate from any thread.
|
|
465
468
|
|
|
466
469
|
### `CesiumMetrics`
|
package/android/CMakeLists.txt
CHANGED
package/android/build.gradle
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
ReactNativeCesium_kotlinVersion=2.
|
|
2
|
-
ReactNativeCesium_minSdkVersion=
|
|
3
|
-
ReactNativeCesium_targetSdkVersion=
|
|
4
|
-
ReactNativeCesium_compileSdkVersion=
|
|
1
|
+
ReactNativeCesium_kotlinVersion=2.2.0
|
|
2
|
+
ReactNativeCesium_minSdkVersion=24
|
|
3
|
+
ReactNativeCesium_targetSdkVersion=36
|
|
4
|
+
ReactNativeCesium_compileSdkVersion=37
|
|
5
5
|
ReactNativeCesium_ndkVersion=27.1.12297006
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include "engine/EngineTunables.hpp"
|
|
4
4
|
#include "engine/GeoidConverter.hpp"
|
|
5
|
+
#include "engine/TerrainFloor.hpp"
|
|
5
6
|
#include "vulkan/VulkanBackend.h"
|
|
6
7
|
|
|
7
8
|
#include <android/native_window_jni.h>
|
|
@@ -149,13 +150,34 @@ void CesiumBridgeAndroid::updateImageryAssetId(int64_t assetId) {
|
|
|
149
150
|
|
|
150
151
|
void CesiumBridgeAndroid::setPosition(double lat, double lon) {
|
|
151
152
|
if (!initialized_ || !integrator_) return;
|
|
153
|
+
const bool ellipsoid =
|
|
154
|
+
altitudeIsEllipsoid_.load(std::memory_order_acquire);
|
|
155
|
+
double adjustedMsl = 0.0;
|
|
156
|
+
bool adjustAltitude = false;
|
|
157
|
+
if (ellipsoid) {
|
|
158
|
+
const auto demand = integrator_->getDemand();
|
|
159
|
+
adjustedMsl = reactnativecesium::mslAfterHorizontalMove(
|
|
160
|
+
demand.latitude, demand.longitude, demand.altitude, lat, lon);
|
|
161
|
+
adjustAltitude = std::abs(adjustedMsl - demand.altitude) >
|
|
162
|
+
reactnativecesium::kHorizontalMoveMslEpsilonMeters;
|
|
163
|
+
}
|
|
152
164
|
integrator_->setPosition(lat, lon);
|
|
165
|
+
if (adjustAltitude) {
|
|
166
|
+
integrator_->setAltitude(adjustedMsl);
|
|
167
|
+
}
|
|
153
168
|
forceRender_.store(true, std::memory_order_release);
|
|
154
169
|
}
|
|
155
170
|
|
|
171
|
+
void CesiumBridgeAndroid::setAltitudeDatumEllipsoid(bool ellipsoid) {
|
|
172
|
+
altitudeIsEllipsoid_.store(ellipsoid, std::memory_order_release);
|
|
173
|
+
}
|
|
174
|
+
|
|
156
175
|
void CesiumBridgeAndroid::setAltitude(double alt) {
|
|
157
176
|
if (!initialized_ || !integrator_) return;
|
|
158
|
-
integrator_->
|
|
177
|
+
const auto demand = integrator_->getDemand();
|
|
178
|
+
integrator_->setAltitude(reactnativecesium::demandAltitudeToMslMeters(
|
|
179
|
+
demand.latitude, demand.longitude, alt,
|
|
180
|
+
altitudeIsEllipsoid_.load(std::memory_order_acquire)));
|
|
159
181
|
forceRender_.store(true, std::memory_order_release);
|
|
160
182
|
}
|
|
161
183
|
|
|
@@ -184,13 +206,15 @@ void CesiumBridgeAndroid::teleport(double lat, double lon, double alt,
|
|
|
184
206
|
reactnativecesium::CameraParams p;
|
|
185
207
|
p.latitude = lat;
|
|
186
208
|
p.longitude = lon;
|
|
187
|
-
p.altitude =
|
|
209
|
+
p.altitude = reactnativecesium::demandAltitudeToMslMeters(
|
|
210
|
+
lat, lon, alt, altitudeIsEllipsoid_.load(std::memory_order_acquire));
|
|
188
211
|
p.heading = heading;
|
|
189
212
|
p.pitch = pitch;
|
|
190
213
|
p.roll = roll;
|
|
191
214
|
p.verticalFov = vfov;
|
|
192
215
|
p.viewCorrection = integrator_->getActual().viewCorrection;
|
|
193
216
|
integrator_->teleport(p);
|
|
217
|
+
reactnativecesium::noteTerrainSlew(*integrator_, terrainLead_);
|
|
194
218
|
engine_->camera().setParams(p);
|
|
195
219
|
forceRender_.store(true, std::memory_order_release);
|
|
196
220
|
}
|
|
@@ -309,20 +333,9 @@ void CesiumBridgeAndroid::renderFrame(double nowSeconds) {
|
|
|
309
333
|
// The integrator owns its own clock — see CameraIntegrator::step().
|
|
310
334
|
// `nowSeconds` is still used below for the metrics-only dt EMA.
|
|
311
335
|
auto actual = integrator_->step();
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (minAbove > 0.0f && engine_->terrainFloorKnown()) {
|
|
316
|
-
const double geoidN =
|
|
317
|
-
reactnativecesium::mslToEllipsoidMeters(actual.latitude, actual.longitude, 0.0);
|
|
318
|
-
const double floorMsl =
|
|
319
|
-
static_cast<double>(engine_->terrainFloorEllipsoidMeters()) - geoidN;
|
|
320
|
-
const double minMsl = floorMsl + static_cast<double>(minAbove);
|
|
321
|
-
if (actual.altitude < minMsl) {
|
|
322
|
-
integrator_->clampActualAltitude(minMsl);
|
|
323
|
-
actual.altitude = minMsl;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
336
|
+
actual = reactnativecesium::applyTerrainFloor(
|
|
337
|
+
*engine_, *integrator_, actual, terrainLead_,
|
|
338
|
+
static_cast<double>(engine_->getConfig().minAltitudeAboveTerrain));
|
|
326
339
|
|
|
327
340
|
engine_->camera().setParams(actual);
|
|
328
341
|
|
|
@@ -348,7 +361,11 @@ double CesiumBridgeAndroid::readActualLongitude() {
|
|
|
348
361
|
return integrator_ ? integrator_->getActual().longitude : 0.0;
|
|
349
362
|
}
|
|
350
363
|
double CesiumBridgeAndroid::readActualAltitude() {
|
|
351
|
-
|
|
364
|
+
if (!integrator_) return 0.0;
|
|
365
|
+
const auto actual = integrator_->getActual();
|
|
366
|
+
return reactnativecesium::mslToDemandAltitudeMeters(
|
|
367
|
+
actual.latitude, actual.longitude, actual.altitude,
|
|
368
|
+
altitudeIsEllipsoid_.load(std::memory_order_acquire));
|
|
352
369
|
}
|
|
353
370
|
double CesiumBridgeAndroid::readActualHeading() {
|
|
354
371
|
return integrator_ ? integrator_->getActual().heading : 0.0;
|
|
@@ -372,7 +389,11 @@ double CesiumBridgeAndroid::readDemandLongitude() {
|
|
|
372
389
|
return integrator_ ? integrator_->getDemand().longitude : 0.0;
|
|
373
390
|
}
|
|
374
391
|
double CesiumBridgeAndroid::readDemandAltitude() {
|
|
375
|
-
|
|
392
|
+
if (!integrator_) return 0.0;
|
|
393
|
+
const auto demand = integrator_->getDemand();
|
|
394
|
+
return reactnativecesium::mslToDemandAltitudeMeters(
|
|
395
|
+
demand.latitude, demand.longitude, demand.altitude,
|
|
396
|
+
altitudeIsEllipsoid_.load(std::memory_order_acquire));
|
|
376
397
|
}
|
|
377
398
|
double CesiumBridgeAndroid::readDemandHeading() {
|
|
378
399
|
return integrator_ ? integrator_->getDemand().heading : 0.0;
|
|
@@ -463,6 +484,11 @@ Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetPosition(
|
|
|
463
484
|
getBridge(ptr)->setPosition(lat, lon);
|
|
464
485
|
}
|
|
465
486
|
JNIEXPORT void JNICALL
|
|
487
|
+
Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetAltitudeDatumEllipsoid(
|
|
488
|
+
JNIEnv*, jobject, jlong ptr, jboolean ellipsoid) {
|
|
489
|
+
getBridge(ptr)->setAltitudeDatumEllipsoid(ellipsoid == JNI_TRUE);
|
|
490
|
+
}
|
|
491
|
+
JNIEXPORT void JNICALL
|
|
466
492
|
Java_com_margelo_nitro_reactnativecesium_CesiumBridgeJNI_nativeSetAltitude(
|
|
467
493
|
JNIEnv*, jobject, jlong ptr, jdouble alt) {
|
|
468
494
|
getBridge(ptr)->setAltitude(alt);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#include "engine/CameraIntegrator.hpp"
|
|
9
9
|
#include "engine/CesiumEngine.hpp"
|
|
10
10
|
#include "engine/MetricsAggregator.hpp"
|
|
11
|
+
#include "engine/TerrainFloor.hpp"
|
|
11
12
|
|
|
12
13
|
struct ANativeWindow;
|
|
13
14
|
|
|
@@ -47,6 +48,10 @@ struct CesiumBridgeAndroid {
|
|
|
47
48
|
|
|
48
49
|
// ── Per-DoF camera demand setters ──────────────────────────────────────
|
|
49
50
|
void setPosition(double lat, double lon);
|
|
51
|
+
// When true, altitudes passed to setAltitude/teleport are WGS84 ellipsoidal
|
|
52
|
+
// heights. They are converted to MSL before the integrator, and altitude
|
|
53
|
+
// readback is converted back. A horizontal move keeps that ellipsoidal height.
|
|
54
|
+
void setAltitudeDatumEllipsoid(bool ellipsoid);
|
|
50
55
|
void setAltitude(double alt);
|
|
51
56
|
void setHeading(double headingDeg);
|
|
52
57
|
void setAttitude(double pitchDeg, double rollDeg);
|
|
@@ -137,6 +142,9 @@ private:
|
|
|
137
142
|
int viewportWidth_ = 0;
|
|
138
143
|
int viewportHeight_ = 0;
|
|
139
144
|
bool initialized_ = false;
|
|
145
|
+
// Written from the render thread when the prop changes; read from the
|
|
146
|
+
// caller thread inside setAltitude / setPosition / teleport / readback.
|
|
147
|
+
std::atomic<bool> altitudeIsEllipsoid_{false};
|
|
140
148
|
|
|
141
149
|
// Force-render flag — set on every demand change so the render loop knows
|
|
142
150
|
// to dispatch at least one frame.
|
|
@@ -144,5 +152,6 @@ private:
|
|
|
144
152
|
|
|
145
153
|
reactnativecesium::EngineConfig config_;
|
|
146
154
|
reactnativecesium::MetricsAggregator metrics_;
|
|
155
|
+
reactnativecesium::TerrainLeadState terrainLead_{};
|
|
147
156
|
double lastTickSeconds_ = 0.0;
|
|
148
157
|
};
|
|
@@ -27,6 +27,8 @@ class CesiumBridgeJNI {
|
|
|
27
27
|
|
|
28
28
|
// ── Per-DoF camera setters ───────────────────────────────────────────────
|
|
29
29
|
fun setPosition(lat: Double, lon: Double) = nativeSetPosition(nativePtr, lat, lon)
|
|
30
|
+
fun setAltitudeDatumEllipsoid(ellipsoid: Boolean) =
|
|
31
|
+
nativeSetAltitudeDatumEllipsoid(nativePtr, ellipsoid)
|
|
30
32
|
fun setAltitude(alt: Double) = nativeSetAltitude(nativePtr, alt)
|
|
31
33
|
fun setHeading(headingDeg: Double) = nativeSetHeading(nativePtr, headingDeg)
|
|
32
34
|
fun setAttitude(pitchDeg: Double, rollDeg: Double) = nativeSetAttitude(nativePtr, pitchDeg, rollDeg)
|
|
@@ -107,6 +109,7 @@ class CesiumBridgeJNI {
|
|
|
107
109
|
private external fun nativeUpdateImageryAssetId(ptr: Long, assetId: Long)
|
|
108
110
|
|
|
109
111
|
private external fun nativeSetPosition(ptr: Long, lat: Double, lon: Double)
|
|
112
|
+
private external fun nativeSetAltitudeDatumEllipsoid(ptr: Long, ellipsoid: Boolean)
|
|
110
113
|
private external fun nativeSetAltitude(ptr: Long, alt: Double)
|
|
111
114
|
private external fun nativeSetHeading(ptr: Long, headingDeg: Double)
|
|
112
115
|
private external fun nativeSetAttitude(ptr: Long, pitchDeg: Double, rollDeg: Double)
|
|
@@ -105,6 +105,14 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
105
105
|
override var ionImageryAssetId: Double = 1.0
|
|
106
106
|
set(value) { field = value; postToRender { bridge?.updateImageryAssetId(value.toLong()) } }
|
|
107
107
|
|
|
108
|
+
/** `"msl"` (default) or `"ellipsoid"`. Ellipsoid heights are converted to MSL. */
|
|
109
|
+
override var altitudeDatum: String? = "msl"
|
|
110
|
+
set(value) {
|
|
111
|
+
field = value
|
|
112
|
+
val ellipsoid = value == "ellipsoid"
|
|
113
|
+
postToRender { bridge?.setAltitudeDatumEllipsoid(ellipsoid) }
|
|
114
|
+
}
|
|
115
|
+
|
|
108
116
|
// ── Optional perf / quality knobs ────────────────────────────────────────
|
|
109
117
|
override var maximumCachedMiB: Double? = null
|
|
110
118
|
set(value) { field = value; if (value != null) postToRender { bridge?.setMaximumCachedMiB(value.toInt()) } }
|
|
@@ -519,6 +527,7 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
519
527
|
// sqliteCacheMaxRows and taskProcessorThreads are applied before init() in
|
|
520
528
|
// surfaceCreated; calling them here (after buildEngine) has no effect.
|
|
521
529
|
minAltitudeAboveTerrain?.let { b.setMinAltitudeAboveTerrain(it.toFloat()) }
|
|
530
|
+
b.setAltitudeDatumEllipsoid(altitudeDatum == "ellipsoid")
|
|
522
531
|
}
|
|
523
532
|
|
|
524
533
|
/**
|
|
@@ -110,7 +110,7 @@ void CameraIntegrator::updateAlphaBeta(
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
void CameraIntegrator::extrapolate(
|
|
113
|
-
AlphaBeta& s, TimePoint now, double maxRateAbs, double demand) {
|
|
113
|
+
AlphaBeta& s, TimePoint now, double maxRateAbs, double demand, bool wrapAngle) {
|
|
114
114
|
double dt = std::chrono::duration<double>(now - s.tState).count();
|
|
115
115
|
if (dt <= 0.0) {
|
|
116
116
|
s.tState = now;
|
|
@@ -166,8 +166,9 @@ void CameraIntegrator::extrapolate(
|
|
|
166
166
|
// sensor streams always deliver a new update before the grace window
|
|
167
167
|
// expires, so this term is never active for them.
|
|
168
168
|
if (measurementsStopped && tunables::kDemandPullTauSec > 1.0e-6) {
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
const double delta = wrapAngle ? shortAngleDeltaDeg(s.value, demand)
|
|
170
|
+
: (demand - s.value);
|
|
171
|
+
s.value += (1.0 - std::exp(-dt / tunables::kDemandPullTauSec)) * delta;
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
@@ -203,21 +204,11 @@ void CameraIntegrator::setAltitude(double altitudeMeters) {
|
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
altitudeMeters = std::min(altitudeMeters, kMaxAltMeters);
|
|
206
|
-
|
|
207
|
-
// Adaptive alpha based on altitude error magnitude:
|
|
208
|
-
// - Large changes (pinch gesture, cliffs) → fast (α ≈ 0.7)
|
|
209
|
-
// - Small changes (gentle terrain) → smooth (α ≈ 0.3)
|
|
210
|
-
const double error = std::abs(altitudeMeters - alt_.value);
|
|
211
|
-
constexpr double kMinAlpha = 0.3; // Smooth for small changes
|
|
212
|
-
constexpr double kMaxAlpha = 0.7; // Fast for large changes
|
|
213
|
-
constexpr double kErrorScale = 20.0; // 20m error → max alpha
|
|
214
|
-
|
|
215
|
-
const double adaptiveAlpha = kMinAlpha + (kMaxAlpha - kMinAlpha) *
|
|
216
|
-
std::min(error / kErrorScale, 1.0);
|
|
217
|
-
|
|
207
|
+
|
|
218
208
|
updateAlphaBeta(alt_, altitudeMeters, now,
|
|
219
|
-
|
|
209
|
+
tunables::kAlphaAlt, tunables::kBetaAlt, false);
|
|
220
210
|
demand_.altitude = altitudeMeters;
|
|
211
|
+
enforceTerrainFloor();
|
|
221
212
|
}
|
|
222
213
|
|
|
223
214
|
void CameraIntegrator::setHeading(double headingDeg) {
|
|
@@ -261,13 +252,8 @@ void CameraIntegrator::setViewCorrection(const glm::dquat& q) {
|
|
|
261
252
|
}
|
|
262
253
|
|
|
263
254
|
void CameraIntegrator::setVerticalFov(double degrees) {
|
|
264
|
-
const auto now = Clock::now();
|
|
265
255
|
std::lock_guard<std::mutex> g(mutex_);
|
|
266
|
-
|
|
267
|
-
const double residual = degrees - vfov_.value;
|
|
268
|
-
vfov_.value += tunables::kAlphaVfov * residual;
|
|
269
|
-
vfov_.velocity = 0.0;
|
|
270
|
-
vfov_.tState = now;
|
|
256
|
+
demand_.verticalFov = degrees;
|
|
271
257
|
}
|
|
272
258
|
|
|
273
259
|
void CameraIntegrator::teleport(const CameraParams& target) {
|
|
@@ -305,6 +291,12 @@ void CameraIntegrator::teleport(const CameraParams& target) {
|
|
|
305
291
|
if (demand_.verticalFov <= 0.0) {
|
|
306
292
|
demand_.verticalFov = vfov_.value;
|
|
307
293
|
}
|
|
294
|
+
|
|
295
|
+
// A slew lands on the requested altitude. The next rendered frame samples
|
|
296
|
+
// the terrain at the new position and establishes a new floor.
|
|
297
|
+
terrainFloorActive_ = false;
|
|
298
|
+
terrainFloorMsl_ = 0.0;
|
|
299
|
+
lastStep_ = now;
|
|
308
300
|
}
|
|
309
301
|
|
|
310
302
|
CameraParams CameraIntegrator::step() {
|
|
@@ -328,13 +320,27 @@ CameraParams CameraIntegrator::step() {
|
|
|
328
320
|
latLonCapDegSec = glm::degrees(groundCap / kEarthMeanRadiusM);
|
|
329
321
|
}
|
|
330
322
|
|
|
331
|
-
extrapolate(lat_, now, latLonCapDegSec, demand_.latitude);
|
|
332
|
-
extrapolate(lon_, now, latLonCapDegSec, demand_.longitude);
|
|
333
|
-
extrapolate(alt_, now, climbCap, demand_.altitude);
|
|
334
|
-
extrapolate(hdg_, now, yawCap, demand_.heading);
|
|
335
|
-
extrapolate(pitch_, now, pitchCap, demand_.pitch);
|
|
336
|
-
extrapolate(roll_, now, rollCap, demand_.roll);
|
|
337
|
-
|
|
323
|
+
extrapolate(lat_, now, latLonCapDegSec, demand_.latitude, false);
|
|
324
|
+
extrapolate(lon_, now, latLonCapDegSec, demand_.longitude, true);
|
|
325
|
+
extrapolate(alt_, now, climbCap, demand_.altitude, false);
|
|
326
|
+
extrapolate(hdg_, now, yawCap, demand_.heading, true);
|
|
327
|
+
extrapolate(pitch_, now, pitchCap, demand_.pitch, true);
|
|
328
|
+
extrapolate(roll_, now, rollCap, demand_.roll, true);
|
|
329
|
+
|
|
330
|
+
double frameDt = 0.0;
|
|
331
|
+
if (lastStep_.time_since_epoch().count() > 0) {
|
|
332
|
+
frameDt = std::chrono::duration<double>(now - lastStep_).count();
|
|
333
|
+
if (frameDt < 0.0) frameDt = 0.0;
|
|
334
|
+
if (frameDt > tunables::kMaxFrameDtSec) frameDt = tunables::kMaxFrameDtSec;
|
|
335
|
+
}
|
|
336
|
+
lastStep_ = now;
|
|
337
|
+
|
|
338
|
+
if (frameDt > 0.0 && tunables::kVfovTauSec > 1.0e-6) {
|
|
339
|
+
const double blend = 1.0 - std::exp(-frameDt / tunables::kVfovTauSec);
|
|
340
|
+
vfov_.value += blend * (demand_.verticalFov - vfov_.value);
|
|
341
|
+
vfov_.velocity = 0.0;
|
|
342
|
+
vfov_.tState = now;
|
|
343
|
+
}
|
|
338
344
|
|
|
339
345
|
hdg_.value = wrap360(hdg_.value);
|
|
340
346
|
lat_.value = std::clamp(lat_.value, -90.0, 90.0);
|
|
@@ -343,12 +349,19 @@ CameraParams CameraIntegrator::step() {
|
|
|
343
349
|
// continuous value for short-arc maths and the camera transform doesn't
|
|
344
350
|
// care.
|
|
345
351
|
|
|
346
|
-
// viewCorrection: SLERP toward the latest target.
|
|
352
|
+
// viewCorrection: time-based SLERP toward the latest target.
|
|
347
353
|
glm::dquat cur = glm::normalize(viewCorrActual_);
|
|
348
354
|
glm::dquat tgt = glm::normalize(viewCorrTarget_);
|
|
349
355
|
if (glm::dot(cur, tgt) < 0.0) tgt = -tgt;
|
|
350
|
-
|
|
351
|
-
|
|
356
|
+
double slerpT = 1.0;
|
|
357
|
+
if (tunables::kViewCorrTauSec > 1.0e-6) {
|
|
358
|
+
slerpT = (frameDt > 0.0)
|
|
359
|
+
? (1.0 - std::exp(-frameDt / tunables::kViewCorrTauSec))
|
|
360
|
+
: 0.0;
|
|
361
|
+
}
|
|
362
|
+
viewCorrActual_ = glm::normalize(glm::slerp(cur, tgt, slerpT));
|
|
363
|
+
|
|
364
|
+
enforceTerrainFloor();
|
|
352
365
|
|
|
353
366
|
CameraParams out;
|
|
354
367
|
out.latitude = lat_.value;
|
|
@@ -362,23 +375,40 @@ CameraParams CameraIntegrator::step() {
|
|
|
362
375
|
return out;
|
|
363
376
|
}
|
|
364
377
|
|
|
365
|
-
void CameraIntegrator::
|
|
366
|
-
const auto now = Clock::now();
|
|
378
|
+
void CameraIntegrator::setTerrainFloor(double floorMsl) {
|
|
367
379
|
std::lock_guard<std::mutex> g(mutex_);
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
380
|
+
if (!std::isfinite(floorMsl)) return;
|
|
381
|
+
terrainFloorActive_ = true;
|
|
382
|
+
terrainFloorMsl_ = std::min(floorMsl, kMaxAltMeters);
|
|
383
|
+
enforceTerrainFloor();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
void CameraIntegrator::clearTerrainFloor() {
|
|
387
|
+
std::lock_guard<std::mutex> g(mutex_);
|
|
388
|
+
terrainFloorActive_ = false;
|
|
389
|
+
terrainFloorMsl_ = 0.0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
void CameraIntegrator::clampActualAltitude(double newAltMsl) {
|
|
393
|
+
setTerrainFloor(newAltMsl);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
void CameraIntegrator::enforceTerrainFloor() {
|
|
397
|
+
if (!terrainFloorActive_) return;
|
|
398
|
+
|
|
399
|
+
const bool inContact =
|
|
400
|
+
demand_.altitude <= terrainFloorMsl_ + tunables::kEpsAltitudeMeters;
|
|
401
|
+
if (inContact) {
|
|
402
|
+
// Contour contact: the floor sample owns altitude, both directions.
|
|
403
|
+
// Residual vertical velocity would carry the camera into the next face.
|
|
404
|
+
alt_.value = terrainFloorMsl_;
|
|
377
405
|
alt_.velocity = 0.0;
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (alt_.value < terrainFloorMsl_) {
|
|
409
|
+
alt_.value = terrainFloorMsl_;
|
|
410
|
+
if (alt_.velocity < 0.0) alt_.velocity = 0.0;
|
|
378
411
|
}
|
|
379
|
-
|
|
380
|
-
// Do not touch demand_.altitude — getDemand() should still report what the
|
|
381
|
-
// user asked for.
|
|
382
412
|
}
|
|
383
413
|
|
|
384
414
|
void CameraIntegrator::setRateCaps(const CameraRateCaps& caps) {
|
|
@@ -410,7 +440,12 @@ bool CameraIntegrator::isActive() const {
|
|
|
410
440
|
if (std::abs(demand_.latitude - lat_.value) > tunables::kEpsLatLon) return true;
|
|
411
441
|
if (std::abs(shortAngleDeltaDeg(lon_.value, demand_.longitude))
|
|
412
442
|
> tunables::kEpsLatLon) return true;
|
|
413
|
-
if (
|
|
443
|
+
if (terrainFloorActive_ &&
|
|
444
|
+
demand_.altitude <= terrainFloorMsl_ + tunables::kEpsAltitudeMeters) {
|
|
445
|
+
if (std::abs(alt_.value - terrainFloorMsl_) > tunables::kEpsAltitudeMeters) return true;
|
|
446
|
+
} else if (std::abs(demand_.altitude - alt_.value) > tunables::kEpsAltitudeMeters) {
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
414
449
|
if (angleAbsDeltaDeg(demand_.heading, hdg_.value) > tunables::kEpsAngleDeg) return true;
|
|
415
450
|
if (angleAbsDeltaDeg(demand_.pitch, pitch_.value) > tunables::kEpsAngleDeg) return true;
|
|
416
451
|
if (angleAbsDeltaDeg(demand_.roll, roll_.value) > tunables::kEpsAngleDeg) return true;
|
|
@@ -73,11 +73,18 @@ public:
|
|
|
73
73
|
// values and an instant runaway.
|
|
74
74
|
CameraParams step();
|
|
75
75
|
|
|
76
|
-
// Terrain
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
76
|
+
// Terrain floor in metres MSL. While active, the rendered altitude is never
|
|
77
|
+
// below this value. When the demand altitude is at or below the floor the
|
|
78
|
+
// camera rides it exactly (uphill and downhill), so a later setAltitude
|
|
79
|
+
// under the surface cannot pull the rendered camera back inside. Demand
|
|
80
|
+
// itself is left untouched.
|
|
81
|
+
void setTerrainFloor(double floorMsl);
|
|
82
|
+
|
|
83
|
+
// Drops the floor so the next slew or the next frame can establish a new
|
|
84
|
+
// one. Used by teleport().
|
|
85
|
+
void clearTerrainFloor();
|
|
86
|
+
|
|
87
|
+
// Sets the terrain floor to `newAltMsl`. Same as setTerrainFloor.
|
|
81
88
|
void clampActualAltitude(double newAltMsl);
|
|
82
89
|
|
|
83
90
|
// Runtime override of the rate caps (driven by view props). 0 = uncapped
|
|
@@ -122,9 +129,14 @@ private:
|
|
|
122
129
|
double alpha, double beta, bool wrapAngle);
|
|
123
130
|
|
|
124
131
|
// Advance the running estimate to `now`, clamp velocity to `maxRateAbs`
|
|
125
|
-
// (per second; 0 = uncapped).
|
|
126
|
-
//
|
|
127
|
-
static void extrapolate(AlphaBeta& s, TimePoint now, double maxRateAbs,
|
|
132
|
+
// (per second; 0 = uncapped). `wrapAngle` makes the silence demand-pull
|
|
133
|
+
// take the short arc (longitude, heading, pitch, roll).
|
|
134
|
+
static void extrapolate(AlphaBeta& s, TimePoint now, double maxRateAbs,
|
|
135
|
+
double demand, bool wrapAngle);
|
|
136
|
+
|
|
137
|
+
// Snap the rendered altitude onto the floor when the camera is in contact
|
|
138
|
+
// or currently below it. Demand is not modified.
|
|
139
|
+
void enforceTerrainFloor();
|
|
128
140
|
|
|
129
141
|
mutable std::mutex mutex_;
|
|
130
142
|
|
|
@@ -141,6 +153,13 @@ private:
|
|
|
141
153
|
|
|
142
154
|
// Runtime overrides; 0 = use compile-time defaults from EngineTunables.
|
|
143
155
|
CameraRateCaps caps_{};
|
|
156
|
+
|
|
157
|
+
// Last step() timestamp. Drives the dt-based fov blend and view-correction
|
|
158
|
+
// slerp. Epoch means "no step yet".
|
|
159
|
+
TimePoint lastStep_{};
|
|
160
|
+
|
|
161
|
+
bool terrainFloorActive_ = false;
|
|
162
|
+
double terrainFloorMsl_ = 0.0;
|
|
144
163
|
};
|
|
145
164
|
|
|
146
165
|
} // namespace reactnativecesium
|