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.
Files changed (58) hide show
  1. package/README.md +11 -8
  2. package/android/CMakeLists.txt +1 -0
  3. package/android/build.gradle +1 -1
  4. package/android/gradle.properties +4 -4
  5. package/android/src/main/cpp/CesiumBridgeAndroid.cpp +44 -18
  6. package/android/src/main/cpp/CesiumBridgeAndroid.h +9 -0
  7. package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +3 -0
  8. package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +9 -0
  9. package/cpp/engine/CameraIntegrator.cpp +82 -47
  10. package/cpp/engine/CameraIntegrator.hpp +27 -8
  11. package/cpp/engine/CesiumEngine.cpp +183 -312
  12. package/cpp/engine/CesiumEngine.hpp +41 -28
  13. package/cpp/engine/EngineTunables.hpp +6 -5
  14. package/cpp/engine/GeoidConverter.cpp +32 -11
  15. package/cpp/engine/GeoidConverter.hpp +41 -0
  16. package/cpp/engine/GltfToMesh.cpp +14 -5
  17. package/cpp/engine/ResourcePreparer.cpp +3 -3
  18. package/cpp/engine/ResourcePreparer.hpp +1 -1
  19. package/cpp/engine/TerrainFloor.hpp +70 -0
  20. package/cpp/engine/TerrainHeight.hpp +113 -0
  21. package/cpp/engine/test/CameraIntegratorTest.cpp +264 -0
  22. package/cpp/engine/test/GeoidConverterTest.cpp +77 -0
  23. package/cpp/engine/test/build_and_run.sh +20 -6
  24. package/ios/CesiumBridge.h +5 -0
  25. package/ios/CesiumBridge.mm +61 -32
  26. package/ios/HybridCesiumView.swift +11 -0
  27. package/lib/typescript/src/specs/cesium-view.nitro.d.ts +34 -9
  28. package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
  29. package/nitrogen/generated/android/c++/JCameraState.hpp +1 -1
  30. package/nitrogen/generated/android/c++/JCesiumMetrics.hpp +1 -1
  31. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +9 -0
  32. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +2 -0
  33. package/nitrogen/generated/android/c++/JQuaternion.hpp +1 -1
  34. package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +182 -131
  35. package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.hpp +6 -1
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CameraState.kt +1 -1
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CesiumMetrics.kt +1 -1
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Func_void_CameraState.kt +0 -2
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Func_void_CesiumMetrics.kt +0 -2
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +8 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Quaternion.kt +1 -1
  42. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewManager.kt +26 -10
  43. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewStateUpdater.kt +2 -2
  44. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +15 -0
  45. package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +7 -0
  46. package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +217 -161
  47. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +1 -0
  48. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +24 -0
  49. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +2 -0
  50. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +2 -0
  51. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +36 -335
  52. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +110 -77
  53. package/nitrogen/generated/shared/json/CesiumViewConfig.json +1 -0
  54. package/package.json +12 -12
  55. package/scripts/cesium/config.mjs +1 -1
  56. package/scripts/cesium/vcpkg-triplets/arm64-ios-simulator.cmake +3 -0
  57. package/scripts/cesium/vcpkg-triplets/arm64-ios.cmake +4 -0
  58. package/src/specs/cesium-view.nitro.ts +34 -9
@@ -12,8 +12,10 @@
12
12
  #include <CesiumUtility/IntrusivePointer.h>
13
13
 
14
14
  #include <chrono>
15
+ #include <cstdint>
15
16
  #include <memory>
16
17
  #include <string>
18
+ #include <vector>
17
19
 
18
20
  namespace Cesium3DTilesSelection {
19
21
  class Tileset;
@@ -103,8 +105,9 @@ struct EngineConfig {
103
105
  int32_t taskProcessorThreads = 0;
104
106
 
105
107
  // ── Camera constraints ───────────────────────────────────────────────
106
- // Minimum altitude above the loaded terrain surface (metres MSL).
107
- // 0 = no constraint (default). Typical use: 2.0 to prevent going underground.
108
+ // Clearance above the rendered terrain surface, in metres. The floor is
109
+ // always enforced. 0 means the camera may sit on the surface and may not
110
+ // go below it.
108
111
  float minAltitudeAboveTerrain = 0.0f;
109
112
  };
110
113
 
@@ -142,15 +145,20 @@ public:
142
145
 
143
146
  const EngineConfig& getConfig() const { return config_; }
144
147
 
145
- // Ellipsoid height (metres) of the terrain surface under the camera nadir,
146
- // sampled from loaded tile meshes. Meaningless until terrainFloorKnown().
147
- float terrainFloorEllipsoidMeters() const { return terrainFloorEllipsoidM_; }
148
- // True after at least one frame has sampled the floor from real terrain tiles.
149
- bool terrainFloorKnown() const { return terrainFloorValid_; }
148
+ // Ellipsoid height (metres) of the rendered terrain surface at lat/lon,
149
+ // interpolated from the finest resident tile. False when no triangle
150
+ // contains the point (tiles not loaded yet, or the point is off the mesh).
151
+ // Uses the tiles captured by the previous updateFrame.
152
+ bool sampleTerrainEllipsoidMeters(double latitudeDeg,
153
+ double longitudeDeg,
154
+ float& outEllipsoidMeters);
150
155
 
151
156
  private:
152
157
  void createTileset();
153
158
  void destroyTileset();
159
+ // Wait until worker tasks and the main-thread continuations they post have
160
+ // finished. Safe to call with or without a live tileset.
161
+ void drainAsyncWork();
154
162
 
155
163
  // Apply runtime-mutable bits of `cfg` to the live tileset. Returns true if a
156
164
  // value actually changed (callers can use this to trigger force-render).
@@ -185,11 +193,6 @@ private:
185
193
  GlobeCamera camera_;
186
194
  TileLifecycleManager lifecycle_;
187
195
 
188
- // Ellipsoid height of the terrain surface at the camera nadir (horizontal
189
- // nearest-neighbour sample). Only written after a successful tile scan.
190
- float terrainFloorEllipsoidM_ = 0.0f;
191
- bool terrainFloorValid_ = false;
192
-
193
196
  // Wall-clock timestamp of the previous updateFrame call. Used to compute
194
197
  // deltaTime for Tileset::updateViewGroup, which drives LOD transition fading.
195
198
  // Zero-initialised "not yet set" state — first frame passes 0 deltaTime.
@@ -224,22 +227,32 @@ private:
224
227
  // is guaranteed to contain the right bytes — fast path becomes legal.
225
228
  int stableSigFrames_ = 0;
226
229
 
227
- // ── terrain-floor scan cache ──────────────────────────────────────
228
- // The minAltitudeAboveTerrain clamp scans every vertex of every tile whose
229
- // bounding region contains the camera lon/lat and picks the altitude of
230
- // the vertex with the smallest horizontal (ENU) distance to the camera
231
- // nadir (tie-break: lower ellipsoid height). That's an O(NV)
232
- // scan over the visible primitives — dominant CPU cost when the camera
233
- // sits over a dense LOD chain. Result depends only on the camera lon/lat
234
- // and the set of visible primitives, so we cache it keyed on
235
- // (camLon, camLat, geometrySignature). Threshold of ~1e-6 degrees (~10 cm)
236
- // is far below the inter-vertex spacing of the densest tiles served by
237
- // Cesium Ion world terrain, so the cached nearest vertex stays correct.
238
- bool floorCacheValid_ = false;
239
- double floorCacheCamLonDeg_ = 0.0;
240
- double floorCacheCamLatDeg_ = 0.0;
241
- uint64_t floorCacheSignature_ = 0;
242
- float floorCacheValue_ = 0.0f;
230
+ // Tiles drawn by the last updateFrame. The next frame's terrain query
231
+ // reads this list before updateViewGroup can free the previous selection.
232
+ struct ResidentTerrainTile {
233
+ const TileGPUResources* res = nullptr;
234
+ double geometricError = 0.0;
235
+ double west = 0.0;
236
+ double south = 0.0;
237
+ double east = 0.0;
238
+ double north = 0.0;
239
+ };
240
+ std::vector<ResidentTerrainTile> residentTiles_;
241
+ uint64_t residentSignature_ = 0;
242
+
243
+ // One-entry hover cache. Keyed by resident signature, finest-tile identity,
244
+ // and a sub-metre lat/lon cell. A miss scans that one tile's triangles.
245
+ struct TerrainHeightCache {
246
+ bool valid = false;
247
+ const TileGPUResources* tile = nullptr;
248
+ int latCell = 0;
249
+ int lonCell = 0;
250
+ uint64_t signature = 0;
251
+ float height = 0.0f;
252
+ };
253
+ TerrainHeightCache heightCache_;
254
+
255
+ void clearResidentTerrain();
243
256
  };
244
257
 
245
258
  } // namespace reactnativecesium
@@ -63,16 +63,17 @@ inline constexpr int32_t kDefaultLoadingDescendantLimit = 20;
63
63
  // steady-state lag at constant motion) while remaining instant under a
64
64
  // 60 Hz gesture-driven worklet.
65
65
  //
66
- // `viewCorrection` (a unit quaternion) uses a single-coefficient SLERP toward
67
- // the latest demand each frame — there is no useful velocity concept for it.
68
- // `verticalFovDeg` is a 1-D scalar with α only (no velocity term).
66
+ // `viewCorrection` (a unit quaternion) slerps toward the latest demand with a
67
+ // time constant, so a single long frame does not close half the error.
68
+ // `verticalFovDeg` eases toward its demand with the same kind of time constant
69
+ // (no velocity term).
69
70
  inline constexpr double kAlphaPos = 0.6; inline constexpr double kBetaPos = 0.05;
70
71
  inline constexpr double kAlphaAlt = 0.6; inline constexpr double kBetaAlt = 0.05;
71
72
  inline constexpr double kAlphaHdg = 0.55; inline constexpr double kBetaHdg = 0.05;
72
73
  inline constexpr double kAlphaPitch = 0.75; inline constexpr double kBetaPitch = 0.05;
73
74
  inline constexpr double kAlphaRoll = 0.75; inline constexpr double kBetaRoll = 0.05;
74
- inline constexpr double kAlphaVfov = 0.4; // no velocity term
75
- inline constexpr double kAlphaViewCorr = 0.5; // SLERP toward target
75
+ inline constexpr double kVfovTauSec = 0.12; // seconds, dt-based blend
76
+ inline constexpr double kViewCorrTauSec = 0.08; // seconds, dt-based slerp
76
77
 
77
78
  // Convergence epsilons (used to decide when the integrator has settled and
78
79
  // the render loop can drop back to idle).
@@ -3,34 +3,55 @@
3
3
  #include "../third_party/egm96/EGM96.h"
4
4
 
5
5
  #include <cmath>
6
+ #include <limits>
6
7
 
7
8
  namespace reactnativecesium {
8
9
 
9
10
  namespace {
10
11
 
11
12
  double normalizeLongitudeDegrees(double longitudeDeg) {
12
- if (!std::isfinite(longitudeDeg)) return 0.0;
13
13
  double wrapped = std::fmod(longitudeDeg + 180.0, 360.0);
14
14
  if (wrapped < 0.0) wrapped += 360.0;
15
15
  return wrapped - 180.0;
16
16
  }
17
17
 
18
+ // Geoid undulation N, where HAE = MSL + N. NaN when the position or the
19
+ // model result is non-finite; callers then return the input height unchanged.
20
+ double geoidUndulationMeters(double latitudeDeg, double longitudeDeg) {
21
+ if (!std::isfinite(latitudeDeg) || !std::isfinite(longitudeDeg)) {
22
+ return std::numeric_limits<double>::quiet_NaN();
23
+ }
24
+
25
+ const double undulation = egm96_compute_altitude_offset(
26
+ latitudeDeg, normalizeLongitudeDegrees(longitudeDeg));
27
+ if (!std::isfinite(undulation)) {
28
+ return std::numeric_limits<double>::quiet_NaN();
29
+ }
30
+ return undulation;
31
+ }
32
+
33
+ double applyUndulation(double heightMeters,
34
+ double latitudeDeg,
35
+ double longitudeDeg,
36
+ double sign) {
37
+ if (!std::isfinite(heightMeters)) return heightMeters;
38
+ const double undulation = geoidUndulationMeters(latitudeDeg, longitudeDeg);
39
+ if (!std::isfinite(undulation)) return heightMeters;
40
+ return heightMeters + sign * undulation;
41
+ }
42
+
18
43
  } // namespace
19
44
 
20
45
  double mslToEllipsoidMeters(double latitudeDeg,
21
46
  double longitudeDeg,
22
47
  double mslMeters) {
23
- if (!std::isfinite(latitudeDeg) || !std::isfinite(longitudeDeg) ||
24
- !std::isfinite(mslMeters)) {
25
- return mslMeters;
26
- }
27
-
28
- const double normalizedLon = normalizeLongitudeDegrees(longitudeDeg);
29
- const double geoidOffset =
30
- egm96_compute_altitude_offset(latitudeDeg, normalizedLon);
48
+ return applyUndulation(mslMeters, latitudeDeg, longitudeDeg, 1.0);
49
+ }
31
50
 
32
- if (!std::isfinite(geoidOffset)) return mslMeters;
33
- return mslMeters + geoidOffset;
51
+ double ellipsoidToMslMeters(double latitudeDeg,
52
+ double longitudeDeg,
53
+ double ellipsoidMeters) {
54
+ return applyUndulation(ellipsoidMeters, latitudeDeg, longitudeDeg, -1.0);
34
55
  }
35
56
 
36
57
  } // namespace reactnativecesium
@@ -7,4 +7,45 @@ double mslToEllipsoidMeters(double latitudeDeg,
7
7
  double longitudeDeg,
8
8
  double mslMeters);
9
9
 
10
+ /// Converts WGS84 ellipsoidal height (HAE) to orthometric height (MSL).
11
+ double ellipsoidToMslMeters(double latitudeDeg,
12
+ double longitudeDeg,
13
+ double ellipsoidMeters);
14
+
15
+ /// `ellipsoidHeight` selects HAE→MSL. Otherwise `altitudeMeters` is already MSL.
16
+ inline double demandAltitudeToMslMeters(double latitudeDeg,
17
+ double longitudeDeg,
18
+ double altitudeMeters,
19
+ bool ellipsoidHeight) {
20
+ if (!ellipsoidHeight) return altitudeMeters;
21
+ return ellipsoidToMslMeters(latitudeDeg, longitudeDeg, altitudeMeters);
22
+ }
23
+
24
+ /// `ellipsoidHeight` selects MSL→HAE. Otherwise `mslMeters` is already the
25
+ /// demand datum. Inverse of `demandAltitudeToMslMeters`.
26
+ inline double mslToDemandAltitudeMeters(double latitudeDeg,
27
+ double longitudeDeg,
28
+ double mslMeters,
29
+ bool ellipsoidHeight) {
30
+ if (!ellipsoidHeight) return mslMeters;
31
+ return mslToEllipsoidMeters(latitudeDeg, longitudeDeg, mslMeters);
32
+ }
33
+
34
+ /// Mean-sea-level change below this is left alone when a horizontal move
35
+ /// re-expresses an ellipsoidal height. One centimetre.
36
+ inline constexpr double kHorizontalMoveMslEpsilonMeters = 0.01;
37
+
38
+ /// Keeps the same ellipsoidal height when the horizontal position changes.
39
+ /// `oldMslMeters` is mean sea level at the old point; the result is mean sea
40
+ /// level at the new point.
41
+ inline double mslAfterHorizontalMove(double oldLatitudeDeg,
42
+ double oldLongitudeDeg,
43
+ double oldMslMeters,
44
+ double newLatitudeDeg,
45
+ double newLongitudeDeg) {
46
+ const double hae =
47
+ mslToEllipsoidMeters(oldLatitudeDeg, oldLongitudeDeg, oldMslMeters);
48
+ return ellipsoidToMslMeters(newLatitudeDeg, newLongitudeDeg, hae);
49
+ }
50
+
10
51
  } // namespace reactnativecesium
@@ -10,6 +10,7 @@
10
10
 
11
11
  #include <cmath>
12
12
  #include <cstring>
13
+ #include <optional>
13
14
 
14
15
  namespace reactnativecesium {
15
16
 
@@ -64,12 +65,17 @@ GltfToMesh::convert(const CesiumGltf::Model& model,
64
65
 
65
66
  const CesiumGltf::PositionAccessorType posAccessor =
66
67
  CesiumGltf::getPositionAccessorView(gltf, primitive);
67
- if (posAccessor.status() != CesiumGltf::AccessorViewStatus::Valid ||
68
- posAccessor.size() <= 0)
68
+ const CesiumGltf::AccessorViewStatus posStatus =
69
+ std::visit(CesiumGltf::StatusFromAccessor{}, posAccessor);
70
+ if (posStatus != CesiumGltf::AccessorViewStatus::Valid)
71
+ return;
72
+
73
+ const int64_t vertexCount =
74
+ std::visit(CesiumGltf::CountFromAccessor{}, posAccessor);
75
+ if (vertexCount <= 0)
69
76
  return;
70
77
 
71
78
  const glm::dmat4 worldTransform = rootTransform * nodeTransform;
72
- const int64_t vertexCount = posAccessor.size();
73
79
 
74
80
  // RTC centre = translation column of the world transform.
75
81
  // After applyRtcCenter this is the tile's ECEF centre embedded by
@@ -82,8 +88,11 @@ GltfToMesh::convert(const CesiumGltf::Model& model,
82
88
  prim.altitudes.reserve(static_cast<size_t>(vertexCount));
83
89
 
84
90
  for (int64_t i = 0; i < vertexCount; ++i) {
85
- const auto& p = posAccessor[i];
86
- const glm::dvec3 gltfLocal(p.value[0], p.value[1], p.value[2]);
91
+ const std::optional<glm::dvec3> pos = std::visit(
92
+ CesiumGltf::PositionFromAccessor{i}, posAccessor);
93
+ if (!pos)
94
+ continue;
95
+ const glm::dvec3 gltfLocal = *pos;
87
96
  // Absolute ECEF in double precision.
88
97
  const glm::dvec3 ecef(worldTransform * glm::dvec4(gltfLocal, 1.0));
89
98
  // Tile-local position (small float, retains full float32 precision
@@ -1,7 +1,7 @@
1
1
  #include "ResourcePreparer.hpp"
2
2
 
3
3
  #include <Cesium3DTilesSelection/Tile.h>
4
- #include <CesiumGltf/ImageAsset.h>
4
+ #include <CesiumImage/ImageAsset.h>
5
5
  #include <CesiumGltf/Model.h>
6
6
  #include <CesiumUtility/JsonValue.h>
7
7
 
@@ -117,12 +117,12 @@ void ResourcePreparer::free(Cesium3DTilesSelection::Tile& /*tile*/,
117
117
 
118
118
  // ── Raster overlay pipeline ─────────────────────────────────────────────────
119
119
 
120
- void* ResourcePreparer::prepareRasterInLoadThread(CesiumGltf::ImageAsset& image,
120
+ void* ResourcePreparer::prepareRasterInLoadThread(CesiumImage::ImageAsset& image,
121
121
  const std::any&) {
122
122
  if (image.pixelData.empty() || image.width <= 0 || image.height <= 0) {
123
123
  return nullptr;
124
124
  }
125
- if (image.compressedPixelFormat != CesiumGltf::GpuCompressedPixelFormat::NONE) {
125
+ if (image.compressedPixelFormat != CesiumImage::GpuCompressedPixelFormat::NONE) {
126
126
  return nullptr;
127
127
  }
128
128
  if (image.bytesPerChannel != 1) return nullptr;
@@ -64,7 +64,7 @@ public:
64
64
  void free(Cesium3DTilesSelection::Tile& tile, void* pLoadThreadResult,
65
65
  void* pMainThreadResult) noexcept override;
66
66
 
67
- void* prepareRasterInLoadThread(CesiumGltf::ImageAsset& image,
67
+ void* prepareRasterInLoadThread(CesiumImage::ImageAsset& image,
68
68
  const std::any& rendererOptions) override;
69
69
  void* prepareRasterInMainThread(
70
70
  CesiumRasterOverlays::RasterOverlayTile& rasterTile,
@@ -0,0 +1,70 @@
1
+ #pragma once
2
+
3
+ #include "CameraIntegrator.hpp"
4
+ #include "CesiumEngine.hpp"
5
+ #include "GeoidConverter.hpp"
6
+ #include "TerrainHeight.hpp"
7
+
8
+ #include <algorithm>
9
+
10
+ namespace reactnativecesium {
11
+
12
+ // Previous horizontal position and whether a real mesh sample has landed.
13
+ // Shared by the iOS and Android render loops so both follow the same floor.
14
+ struct TerrainLeadState {
15
+ bool hasPrev = false;
16
+ bool latched = false;
17
+ double prevLatDeg = 0.0;
18
+ double prevLonDeg = 0.0;
19
+ };
20
+
21
+ // A position slew drops the old floor and the one-frame lead. The next
22
+ // rendered frame samples terrain at the new position.
23
+ inline void noteTerrainSlew(CameraIntegrator& integrator, TerrainLeadState& lead) {
24
+ integrator.clearTerrainFloor();
25
+ lead = {};
26
+ }
27
+
28
+ // Samples the resident mesh at the stepped camera and one frame ahead, then
29
+ // holds the rendered altitude to surface + clearance before the frame is drawn.
30
+ // Demand is not modified. Returns the camera to render.
31
+ inline CameraParams applyTerrainFloor(CesiumEngine& engine,
32
+ CameraIntegrator& integrator,
33
+ const CameraParams& stepped,
34
+ TerrainLeadState& lead,
35
+ double clearanceMeters) {
36
+ const double clearance = std::max(0.0, clearanceMeters);
37
+
38
+ double leadLat = stepped.latitude;
39
+ double leadLon = stepped.longitude;
40
+ const bool sampleLead = terrainLeadLatLon(
41
+ stepped.latitude, stepped.longitude,
42
+ lead.prevLatDeg, lead.prevLonDeg, lead.hasPrev,
43
+ leadLat, leadLon);
44
+
45
+ float nadir = 0.0f;
46
+ float ahead = 0.0f;
47
+ const bool hasNadir =
48
+ engine.sampleTerrainEllipsoidMeters(stepped.latitude, stepped.longitude, nadir);
49
+ const bool hasAhead = sampleLead &&
50
+ engine.sampleTerrainEllipsoidMeters(leadLat, leadLon, ahead);
51
+
52
+ float surface = 0.0f;
53
+ const bool haveMesh = resolveSurfaceEllipsoid(hasNadir, nadir, hasAhead, ahead, surface);
54
+ const double geoidN = mslToEllipsoidMeters(stepped.latitude, stepped.longitude, 0.0);
55
+ if (haveMesh) {
56
+ integrator.setTerrainFloor(static_cast<double>(surface) - geoidN + clearance);
57
+ lead.latched = true;
58
+ } else if (!lead.latched) {
59
+ // No mesh yet: stay at or above the ellipsoid. Once a mesh sample has
60
+ // landed, a later miss keeps the last floor instead of dropping to sea level.
61
+ integrator.setTerrainFloor(-geoidN + clearance);
62
+ }
63
+
64
+ lead.prevLatDeg = stepped.latitude;
65
+ lead.prevLonDeg = stepped.longitude;
66
+ lead.hasPrev = true;
67
+ return integrator.getActual();
68
+ }
69
+
70
+ } // namespace reactnativecesium
@@ -0,0 +1,113 @@
1
+ #pragma once
2
+
3
+ #include <algorithm>
4
+ #include <cmath>
5
+
6
+ namespace reactnativecesium {
7
+
8
+ // Pure terrain-floor helpers. No Cesium types, so the host harness can test
9
+ // contour rules without a tileset.
10
+
11
+ // Ignore a lead step shorter than this (hover) or longer than this (a slew,
12
+ // not one frame of travel).
13
+ inline constexpr double kTerrainLeadMinMeters = 0.05;
14
+ inline constexpr double kTerrainLeadMaxMeters = 80.0;
15
+ inline constexpr double kEarthRadiusMeters = 6371008.7714150595;
16
+
17
+ // Horizontal barycentric height. (pe, pn) and the triangle are in metres in
18
+ // the same local east/north frame. Near-vertical triangles (terrain skirts)
19
+ // have ~0 horizontal area and are rejected. On a hit, outHeight is the
20
+ // interpolated ellipsoid height in metres.
21
+ inline bool horizontalTriangleHeight(double pe, double pn,
22
+ double e0, double n0, float h0,
23
+ double e1, double n1, float h1,
24
+ double e2, double n2, float h2,
25
+ float& outHeight) {
26
+ const double v0e = e1 - e0;
27
+ const double v0n = n1 - n0;
28
+ const double v1e = e2 - e0;
29
+ const double v1n = n2 - n0;
30
+ const double v2e = pe - e0;
31
+ const double v2n = pn - n0;
32
+
33
+ const double dot00 = v0e * v0e + v0n * v0n;
34
+ const double dot01 = v0e * v1e + v0n * v1n;
35
+ const double dot11 = v1e * v1e + v1n * v1n;
36
+ const double dot02 = v0e * v2e + v0n * v2n;
37
+ const double dot12 = v1e * v2e + v1n * v2n;
38
+
39
+ // denom = (2 * signed horizontal area)^2.
40
+ const double denom = dot00 * dot11 - dot01 * dot01;
41
+ if (std::abs(denom) < 1.0e-12) return false;
42
+
43
+ const double e20 = e2 - e1;
44
+ const double n20 = n2 - n1;
45
+ const double edge2 = e20 * e20 + n20 * n20;
46
+ const double longest = std::sqrt(std::max(dot00, std::max(dot11, edge2)));
47
+ const double twiceArea = std::sqrt(std::abs(denom));
48
+ // Width of the triangle across its longest edge. Terrain skirts are
49
+ // vertical, so float error leaves them only millimetres wide.
50
+ if (longest < 1.0e-6 || twiceArea / longest < 0.05) return false;
51
+
52
+ const double inv = 1.0 / denom;
53
+ const double u = (dot11 * dot02 - dot01 * dot12) * inv;
54
+ const double v = (dot00 * dot12 - dot01 * dot02) * inv;
55
+ const double w = 1.0 - u - v;
56
+
57
+ constexpr double kEdge = -1.0e-4;
58
+ if (u < kEdge || v < kEdge || w < kEdge) return false;
59
+
60
+ outHeight = static_cast<float>(w * static_cast<double>(h0) +
61
+ u * static_cast<double>(h1) +
62
+ v * static_cast<double>(h2));
63
+ return true;
64
+ }
65
+
66
+ // Fresh each frame. The higher of the nadir sample and the one-frame lead.
67
+ // A lower lead (past a crest) does not keep the previous peak. A higher lead
68
+ // (valley turning uphill) raises the floor in this frame. No memory.
69
+ inline bool resolveSurfaceEllipsoid(bool hasNadir, float nadir,
70
+ bool hasAhead, float ahead,
71
+ float& outHeight) {
72
+ if (hasNadir && hasAhead) {
73
+ outHeight = std::max(nadir, ahead);
74
+ return true;
75
+ }
76
+ if (hasNadir) {
77
+ outHeight = nadir;
78
+ return true;
79
+ }
80
+ if (hasAhead) {
81
+ outHeight = ahead;
82
+ return true;
83
+ }
84
+ return false;
85
+ }
86
+
87
+ // One frame ahead of (latDeg, lonDeg) along the last step. Returns false when
88
+ // there is no distinct lead; out* are then equal to the current position.
89
+ inline bool terrainLeadLatLon(double latDeg, double lonDeg,
90
+ double prevLatDeg, double prevLonDeg,
91
+ bool hasPrev,
92
+ double& outLatDeg, double& outLonDeg) {
93
+ outLatDeg = latDeg;
94
+ outLonDeg = lonDeg;
95
+ if (!hasPrev) return false;
96
+
97
+ const double dLat = latDeg - prevLatDeg;
98
+ double dLon = lonDeg - prevLonDeg;
99
+ dLon = std::fmod(dLon + 540.0, 360.0) - 180.0;
100
+
101
+ const double latRad = latDeg * (3.14159265358979323846 / 180.0);
102
+ const double northM = dLat * (3.14159265358979323846 / 180.0) * kEarthRadiusMeters;
103
+ const double eastM =
104
+ dLon * (3.14159265358979323846 / 180.0) * kEarthRadiusMeters * std::cos(latRad);
105
+ const double dist = std::hypot(eastM, northM);
106
+ if (dist < kTerrainLeadMinMeters || dist > kTerrainLeadMaxMeters) return false;
107
+
108
+ outLatDeg = latDeg + dLat;
109
+ outLonDeg = lonDeg + dLon;
110
+ return true;
111
+ }
112
+
113
+ } // namespace reactnativecesium