react-native-cesium 1.0.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.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +12 -0
  3. package/README.md +308 -0
  4. package/ReactNativeCesium.podspec +54 -0
  5. package/android/CMakeLists.txt +146 -0
  6. package/android/build.gradle +178 -0
  7. package/android/fix-prefab.gradle +51 -0
  8. package/android/gradle.properties +5 -0
  9. package/android/src/main/AndroidManifest.xml +5 -0
  10. package/android/src/main/assets/cacert.pem +3529 -0
  11. package/android/src/main/cpp/CesiumBridgeAndroid.cpp +487 -0
  12. package/android/src/main/cpp/CesiumBridgeAndroid.h +94 -0
  13. package/android/src/main/cpp/cpp-adapter.cpp +17 -0
  14. package/android/src/main/cpp/vulkan/VulkanBackend.cpp +1781 -0
  15. package/android/src/main/cpp/vulkan/VulkanBackend.h +190 -0
  16. package/android/src/main/cpp/vulkan/shaders/sky.frag +86 -0
  17. package/android/src/main/cpp/vulkan/shaders/sky.vert +9 -0
  18. package/android/src/main/cpp/vulkan/shaders/terrain.frag +144 -0
  19. package/android/src/main/cpp/vulkan/shaders/terrain.vert +25 -0
  20. package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +84 -0
  21. package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +225 -0
  22. package/android/src/main/java/com/reactnativecesium/ReactNativeCesiumPackage.kt +28 -0
  23. package/cpp/engine/CesiumEngine.cpp +413 -0
  24. package/cpp/engine/CesiumEngine.hpp +90 -0
  25. package/cpp/engine/DeletionQueue.cpp +28 -0
  26. package/cpp/engine/DeletionQueue.hpp +28 -0
  27. package/cpp/engine/GlobeCamera.cpp +181 -0
  28. package/cpp/engine/GlobeCamera.hpp +64 -0
  29. package/cpp/engine/GltfToMesh.cpp +154 -0
  30. package/cpp/engine/GltfToMesh.hpp +20 -0
  31. package/cpp/engine/ResourcePreparer.cpp +224 -0
  32. package/cpp/engine/ResourcePreparer.hpp +96 -0
  33. package/cpp/engine/TaskProcessor.cpp +61 -0
  34. package/cpp/engine/TaskProcessor.hpp +43 -0
  35. package/cpp/engine/TileLifecycleManager.cpp +30 -0
  36. package/cpp/engine/TileLifecycleManager.hpp +25 -0
  37. package/cpp/fmt/format.h +9 -0
  38. package/cpp/renderer/IGPUBackend.hpp +22 -0
  39. package/cpp/renderer/RenderTypes.hpp +112 -0
  40. package/cpp/renderer/ShaderTypes.hpp +29 -0
  41. package/example/README.md +43 -0
  42. package/example/react-native-cesium-example.jpg +0 -0
  43. package/ios/CesiumBridge.h +55 -0
  44. package/ios/CesiumBridge.mm +393 -0
  45. package/ios/HybridCesiumView.swift +299 -0
  46. package/ios/ReactNativeCesium-umbrella.h +16 -0
  47. package/ios/metal/MetalBackend.h +82 -0
  48. package/ios/metal/MetalBackend.mm +839 -0
  49. package/ios/react_native_cesium_ensure_native.rb +12 -0
  50. package/ios/react_native_cesium_post_install.rb +30 -0
  51. package/lib/commonjs/CesiumView.js +13 -0
  52. package/lib/commonjs/CesiumView.js.map +1 -0
  53. package/lib/commonjs/index.js +13 -0
  54. package/lib/commonjs/index.js.map +1 -0
  55. package/lib/commonjs/package.json +1 -0
  56. package/lib/commonjs/specs/cesium-view.nitro.js +6 -0
  57. package/lib/commonjs/specs/cesium-view.nitro.js.map +1 -0
  58. package/lib/module/CesiumView.js +9 -0
  59. package/lib/module/CesiumView.js.map +1 -0
  60. package/lib/module/index.js +4 -0
  61. package/lib/module/index.js.map +1 -0
  62. package/lib/module/specs/cesium-view.nitro.js +4 -0
  63. package/lib/module/specs/cesium-view.nitro.js.map +1 -0
  64. package/lib/typescript/src/CesiumView.d.ts +5 -0
  65. package/lib/typescript/src/CesiumView.d.ts.map +1 -0
  66. package/lib/typescript/src/index.d.ts +3 -0
  67. package/lib/typescript/src/index.d.ts.map +1 -0
  68. package/lib/typescript/src/specs/cesium-view.nitro.d.ts +43 -0
  69. package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -0
  70. package/nitro.json +30 -0
  71. package/nitrogen/generated/.gitattributes +1 -0
  72. package/nitrogen/generated/android/ReactNativeCesium+autolinking.cmake +83 -0
  73. package/nitrogen/generated/android/ReactNativeCesium+autolinking.gradle +27 -0
  74. package/nitrogen/generated/android/ReactNativeCesiumOnLoad.cpp +58 -0
  75. package/nitrogen/generated/android/ReactNativeCesiumOnLoad.hpp +34 -0
  76. package/nitrogen/generated/android/c++/JCameraState.hpp +81 -0
  77. package/nitrogen/generated/android/c++/JCesiumMetrics.hpp +81 -0
  78. package/nitrogen/generated/android/c++/JFunc_void_CesiumMetrics.hpp +78 -0
  79. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +178 -0
  80. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +83 -0
  81. package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +92 -0
  82. package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.hpp +49 -0
  83. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CameraState.kt +56 -0
  84. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CesiumMetrics.kt +56 -0
  85. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Func_void_CesiumMetrics.kt +80 -0
  86. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +126 -0
  87. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/ReactNativeCesiumOnLoad.kt +35 -0
  88. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewManager.kt +80 -0
  89. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewStateUpdater.kt +23 -0
  90. package/nitrogen/generated/ios/ReactNativeCesium+autolinking.rb +60 -0
  91. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.cpp +57 -0
  92. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +164 -0
  93. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Umbrella.hpp +53 -0
  94. package/nitrogen/generated/ios/ReactNativeCesiumAutolinking.mm +33 -0
  95. package/nitrogen/generated/ios/ReactNativeCesiumAutolinking.swift +26 -0
  96. package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.cpp +11 -0
  97. package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +157 -0
  98. package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +167 -0
  99. package/nitrogen/generated/ios/swift/CameraState.swift +59 -0
  100. package/nitrogen/generated/ios/swift/CesiumMetrics.swift +59 -0
  101. package/nitrogen/generated/ios/swift/Func_void_CameraState.swift +46 -0
  102. package/nitrogen/generated/ios/swift/Func_void_CesiumMetrics.swift +46 -0
  103. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  104. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +65 -0
  105. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +306 -0
  106. package/nitrogen/generated/shared/c++/CameraState.hpp +107 -0
  107. package/nitrogen/generated/shared/c++/CesiumMetrics.hpp +107 -0
  108. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +42 -0
  109. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +90 -0
  110. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +182 -0
  111. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +122 -0
  112. package/nitrogen/generated/shared/json/CesiumViewConfig.json +19 -0
  113. package/package.json +140 -0
  114. package/react-native.config.js +16 -0
  115. package/scripts/cesium/build.mjs +901 -0
  116. package/scripts/cesium/clean.mjs +35 -0
  117. package/scripts/cesium/clone.mjs +32 -0
  118. package/scripts/cesium/ensure-native.mjs +116 -0
  119. package/scripts/cesium/vcpkg-ports/zstd/fix-emscripten-and-clang-cl.patch +31 -0
  120. package/scripts/cesium/vcpkg-ports/zstd/fix-windows-rc-compile.patch +13 -0
  121. package/scripts/cesium/vcpkg-ports/zstd/no-static-suffix.patch +13 -0
  122. package/scripts/cesium/vcpkg-ports/zstd/portfile.cmake +77 -0
  123. package/scripts/cesium/vcpkg-ports/zstd/usage +4 -0
  124. package/scripts/cesium/vcpkg-ports/zstd/vcpkg.json +23 -0
  125. package/scripts/cesium/vcpkg-triplets/arm64-ios-simulator.cmake +10 -0
  126. package/scripts/cesium/vcpkg-triplets/arm64-ios.cmake +14 -0
  127. package/src/CesiumView.tsx +13 -0
  128. package/src/index.ts +7 -0
  129. package/src/specs/cesium-view.nitro.ts +51 -0
@@ -0,0 +1,225 @@
1
+ package com.margelo.nitro.reactnativecesium
2
+
3
+ import android.content.Context
4
+ import android.view.Choreographer
5
+ import android.view.SurfaceHolder
6
+ import android.view.SurfaceView
7
+ import android.view.View
8
+ import com.margelo.nitro.core.Promise
9
+ import java.io.File
10
+ import java.io.FileOutputStream
11
+
12
+ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec() {
13
+ private val surfaceView = SurfaceView(appContext)
14
+ private var bridge: CesiumBridgeJNI? = null
15
+ private var metricsFrameCounter = 0
16
+ private var idleProbeAccumulator = 0.0
17
+ private var usingLowRefreshRate = false
18
+ private var lastFrameTimeNanos = 0L
19
+ private var lastPushedCamera: CameraState? = null
20
+ private var renderLoopActive = false
21
+
22
+ override var ionAccessToken: String = ""
23
+ set(value) {
24
+ field = value
25
+ bridge?.updateIonAccessToken(value, ionAssetId.toLong())
26
+ }
27
+
28
+ override var ionAssetId: Double = 1.0
29
+ set(value) {
30
+ field = value
31
+ bridge?.updateIonAccessToken(ionAccessToken, value.toLong())
32
+ }
33
+
34
+ override var initialCamera: CameraState = CameraState(
35
+ latitude = 46.15,
36
+ longitude = 7.35,
37
+ altitude = 12000.0,
38
+ heading = 129.0,
39
+ pitch = -45.0,
40
+ roll = 0.0,
41
+ verticalFovDeg = 60.0,
42
+ )
43
+
44
+ override var pauseRendering: Boolean = false
45
+ set(value) {
46
+ field = value
47
+ if (!value) {
48
+ bridge?.markNeedsRender()
49
+ scheduleNextFrame()
50
+ }
51
+ }
52
+
53
+ override var maximumScreenSpaceError: Double = 32.0
54
+ set(value) { field = value; bridge?.setMaxSSE(value) }
55
+
56
+ override var maximumSimultaneousTileLoads: Double = 12.0
57
+ set(value) { field = value; bridge?.setMaxSimLoads(value.toInt()) }
58
+
59
+ override var loadingDescendantLimit: Double = 20.0
60
+ set(value) { field = value; bridge?.setLoadDescLim(value.toInt()) }
61
+
62
+ override var msaaSampleCount: Double = 1.0
63
+ set(value) { field = value; bridge?.setMsaa(value.toInt()) }
64
+
65
+ override var ionImageryAssetId: Double = 1.0
66
+ set(value) { field = value; bridge?.updateImageryAssetId(value.toLong()) }
67
+
68
+ override var onMetrics: ((metrics: CesiumMetrics) -> Unit)? = null
69
+
70
+ override val view: View get() = surfaceView
71
+
72
+ private val frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
73
+ renderLoopActive = false
74
+ if (pauseRendering || bridge == null) return@FrameCallback
75
+
76
+ val dt = if (lastFrameTimeNanos > 0) {
77
+ (frameTimeNanos - lastFrameTimeNanos).coerceAtLeast(1_000_000L) / 1_000_000_000.0
78
+ } else {
79
+ 1.0 / 60.0
80
+ }
81
+ lastFrameTimeNanos = frameTimeNanos
82
+
83
+ val shouldRender = bridge?.shouldRenderNextFrame() ?: false
84
+ if (shouldRender) {
85
+ usingLowRefreshRate = false
86
+ idleProbeAccumulator = 0.0
87
+ bridge?.renderFrame(dt)
88
+ } else {
89
+ usingLowRefreshRate = true
90
+ idleProbeAccumulator += dt
91
+ if (idleProbeAccumulator >= 0.25) {
92
+ idleProbeAccumulator = 0.0
93
+ bridge?.markNeedsRender()
94
+ }
95
+ }
96
+
97
+ metricsFrameCounter++
98
+ if (metricsFrameCounter >= 20) {
99
+ metricsFrameCounter = 0
100
+ val b = bridge ?: return@FrameCallback
101
+ val cb = onMetrics ?: return@FrameCallback
102
+ cb(CesiumMetrics(
103
+ fps = b.getMetricsFps(),
104
+ tilesRendered = b.getMetricsTilesRendered().toDouble(),
105
+ tilesLoading = b.getMetricsTilesLoading().toDouble(),
106
+ tilesVisited = b.getMetricsTilesVisited().toDouble(),
107
+ ionTokenConfigured = b.getMetricsIonTokenConfigured(),
108
+ tilesetReady = b.getMetricsTilesetReady(),
109
+ creditsPlainText = b.getMetricsCreditsPlainText(),
110
+ ))
111
+ }
112
+
113
+ scheduleNextFrame()
114
+ }
115
+
116
+ private val surfaceCallback = object : SurfaceHolder.Callback {
117
+ override fun surfaceCreated(holder: SurfaceHolder) {
118
+ val surface = holder.surface
119
+ val w = surfaceView.width
120
+ val h = surfaceView.height
121
+ if (w <= 0 || h <= 0) return
122
+
123
+ val b = CesiumBridgeJNI()
124
+ val cacheDir = appContext.cacheDir.absolutePath
125
+ val cacertPath = extractCacert()
126
+ b.init(surface, w, h, cacheDir, cacertPath)
127
+ bridge = b
128
+
129
+ syncPropsTobridge()
130
+ if (ionAccessToken.isNotEmpty()) {
131
+ b.updateIonAccessToken(ionAccessToken, ionAssetId.toLong())
132
+ }
133
+ pushCameraIfChanged(initialCamera)
134
+ if (ionImageryAssetId != 1.0) {
135
+ b.updateImageryAssetId(ionImageryAssetId.toLong())
136
+ }
137
+ b.setMsaa(msaaSampleCount.toInt())
138
+
139
+ lastFrameTimeNanos = 0L
140
+ scheduleNextFrame()
141
+ }
142
+
143
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
144
+ bridge?.resize(width, height)
145
+ bridge?.markNeedsRender()
146
+ }
147
+
148
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
149
+ bridge?.shutdown()
150
+ bridge?.destroy()
151
+ bridge = null
152
+ }
153
+ }
154
+
155
+ init {
156
+ surfaceView.holder.addCallback(surfaceCallback)
157
+ }
158
+
159
+ override fun getCameraState(): Promise<CameraState> {
160
+ val b = bridge ?: return Promise.resolved(initialCamera)
161
+ return Promise.resolved(CameraState(
162
+ latitude = b.getCameraLat(),
163
+ longitude = b.getCameraLon(),
164
+ altitude = b.getCameraAlt(),
165
+ heading = b.getCameraHeading(),
166
+ pitch = b.getCameraPitch(),
167
+ roll = b.getCameraRoll(),
168
+ verticalFovDeg = b.getVerticalFovDeg(),
169
+ ))
170
+ }
171
+
172
+ override fun setCamera(camera: CameraState) {
173
+ initialCamera = camera
174
+ pushCameraIfChanged(camera)
175
+ }
176
+
177
+ private fun pushCameraIfChanged(camera: CameraState) {
178
+ val b = bridge ?: return
179
+ val last = lastPushedCamera
180
+ if (last != null &&
181
+ last.latitude == camera.latitude &&
182
+ last.longitude == camera.longitude &&
183
+ last.altitude == camera.altitude &&
184
+ last.heading == camera.heading &&
185
+ last.pitch == camera.pitch &&
186
+ last.roll == camera.roll &&
187
+ last.verticalFovDeg == camera.verticalFovDeg) {
188
+ return
189
+ }
190
+ lastPushedCamera = camera
191
+ b.updateCamera(camera.latitude, camera.longitude, camera.altitude,
192
+ camera.heading, camera.pitch, camera.roll)
193
+ b.setVerticalFovDeg(camera.verticalFovDeg)
194
+ }
195
+
196
+ private fun syncPropsTobridge() {
197
+ bridge?.setMaxSSE(maximumScreenSpaceError)
198
+ bridge?.setMaxSimLoads(maximumSimultaneousTileLoads.toInt())
199
+ bridge?.setLoadDescLim(loadingDescendantLimit.toInt())
200
+ }
201
+
202
+ private fun scheduleNextFrame() {
203
+ if (renderLoopActive || pauseRendering || bridge == null) return
204
+ renderLoopActive = true
205
+ Choreographer.getInstance().postFrameCallback(frameCallback)
206
+ }
207
+
208
+ private fun extractCacert(): String {
209
+ val outFile = File(appContext.cacheDir, "cacert.pem")
210
+ try {
211
+ appContext.assets.open("cacert.pem").use { input ->
212
+ // Re-extract if the cached copy is missing or a different size to the
213
+ // bundled asset (handles the file being updated across app versions).
214
+ val assetSize = input.available().toLong()
215
+ if (!outFile.exists() || outFile.length() != assetSize) {
216
+ FileOutputStream(outFile).use { output -> input.copyTo(output) }
217
+ }
218
+ }
219
+ } catch (_: Exception) {
220
+ // cacert.pem not bundled as asset — libcurl will use system certs
221
+ return if (outFile.exists()) outFile.absolutePath else ""
222
+ }
223
+ return outFile.absolutePath
224
+ }
225
+ }
@@ -0,0 +1,28 @@
1
+ package com.reactnativecesium;
2
+
3
+ import com.facebook.react.bridge.NativeModule;
4
+ import com.facebook.react.bridge.ReactApplicationContext;
5
+ import com.facebook.react.module.model.ReactModuleInfoProvider;
6
+ import com.facebook.react.uimanager.ViewManager;
7
+ import com.facebook.react.BaseReactPackage;
8
+ import com.margelo.nitro.reactnativecesium.ReactNativeCesiumOnLoad;
9
+ import com.margelo.nitro.reactnativecesium.views.HybridCesiumViewManager;
10
+
11
+
12
+ public class ReactNativeCesiumPackage : BaseReactPackage() {
13
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
14
+
15
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { emptyMap() }
16
+
17
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
18
+ val viewManagers = ArrayList<ViewManager<*, *>>()
19
+ viewManagers.add(HybridCesiumViewManager())
20
+ return viewManagers
21
+ }
22
+
23
+ companion object {
24
+ init {
25
+ ReactNativeCesiumOnLoad.initializeNative();
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,413 @@
1
+ #ifndef NDEBUG
2
+ # define NDEBUG
3
+ # define CESIUM_ENGINE_UNDEF_NDEBUG
4
+ #endif
5
+
6
+ #include "CesiumEngine.hpp"
7
+
8
+ #include <Cesium3DTilesContent/registerAllTileContentTypes.h>
9
+ #include <Cesium3DTilesSelection/BoundingVolume.h>
10
+ #include <Cesium3DTilesSelection/Tile.h>
11
+ #include <Cesium3DTilesSelection/Tileset.h>
12
+ #include <Cesium3DTilesSelection/TilesetExternals.h>
13
+ #include <Cesium3DTilesSelection/TilesetOptions.h>
14
+ #include <CesiumGeospatial/BoundingRegion.h>
15
+ #include <CesiumGeospatial/GlobeRectangle.h>
16
+ #include <CesiumAsync/CachingAssetAccessor.h>
17
+ #include <CesiumAsync/SqliteCache.h>
18
+ #include <CesiumCurl/CurlAssetAccessor.h>
19
+ #include <CesiumRasterOverlays/IonRasterOverlay.h>
20
+ #include <CesiumUtility/CreditSystem.h>
21
+ #include <CesiumUtility/IntrusivePointer.h>
22
+
23
+ #ifdef CESIUM_ENGINE_UNDEF_NDEBUG
24
+ # undef NDEBUG
25
+ # undef CESIUM_ENGINE_UNDEF_NDEBUG
26
+ #endif
27
+
28
+ #include <spdlog/spdlog.h>
29
+ #include <glm/gtc/matrix_inverse.hpp>
30
+ #include <glm/gtc/matrix_transform.hpp>
31
+
32
+ #include <algorithm>
33
+ #include <cmath>
34
+
35
+ namespace reactnativecesium {
36
+
37
+ CesiumEngine::CesiumEngine()
38
+ : taskProcessor_(std::make_shared<TaskProcessor>(4)),
39
+ asyncSystem_(taskProcessor_) {
40
+ Cesium3DTilesContent::registerAllTileContentTypes();
41
+ }
42
+
43
+ CesiumEngine::~CesiumEngine() { shutdown(); }
44
+
45
+ bool CesiumEngine::tilesetOptionsMatch(const EngineConfig& a,
46
+ const EngineConfig& b) const {
47
+ return a.maximumScreenSpaceError == b.maximumScreenSpaceError &&
48
+ a.maximumSimultaneousTileLoads == b.maximumSimultaneousTileLoads &&
49
+ a.loadingDescendantLimit == b.loadingDescendantLimit;
50
+ }
51
+
52
+ void CesiumEngine::buildEllipsoidMesh() {
53
+ // Tessellate a WGS84 ellipsoid slightly inset so terrain tiles (at true
54
+ // surface height) reliably overdraw it via the reversed-Z depth test.
55
+ constexpr int nLon = 128;
56
+ constexpr int nLat = 64;
57
+
58
+ constexpr double a_full = 6378137.0;
59
+ constexpr double b_full = 6356752.31424518;
60
+ constexpr double inset = 20.0;
61
+ const double a = a_full - inset;
62
+ const double b = b_full - inset;
63
+
64
+ ellipsoidPositions_.clear();
65
+ ellipsoidIndices_.clear();
66
+ ellipsoidPositions_.reserve(static_cast<size_t>((nLat + 1) * nLon));
67
+ ellipsoidIndices_.reserve(static_cast<size_t>(nLat * nLon * 6));
68
+
69
+ for (int lat = 0; lat <= nLat; ++lat) {
70
+ const double phi = M_PI * (static_cast<double>(lat) / nLat - 0.5);
71
+ const double cosPhi = std::cos(phi);
72
+ const double sinPhi = std::sin(phi);
73
+ for (int lon = 0; lon < nLon; ++lon) {
74
+ const double theta = 2.0 * M_PI * static_cast<double>(lon) / nLon;
75
+ ellipsoidPositions_.push_back({
76
+ a * cosPhi * std::cos(theta),
77
+ a * cosPhi * std::sin(theta),
78
+ b * sinPhi
79
+ });
80
+ }
81
+ }
82
+
83
+ for (int lat = 0; lat < nLat; ++lat) {
84
+ for (int lon = 0; lon < nLon; ++lon) {
85
+ const uint32_t v00 = static_cast<uint32_t>(lat * nLon + lon);
86
+ const uint32_t v10 = static_cast<uint32_t>(lat * nLon + (lon + 1) % nLon);
87
+ const uint32_t v01 = static_cast<uint32_t>((lat + 1) * nLon + lon);
88
+ const uint32_t v11 = static_cast<uint32_t>((lat + 1) * nLon + (lon + 1) % nLon);
89
+ ellipsoidIndices_.push_back(v00); ellipsoidIndices_.push_back(v10); ellipsoidIndices_.push_back(v11);
90
+ ellipsoidIndices_.push_back(v00); ellipsoidIndices_.push_back(v11); ellipsoidIndices_.push_back(v01);
91
+ }
92
+ }
93
+ }
94
+
95
+ // Append the fallback ellipsoid as a draw.
96
+ // RTC convention for the fallback: the tile's "RTC centre" is the camera
97
+ // position itself, so local positions == old camera-relative positions. The
98
+ // per-draw MVP is therefore the same rotation-only VP used before RTC was
99
+ // introduced, keeping the fallback on the same code path.
100
+ void CesiumEngine::appendEllipsoidDraws(FrameResult& result) const {
101
+ if (ellipsoidPositions_.empty() || ellipsoidIndices_.empty()) return;
102
+
103
+ const glm::dvec3 cameraPos = camera_.getECEFPosition();
104
+ const size_t baseVertex = result.localPositions.size() / 3;
105
+ const uint32_t indexByteOff =
106
+ static_cast<uint32_t>(result.indices.size() * sizeof(uint32_t));
107
+ const size_t vertexCount = ellipsoidPositions_.size();
108
+
109
+ result.localPositions.resize(result.localPositions.size() + vertexCount * 3);
110
+ result.altitudes.resize(result.altitudes.size() + vertexCount);
111
+ result.uvs.resize(result.uvs.size() + vertexCount * 2);
112
+
113
+ float* posOut = result.localPositions.data() + baseVertex * 3;
114
+ float* altOut = result.altitudes.data() + baseVertex;
115
+ float* uvOut = result.uvs.data() + baseVertex * 2;
116
+
117
+ for (const auto& posEcef : ellipsoidPositions_) {
118
+ // Local position = ECEF - camera (same as old eye-relative, since camera is the RTC centre).
119
+ const glm::dvec3 local = posEcef - cameraPos;
120
+ *posOut++ = static_cast<float>(local.x);
121
+ *posOut++ = static_cast<float>(local.y);
122
+ *posOut++ = static_cast<float>(local.z);
123
+ // Fallback geoid should represent land by default when terrain tiles are not
124
+ // loaded yet, so keep altitude at sea level for hypsometric land colouring.
125
+ *altOut++ = 0.0f;
126
+ *uvOut++ = 0.5f;
127
+ *uvOut++ = 0.5f;
128
+ }
129
+
130
+ const size_t indexOffset = result.indices.size();
131
+ result.indices.resize(indexOffset + ellipsoidIndices_.size());
132
+ uint32_t* indexOut = result.indices.data() + indexOffset;
133
+ for (uint32_t idx : ellipsoidIndices_) {
134
+ *indexOut++ = static_cast<uint32_t>(baseVertex) + idx;
135
+ }
136
+
137
+ DrawPrimitive draw;
138
+ draw.indexByteOffset = indexByteOff;
139
+ draw.indexCount = static_cast<uint32_t>(ellipsoidIndices_.size());
140
+ draw.hasUVs = false;
141
+ draw.isEllipsoidFallback = true;
142
+ draw.overlayTexture = nullptr;
143
+ // Ellipsoid RTC centre = camera → local pos = eye-relative → use the
144
+ // rotation-only VP (identical to the pre-RTC vertex transform).
145
+ draw.mvpMatrix = result.vpMatrix;
146
+ draw.rtcCenterEcef = result.cameraEcef;
147
+ result.draws.push_back(draw);
148
+ }
149
+
150
+ void CesiumEngine::initialize(const EngineConfig& config) {
151
+ config_ = config;
152
+
153
+ auto logger = spdlog::default_logger();
154
+
155
+ CesiumCurl::CurlAssetAccessorOptions curlOpts;
156
+ if (!config.tlsCaBundlePath.empty()) {
157
+ curlOpts.certificateFile = config.tlsCaBundlePath;
158
+ }
159
+ auto curlAccessor = std::make_shared<CesiumCurl::CurlAssetAccessor>(curlOpts);
160
+
161
+ if (!config.cacheDatabasePath.empty()) {
162
+ auto cache = std::make_shared<CesiumAsync::SqliteCache>(
163
+ logger, config.cacheDatabasePath, 4096);
164
+ assetAccessor_ = std::make_shared<CesiumAsync::CachingAssetAccessor>(
165
+ logger, curlAccessor, cache);
166
+ } else {
167
+ assetAccessor_ = curlAccessor;
168
+ }
169
+
170
+ resourcePreparer_ = std::make_shared<ResourcePreparer>(lifecycle_);
171
+
172
+ if (!creditSystem_) {
173
+ creditSystem_ = std::make_shared<CesiumUtility::CreditSystem>();
174
+ }
175
+
176
+ buildEllipsoidMesh();
177
+ createTileset(config.ionAccessToken, config.ionAssetId);
178
+ }
179
+
180
+ void CesiumEngine::shutdown() {
181
+ destroyTileset();
182
+ creditSystem_.reset();
183
+ }
184
+
185
+ void CesiumEngine::updateConfig(const EngineConfig& config) {
186
+ const bool needRebuild =
187
+ config.ionAccessToken != config_.ionAccessToken ||
188
+ config.ionAssetId != config_.ionAssetId ||
189
+ !tilesetOptionsMatch(config, config_);
190
+
191
+ config_ = config;
192
+
193
+ if (!needRebuild) return;
194
+
195
+ destroyTileset();
196
+ if (!config_.ionAccessToken.empty()) {
197
+ createTileset(config_.ionAccessToken, config_.ionAssetId, currentImageryAssetId_);
198
+ }
199
+ }
200
+
201
+ void CesiumEngine::createTileset(const std::string& token,
202
+ int64_t assetId,
203
+ int64_t imageryAssetId) {
204
+ if (token.empty()) return;
205
+
206
+ if (!creditSystem_) {
207
+ creditSystem_ = std::make_shared<CesiumUtility::CreditSystem>();
208
+ }
209
+
210
+ auto logger = spdlog::default_logger();
211
+
212
+ Cesium3DTilesSelection::TilesetExternals externals{
213
+ assetAccessor_, resourcePreparer_, asyncSystem_, creditSystem_,
214
+ logger, nullptr};
215
+
216
+ Cesium3DTilesSelection::TilesetOptions opts;
217
+ opts.maximumCachedBytes = 256 * 1024 * 1024;
218
+ opts.maximumSimultaneousTileLoads =
219
+ static_cast<uint32_t>(std::max(0, config_.maximumSimultaneousTileLoads));
220
+ opts.loadingDescendantLimit =
221
+ static_cast<uint32_t>(std::max(1, config_.loadingDescendantLimit));
222
+ opts.maximumScreenSpaceError =
223
+ std::max(1.0, config_.maximumScreenSpaceError);
224
+ opts.preloadAncestors = true;
225
+ opts.preloadSiblings = true;
226
+ opts.forbidHoles = true;
227
+ opts.contentOptions.enableWaterMask = true;
228
+
229
+ tileset_ = std::make_unique<Cesium3DTilesSelection::Tileset>(
230
+ externals, assetId, token, opts);
231
+
232
+ if (imageryAssetId != 1) {
233
+ CesiumUtility::IntrusivePointer<CesiumRasterOverlays::RasterOverlay> ov =
234
+ new CesiumRasterOverlays::IonRasterOverlay("imagery", imageryAssetId, token);
235
+ tileset_->getOverlays().add(ov);
236
+ }
237
+ }
238
+
239
+ void CesiumEngine::destroyTileset() {
240
+ tileset_.reset();
241
+ taskProcessor_->waitUntilIdle();
242
+ }
243
+
244
+ void CesiumEngine::setImageryAssetId(int64_t assetId) {
245
+ if (config_.ionAccessToken.empty()) return;
246
+ currentImageryAssetId_ = assetId;
247
+ destroyTileset();
248
+ createTileset(config_.ionAccessToken, config_.ionAssetId, assetId);
249
+ }
250
+
251
+ void CesiumEngine::updateFrame(double w, double h, FrameResult& result) {
252
+ result.localPositions.clear();
253
+ result.altitudes.clear();
254
+ result.uvs.clear();
255
+ result.indices.clear();
256
+ result.draws.clear();
257
+ result.creditHtmlLines.clear();
258
+
259
+ result.ionTokenConfigured = !config_.ionAccessToken.empty();
260
+ result.tilesetActive = (tileset_ != nullptr);
261
+ result.verticalFovDeg = camera_.getVerticalFovDegrees();
262
+
263
+ const glm::dvec3 cameraPos = camera_.getECEFPosition();
264
+ result.cameraEcef = glm::vec3(cameraPos);
265
+
266
+ // Rotation-only VP (float) — used by the sky shader and the ellipsoid fallback.
267
+ result.vpMatrix = camera_.computeVPMatrix(w, h);
268
+ result.invVP = glm::inverse(result.vpMatrix);
269
+
270
+ // Double-precision rotation-only VP — used to build per-tile MVP matrices
271
+ // so the camera↔tile-centre translation is resolved in double before the
272
+ // final cast to float32.
273
+ const glm::dmat4 vpDouble = camera_.computeVPMatrixDouble(w, h);
274
+
275
+ if (!tileset_) {
276
+ appendEllipsoidDraws(result);
277
+ lifecycle_.advanceFrame();
278
+ return;
279
+ }
280
+
281
+ result.localPositions.reserve(512 * 1024);
282
+ result.altitudes.reserve(512 * 1024 / 3);
283
+ result.uvs.reserve(512 * 1024 * 2);
284
+ result.indices.reserve(3 * 1024 * 1024);
285
+
286
+ asyncSystem_.dispatchMainThreadTasks();
287
+
288
+ // Ellipsoid fallback is appended first so real terrain tiles (drawn later)
289
+ // overdraw it via the reversed-Z depth test.
290
+ appendEllipsoidDraws(result);
291
+
292
+ const auto viewState = camera_.computeViewState(w, h);
293
+ const auto& updateResult = tileset_->updateView({viewState});
294
+
295
+ if (creditSystem_) {
296
+ const CesiumUtility::CreditsSnapshot& snap =
297
+ creditSystem_->getSnapshot(CesiumUtility::CreditFilteringMode::UniqueHtml);
298
+ for (const auto& c : snap.currentCredits) {
299
+ const std::string& html = creditSystem_->getHtml(c);
300
+ if (html.empty()) continue;
301
+ if (html.find("Error: Invalid Credit") != std::string::npos) continue;
302
+ result.creditHtmlLines.push_back(html);
303
+ }
304
+ }
305
+
306
+ for (const auto& tile : updateResult.tilesToRenderThisFrame) {
307
+ if (!tile) continue;
308
+
309
+ const auto* renderContent = tile->getContent().getRenderContent();
310
+ if (!renderContent) continue;
311
+
312
+ const auto* res = static_cast<const TileGPUResources*>(
313
+ renderContent->getRenderResources());
314
+ if (!res) continue;
315
+
316
+ lifecycle_.stampTileUsed(const_cast<TileGPUResources*>(res));
317
+
318
+ glm::vec4 wmTileBounds(0.0f);
319
+ const auto& bv = tile->getBoundingVolume();
320
+ const CesiumGeospatial::BoundingRegion* bRegion =
321
+ Cesium3DTilesSelection::getBoundingRegionFromBoundingVolume(bv);
322
+ if (bRegion) {
323
+ const auto& rect = bRegion->getRectangle();
324
+ wmTileBounds = glm::vec4(
325
+ static_cast<float>(rect.getWest()),
326
+ static_cast<float>(rect.getSouth()),
327
+ static_cast<float>(rect.getEast()),
328
+ static_cast<float>(rect.getNorth()));
329
+ }
330
+
331
+ for (const auto& prim : res->primitives) {
332
+ if (prim.indices.empty() || prim.localPositions.empty()) continue;
333
+
334
+ const size_t baseVertex = result.localPositions.size() / 3;
335
+ const uint32_t indexByteOff =
336
+ static_cast<uint32_t>(result.indices.size() * sizeof(uint32_t));
337
+ const size_t vertexCount = prim.localPositions.size();
338
+ const bool hasPrimUVs =
339
+ !prim.uvs.empty() && prim.uvs.size() == vertexCount;
340
+
341
+ // ── RTC: copy tile-local positions & altitudes ────────────────────────
342
+ // Positions are already relative to prim.rtcCenter (small float values).
343
+ // Altitudes are computed in double in GltfToMesh — sub-millimetre precision.
344
+ result.localPositions.resize(result.localPositions.size() + vertexCount * 3);
345
+ result.altitudes.resize(result.altitudes.size() + vertexCount);
346
+ result.uvs.resize(result.uvs.size() + vertexCount * 2);
347
+
348
+ float* posOut = result.localPositions.data() + baseVertex * 3;
349
+ float* altOut = result.altitudes.data() + baseVertex;
350
+ float* uvOut = result.uvs.data() + baseVertex * 2;
351
+
352
+ for (size_t vi = 0; vi < vertexCount; ++vi) {
353
+ const glm::vec3& lp = prim.localPositions[vi];
354
+ *posOut++ = lp.x;
355
+ *posOut++ = lp.y;
356
+ *posOut++ = lp.z;
357
+ *altOut++ = prim.altitudes.empty() ? 0.0f : prim.altitudes[vi];
358
+ if (hasPrimUVs) {
359
+ *uvOut++ = prim.uvs[vi].x;
360
+ *uvOut++ = prim.uvs[vi].y;
361
+ } else {
362
+ *uvOut++ = 0.5f;
363
+ *uvOut++ = 0.5f;
364
+ }
365
+ }
366
+
367
+ for (uint32_t idx : prim.indices) {
368
+ result.indices.push_back(static_cast<uint32_t>(baseVertex) + idx);
369
+ }
370
+
371
+ // ── RTC: per-tile MVP matrix (double → float) ─────────────────────────
372
+ // Compute the camera-relative offset of the tile's RTC centre in double
373
+ // and bake it into the MVP matrix before casting to float32. This means
374
+ // the large camera↔tile translation is resolved with double precision;
375
+ // the vertex shader only ever adds small tile-local offsets on top of it.
376
+ const glm::dvec3 rtcCamRel =
377
+ prim.rtcCenter - glm::dvec3(cameraPos);
378
+ const glm::dmat4 translateD =
379
+ glm::translate(glm::dmat4(1.0), rtcCamRel);
380
+ const glm::mat4 perTileMVP =
381
+ glm::mat4(vpDouble * translateD);
382
+
383
+ DrawPrimitive draw;
384
+ draw.indexByteOffset = indexByteOff;
385
+ draw.indexCount = static_cast<uint32_t>(prim.indices.size());
386
+ draw.hasUVs = hasPrimUVs;
387
+ draw.overlayTexture = res->overlayTexture;
388
+ draw.overlayTranslation = res->overlayTranslation;
389
+ draw.overlayScale = res->overlayScale;
390
+ draw.waterMaskTexture = res->waterMaskTexture;
391
+ draw.isOnlyWater = res->isOnlyWater;
392
+ draw.wmTileBounds = wmTileBounds;
393
+ draw.wmTranslation = res->wmTranslation;
394
+ draw.wmScale = res->wmScale;
395
+ draw.mvpMatrix = perTileMVP;
396
+ draw.rtcCenterEcef = glm::vec3(prim.rtcCenter);
397
+ result.draws.push_back(draw);
398
+ }
399
+ }
400
+
401
+ result.tilesRendered = static_cast<int>(updateResult.tilesToRenderThisFrame.size());
402
+ result.tilesLoading = updateResult.workerThreadTileLoadQueueLength +
403
+ updateResult.mainThreadTileLoadQueueLength;
404
+ result.tilesVisited = static_cast<int>(updateResult.tilesVisited);
405
+ const auto params = camera_.getParams();
406
+ result.cameraLat = params.latitude;
407
+ result.cameraLon = params.longitude;
408
+ result.cameraAlt = params.altitude;
409
+
410
+ lifecycle_.advanceFrame();
411
+ }
412
+
413
+ } // namespace reactnativecesium