react-native-cesium 1.2.6 → 1.2.8

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.
@@ -27,7 +27,7 @@ layout(push_constant) uniform PC {
27
27
  float translationY; // byte 112
28
28
  float scaleX; // byte 116
29
29
  float scaleY; // byte 120
30
- float _pad; // byte 124
30
+ float lodFade; // byte 124
31
31
  } pc;
32
32
 
33
33
  layout(set = 1, binding = 0) uniform sampler2D overlayTex;
@@ -56,6 +56,20 @@ vec3 hypsometricColor(float alt, float steep) {
56
56
  }
57
57
 
58
58
  void main() {
59
+ #ifdef TERRAIN_DITHER
60
+ // ── LOD transition dither (IGN / Jorge Jimenez) ─────────────────────────────
61
+ // Compiled only into the dither pipeline so the solid pipeline keeps the GPU's
62
+ // early-Z optimisation enabled at full speed. Interleaved Gradient Noise
63
+ // produces a per-pixel threshold approximating blue noise without a texture
64
+ // lookup; the pattern is far less perceptible than Bayer 4×4.
65
+ // lodFade=1 → all pass; lodFade=0 → all discard.
66
+ {
67
+ float ign = fract(52.9829189 *
68
+ fract(0.06711056 * gl_FragCoord.x + 0.00583715 * gl_FragCoord.y));
69
+ if (pc.lodFade <= ign) { discard; }
70
+ }
71
+ #endif
72
+
59
73
  // ── World position (RTC reconstruction) ────────────────────────────────────
60
74
  // wp = tile-local pos + RTC centre (both float32 at ~6.4 Mm scale).
61
75
  // ~0.75 m ULP — far better than eyeRelPos + cameraEcef at altitude.
@@ -86,10 +100,16 @@ void main() {
86
100
  vec2 texUV = fragUV * vec2(pc.scaleX, pc.scaleY)
87
101
  + vec2(pc.translationX, pc.translationY);
88
102
  texUV.y = 1.0 - texUV.y;
103
+ // Return the overlay RGBA directly — preserves any transparency at tile
104
+ // edges and avoids incorrectly stamping the coastline outline on top of
105
+ // satellite imagery. The dither discard above fires before this return,
106
+ // so LOD fading still works correctly on overlay tiles.
89
107
  outColor = texture(overlayTex, texUV);
90
108
  return;
91
109
  }
92
110
 
111
+ vec3 lit;
112
+ {
93
113
  // ── Hypsometric path ──────────────────────────────────────────────────────
94
114
  vec3 dpdx_v = dFdx(localPos), dpdy_v = dFdy(localPos);
95
115
  vec3 rawN = cross(dpdx_v, dpdy_v);
@@ -115,24 +135,32 @@ void main() {
115
135
  vec3 oceanBlue = vec3(0.04, 0.26, 0.52);
116
136
  float landAlt = (pc.hasWaterMask != 0u) ? max(rawAlt, 0.0) : rawAlt;
117
137
  vec3 landLit = hypsometricColor(landAlt, steep) * (amb + diff * 0.72 + rim);
118
- vec3 lit = mix(landLit, oceanBlue, waterVal);
138
+ lit = mix(landLit, oceanBlue, waterVal);
119
139
 
120
140
  // 1 km lat/lon grid — fades when sub-pixel to suppress Moiré.
121
141
  const float gridLat = 1000.0 / 6371000.0;
122
- float cosLat = max(cos(lat), 0.001);
142
+ // cosLat from pxy (already computed above for the Bowring iteration) —
143
+ // avoids a second cos() transcendental. pxy/a_e is the geocentric cosine;
144
+ // the ~0.2 % geodetic difference is imperceptible at grid-line scale.
145
+ float cosLat = max(pxy / 6378137.0, 0.001);
123
146
  float gridLon = gridLat / cosLat;
124
147
  float wLat = fwidth(lat) * 1.5, wLon = fwidth(lon) * 1.5;
125
148
  float gridVis = clamp(min(gridLat / max(wLat, 1e-9),
126
149
  gridLon / max(wLon, 1e-9)), 0.0, 1.0);
127
- float latCell = lat - gridLat * floor(lat / gridLat);
128
- float lonCell = lon - gridLon * floor(lon / gridLon);
150
+ // GLSL mod() is floor-based (always non-negative) — equivalent to the
151
+ // original floor dance but one instruction cleaner.
152
+ float latCell = mod(lat, gridLat);
153
+ float lonCell = mod(lon, gridLon);
129
154
  float dLat = min(latCell, gridLat - latCell);
130
155
  float dLon = min(lonCell, gridLon - lonCell);
131
- float gridA = max(1.0 - smoothstep(0.0, wLat, dLat),
132
- 1.0 - smoothstep(0.0, wLon, dLon)) * gridVis;
156
+ // Linear ramp instead of smoothstep cubic — visually identical for a
157
+ // 1.5 px anti-aliased line, saves two multiplies per component.
158
+ float gridA = max(clamp(1.0 - dLat / max(wLat, 1e-9), 0.0, 1.0),
159
+ clamp(1.0 - dLon / max(wLon, 1e-9), 0.0, 1.0)) * gridVis;
133
160
  lit = mix(lit, vec3(0.15, 0.15, 0.15), gridA * 0.6);
161
+ } // end hypsometric block
134
162
 
135
- // Coastline outline from water mask.
163
+ // Coastline outline from water mask (hypsometric path only — overlay returns above).
136
164
  if (pc.isEllipsoidFallback == 0u) {
137
165
  float dWater = fwidth(waterVal);
138
166
  float coastPx = abs(waterVal - 0.5) / max(dWater * 0.5, 0.01);
@@ -29,6 +29,10 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
29
29
  private var idleProbeAccumulator = 0.0
30
30
  private var renderLoopActive = false
31
31
  private val renderInFlight = AtomicBoolean(false)
32
+ // Last System.nanoTime() when tilesLoading was > 0. Used to keep the render
33
+ // loop at full speed for a grace period after loading finishes — prevents the
34
+ // cascade of idle gaps between LOD refinement phases. 0 = not yet seen.
35
+ private var lastLoadingTimeNs: Long = 0L
32
36
 
33
37
  /// Set in `surfaceDestroyed` before any teardown work begins. Render-thread
34
38
  /// callbacks and choreographer frame ticks check this flag and bail out so
@@ -199,11 +203,20 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
199
203
  return
200
204
  }
201
205
 
202
- val shouldRender = b.shouldRenderNextFrame()
206
+ // Post-loading grace period: keep the loop at full speed for 2 seconds
207
+ // after the last non-zero loading frame so LOD transitions complete quickly
208
+ // and the next refinement level is discovered without a multi-second idle gap.
209
+ val nowNs = System.nanoTime()
210
+ val inGracePeriod = lastLoadingTimeNs > 0L &&
211
+ (nowNs - lastLoadingTimeNs) < 2_000_000_000L
212
+
213
+ val shouldRender = b.shouldRenderNextFrame() || inGracePeriod
203
214
  val nextImmediate: Boolean
204
215
  if (shouldRender) {
205
216
  idleProbeAccumulator = 0.0
206
217
  b.renderFrame(nowSeconds)
218
+ // Stamp after renderFrame so the freshly-updated tile counts are used.
219
+ if (b.getMetricsTilesLoading() > 0) lastLoadingTimeNs = nowNs
207
220
  nextImmediate = true
208
221
  } else {
209
222
  // Approximate dt for the idle probe; not used for motion.
@@ -15,6 +15,10 @@ namespace {
15
15
  // isolation without linking CesiumGeospatial. Matches Ellipsoid::WGS84.
16
16
  inline constexpr double kEarthMeanRadiusM = 6371008.7714150595;
17
17
 
18
+ // Hard ceiling on altitude (metres MSL). Enforced on both the demand and the
19
+ // running estimate so no code path can push the camera above this value.
20
+ inline constexpr double kMaxAltMeters = 100'000.0;
21
+
18
22
  inline double shortAngleDeltaDeg(double from, double to) {
19
23
  return std::fmod(to - from + 540.0, 360.0) - 180.0;
20
24
  }
@@ -105,7 +109,8 @@ void CameraIntegrator::updateAlphaBeta(
105
109
  s.tLastMeas = tz;
106
110
  }
107
111
 
108
- void CameraIntegrator::extrapolate(AlphaBeta& s, TimePoint now, double maxRateAbs) {
112
+ void CameraIntegrator::extrapolate(
113
+ AlphaBeta& s, TimePoint now, double maxRateAbs, double demand) {
109
114
  double dt = std::chrono::duration<double>(now - s.tState).count();
110
115
  if (dt <= 0.0) {
111
116
  s.tState = now;
@@ -121,9 +126,14 @@ void CameraIntegrator::extrapolate(AlphaBeta& s, TimePoint now, double maxRateAb
121
126
  // grace window and the decay τ scale with the EWMA of inter-arrival times
122
127
  // so a 60 Hz worklet bleeds in ~50 ms while a 1 Hz GPS glide is left
123
128
  // alone between fixes.
129
+ //
130
+ // silence and grace are hoisted out of the block so the demand-pull term
131
+ // below can reuse them without a second chrono subtraction.
132
+ double silence = -1.0; // negative sentinel: "no measurement seen yet"
133
+ double grace = 0.0;
124
134
  if (s.tLastMeas.time_since_epoch().count() > 0) {
125
- const double silence = std::chrono::duration<double>(now - s.tLastMeas).count();
126
- const double grace = tunables::kVelocitySilenceGraceFactor * s.meanIntervalSec;
135
+ silence = std::chrono::duration<double>(now - s.tLastMeas).count();
136
+ grace = tunables::kVelocitySilenceGraceFactor * s.meanIntervalSec;
127
137
  if (silence > grace) {
128
138
  const double tau = tunables::kVelocityBleedTauFactor * s.meanIntervalSec;
129
139
  if (tau > 1.0e-6) {
@@ -139,6 +149,16 @@ void CameraIntegrator::extrapolate(AlphaBeta& s, TimePoint now, double maxRateAb
139
149
  }
140
150
  s.value += s.velocity * dt;
141
151
  s.tState = now;
152
+
153
+ // Demand pull — guarantees convergence to the last demanded value for
154
+ // one-shot API calls (e.g. setAltitude from a button tap). Active only
155
+ // when measurements have stopped (silence > grace); continuous gesture or
156
+ // sensor streams always deliver a new update before the grace window
157
+ // expires, so this term is never active for them.
158
+ if (silence > grace && tunables::kDemandPullTauSec > 1.0e-6) {
159
+ s.value += (1.0 - std::exp(-dt / tunables::kDemandPullTauSec))
160
+ * (demand - s.value);
161
+ }
142
162
  }
143
163
 
144
164
  // ── Demand setters ────────────────────────────────────────────────────────
@@ -172,6 +192,7 @@ void CameraIntegrator::setAltitude(double altitudeMeters) {
172
192
  return;
173
193
  }
174
194
 
195
+ altitudeMeters = std::min(altitudeMeters, kMaxAltMeters);
175
196
  updateAlphaBeta(alt_, altitudeMeters, now,
176
197
  tunables::kAlphaAlt, tunables::kBetaAlt, false);
177
198
  demand_.altitude = altitudeMeters;
@@ -285,16 +306,17 @@ CameraParams CameraIntegrator::step() {
285
306
  latLonCapDegSec = glm::degrees(groundCap / kEarthMeanRadiusM);
286
307
  }
287
308
 
288
- extrapolate(lat_, now, latLonCapDegSec);
289
- extrapolate(lon_, now, latLonCapDegSec);
290
- extrapolate(alt_, now, climbCap);
291
- extrapolate(hdg_, now, yawCap);
292
- extrapolate(pitch_, now, pitchCap);
293
- extrapolate(roll_, now, rollCap);
294
- extrapolate(vfov_, now, 0.0); // no velocity term anyway
309
+ extrapolate(lat_, now, latLonCapDegSec, demand_.latitude);
310
+ extrapolate(lon_, now, latLonCapDegSec, demand_.longitude);
311
+ extrapolate(alt_, now, climbCap, demand_.altitude);
312
+ extrapolate(hdg_, now, yawCap, demand_.heading);
313
+ extrapolate(pitch_, now, pitchCap, demand_.pitch);
314
+ extrapolate(roll_, now, rollCap, demand_.roll);
315
+ extrapolate(vfov_, now, 0.0, demand_.verticalFov);
295
316
 
296
317
  hdg_.value = wrap360(hdg_.value);
297
318
  lat_.value = std::clamp(lat_.value, -90.0, 90.0);
319
+ alt_.value = std::min(alt_.value, kMaxAltMeters);
298
320
  // Longitude is allowed to drift over ±180; consumers usually want the raw
299
321
  // continuous value for short-arc maths and the camera transform doesn't
300
322
  // care.
@@ -124,7 +124,7 @@ private:
124
124
  // Advance the running estimate to `now`, clamp velocity to `maxRateAbs`
125
125
  // (per second; 0 = uncapped). For angles, the resulting value is also
126
126
  // normalised to its canonical range by the caller as needed.
127
- static void extrapolate(AlphaBeta& s, TimePoint now, double maxRateAbs);
127
+ static void extrapolate(AlphaBeta& s, TimePoint now, double maxRateAbs, double demand);
128
128
 
129
129
  mutable std::mutex mutex_;
130
130