react-native-cesium 1.2.7 → 1.2.9

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);
@@ -9,8 +9,9 @@ import android.view.Surface
9
9
  class CesiumBridgeJNI {
10
10
  private var nativePtr: Long = nativeCreate()
11
11
 
12
- fun init(surface: Surface, width: Int, height: Int, cacheDir: String, cacertPath: String) {
13
- nativeInit(nativePtr, surface, width, height, cacheDir, cacertPath)
12
+ fun init(surface: Surface, width: Int, height: Int, cacheDir: String, cacertPath: String,
13
+ ionAccessToken: String = "", ionAssetId: Long = 1L) {
14
+ nativeInit(nativePtr, surface, width, height, cacheDir, cacertPath, ionAccessToken, ionAssetId)
14
15
  }
15
16
 
16
17
  fun shutdown() = nativeShutdown(nativePtr)
@@ -98,7 +99,7 @@ class CesiumBridgeJNI {
98
99
  fun setMinAltitudeAboveTerrain(v: Float) = nativeSetMinAltitudeAboveTerrain(nativePtr, v)
99
100
 
100
101
  private external fun nativeCreate(): Long
101
- private external fun nativeInit(ptr: Long, surface: Surface, w: Int, h: Int, cacheDir: String, cacertPath: String)
102
+ private external fun nativeInit(ptr: Long, surface: Surface, w: Int, h: Int, cacheDir: String, cacertPath: String, ionAccessToken: String, ionAssetId: Long)
102
103
  private external fun nativeShutdown(ptr: Long)
103
104
  private external fun nativeDestroy(ptr: Long)
104
105
  private external fun nativeResize(ptr: Long, w: Int, h: Int)
@@ -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.
@@ -273,7 +286,12 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
273
286
  val cacertPath = extractCacert()
274
287
  rh.post {
275
288
  val b = CesiumBridgeJNI()
276
- b.init(surface, w, h, cacheDir, cacertPath)
289
+ // sqliteCacheMaxRows and taskProcessorThreads are consumed once inside
290
+ // CesiumEngine::initialize() (SqliteCache + TaskProcessor sizing).
291
+ // They must be seeded into config_ before init() calls buildEngine().
292
+ sqliteCacheMaxRows?.let { b.setSqliteCacheMaxRows(it.toInt()) }
293
+ taskProcessorThreads?.let { b.setTaskProcessorThreads(it.toInt()) }
294
+ b.init(surface, w, h, cacheDir, cacertPath, ionAccessToken, ionAssetId.toLong())
277
295
  bridge = b
278
296
  syncBridgePropsOnRenderThread(b)
279
297
 
@@ -300,12 +318,13 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
300
318
  b.setViewCorrection(q.w, q.x, q.y, q.z); pendingViewCorrection = null
301
319
  }
302
320
 
321
+ // Ion credentials were seeded at init() time for parallel tileset
322
+ // fetch; call updateIonAccessToken here only as a hot-reload safety
323
+ // net in case the token changed between bridge teardown and rebuild.
303
324
  if (ionAccessToken.isNotEmpty()) {
304
325
  b.updateIonAccessToken(ionAccessToken, ionAssetId.toLong())
305
326
  }
306
- if (ionImageryAssetId != 1.0) {
307
- b.updateImageryAssetId(ionImageryAssetId.toLong())
308
- }
327
+ b.updateImageryAssetId(ionImageryAssetId.toLong())
309
328
  b.setMsaa(msaaSampleCount.toInt())
310
329
 
311
330
  pushRateCaps()
@@ -497,8 +516,8 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
497
516
  culledScreenSpaceError?.let { b.setCulledScreenSpaceError(it) }
498
517
  enableLodTransitionPeriod?.let { b.setEnableLodTransitionPeriod(it) }
499
518
  lodTransitionLength?.let { b.setLodTransitionLength(it) }
500
- sqliteCacheMaxRows?.let { b.setSqliteCacheMaxRows(it.toInt()) }
501
- taskProcessorThreads?.let { b.setTaskProcessorThreads(it.toInt()) }
519
+ // sqliteCacheMaxRows and taskProcessorThreads are applied before init() in
520
+ // surfaceCreated; calling them here (after buildEngine) has no effect.
502
521
  minAltitudeAboveTerrain?.let { b.setMinAltitudeAboveTerrain(it.toFloat()) }
503
522
  }
504
523