react-native-cesium 1.2.3 → 1.2.4
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 +100 -27
- package/android/CMakeLists.txt +1 -2
- package/android/src/main/cpp/CesiumBridgeAndroid.cpp +264 -130
- package/android/src/main/cpp/CesiumBridgeAndroid.h +70 -27
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +68 -44
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +160 -187
- package/cpp/engine/CameraIntegrator.cpp +374 -0
- package/cpp/engine/CameraIntegrator.hpp +146 -0
- package/cpp/engine/EngineTunables.hpp +78 -31
- package/cpp/engine/GlobeCamera.cpp +17 -6
- package/cpp/engine/GlobeCamera.hpp +11 -8
- package/cpp/engine/test/CameraIntegratorTest.cpp +205 -0
- package/cpp/engine/test/build_and_run.sh +27 -0
- package/example/README.md +1 -1
- package/ios/CesiumBridge.h +53 -28
- package/ios/CesiumBridge.mm +172 -86
- package/ios/HybridCesiumView.swift +213 -145
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts +77 -13
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +90 -9
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +19 -3
- package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +20 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +57 -3
- package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +9 -9
- package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +82 -9
- package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +25 -0
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +14 -3
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +210 -16
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +19 -3
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +20 -4
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +55 -0
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +5 -0
- package/nitrogen/generated/shared/json/CesiumViewConfig.json +5 -0
- package/package.json +1 -1
- package/src/specs/cesium-view.nitro.ts +93 -14
- package/cpp/engine/CameraSmoother.cpp +0 -68
- package/cpp/engine/CameraSmoother.hpp +0 -28
- package/cpp/engine/CameraTargetState.cpp +0 -205
- package/cpp/engine/CameraTargetState.hpp +0 -126
|
@@ -4,6 +4,7 @@ import android.content.Context
|
|
|
4
4
|
import android.os.Handler
|
|
5
5
|
import android.os.HandlerThread
|
|
6
6
|
import android.os.Process
|
|
7
|
+
import android.os.SystemClock
|
|
7
8
|
import android.util.Log
|
|
8
9
|
import android.view.Choreographer
|
|
9
10
|
import android.view.SurfaceHolder
|
|
@@ -16,34 +17,39 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|
|
16
17
|
|
|
17
18
|
class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec() {
|
|
18
19
|
private val surfaceView = SurfaceView(appContext)
|
|
20
|
+
|
|
19
21
|
/// Bridge is only ever read on the render thread. The UI thread posts
|
|
20
|
-
/// requests via `renderHandler`. Marked volatile so
|
|
21
|
-
/// (`bridge != null`)
|
|
22
|
-
///
|
|
22
|
+
/// requests via `renderHandler`. Marked volatile so observers on the UI
|
|
23
|
+
/// thread (`bridge != null`) see the latest reference; real work is always
|
|
24
|
+
/// serialized through the render thread for config-style writes, and
|
|
25
|
+
/// goes directly into CameraIntegrator (with its own mutex) for camera
|
|
26
|
+
/// demand writes.
|
|
23
27
|
@Volatile private var bridge: CesiumBridgeJNI? = null
|
|
24
28
|
private var metricsFrameCounter = 0
|
|
25
29
|
private var idleProbeAccumulator = 0.0
|
|
26
|
-
private var lastFrameTimeNanos = 0L
|
|
27
|
-
/// Last-pushed camera state — read/written on the render thread (via
|
|
28
|
-
/// `renderHandler.post`), never directly on the UI thread.
|
|
29
|
-
private var lastPushedCamera: CameraState? = null
|
|
30
|
-
private var lastPushedViewCorrection: Quaternion? = null
|
|
31
|
-
/// Camera state requested by `setCamera` before the bridge was ready.
|
|
32
|
-
/// Updated on the UI thread, drained on the render thread.
|
|
33
|
-
@Volatile private var pendingRuntimeCamera: CameraState? = null
|
|
34
|
-
@Volatile private var pendingRuntimeViewCorrection: Quaternion? = null
|
|
35
|
-
/// True while a Choreographer frame callback is pending. Mutated on the UI
|
|
36
|
-
/// thread inside the callback / scheduleNextFrame.
|
|
37
30
|
private var renderLoopActive = false
|
|
38
|
-
/// Drops Choreographer ticks while the render thread is still finishing the
|
|
39
|
-
/// previous frame. Set on UI thread when posting; cleared on render thread
|
|
40
|
-
/// after the bridge call completes.
|
|
41
31
|
private val renderInFlight = AtomicBoolean(false)
|
|
42
32
|
|
|
43
|
-
///
|
|
44
|
-
///
|
|
45
|
-
///
|
|
46
|
-
///
|
|
33
|
+
/// Set in `surfaceDestroyed` before any teardown work begins. Render-thread
|
|
34
|
+
/// callbacks and choreographer frame ticks check this flag and bail out so
|
|
35
|
+
/// they don't try to emit `onMetrics` into a JS dispatcher that may already
|
|
36
|
+
/// have been torn down by fast refresh / view unmount. Volatile so the
|
|
37
|
+
/// render thread sees the write made on the UI thread.
|
|
38
|
+
@Volatile private var isShutdown = false
|
|
39
|
+
|
|
40
|
+
// Demand values that arrived before the bridge was created. Drained once
|
|
41
|
+
// in surfaceCreated after the seed teleport.
|
|
42
|
+
@Volatile private var pendingTeleport: CameraState? = null
|
|
43
|
+
@Volatile private var pendingLat: Double? = null
|
|
44
|
+
@Volatile private var pendingLon: Double? = null
|
|
45
|
+
@Volatile private var pendingAltitude: Double? = null
|
|
46
|
+
@Volatile private var pendingHeading: Double? = null
|
|
47
|
+
@Volatile private var pendingPitch: Double? = null
|
|
48
|
+
@Volatile private var pendingRoll: Double? = null
|
|
49
|
+
@Volatile private var pendingVfov: Double? = null
|
|
50
|
+
@Volatile private var pendingViewCorrection: Quaternion? = null
|
|
51
|
+
|
|
52
|
+
/// Dedicated render thread — owns all CesiumEngine + GPU work.
|
|
47
53
|
private var renderThread: HandlerThread? = null
|
|
48
54
|
private var renderHandler: Handler? = null
|
|
49
55
|
|
|
@@ -123,27 +129,38 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
123
129
|
override var minAltitudeAboveTerrain: Double? = null
|
|
124
130
|
set(value) { field = value; if (value != null) postToRender { bridge?.setMinAltitudeAboveTerrain(value.toFloat()) } }
|
|
125
131
|
|
|
132
|
+
// ── Rate caps ────────────────────────────────────────────────────────────
|
|
133
|
+
override var maxYawRateDegSec: Double? = null
|
|
134
|
+
set(value) { field = value; pushRateCaps() }
|
|
135
|
+
override var maxPitchRateDegSec: Double? = null
|
|
136
|
+
set(value) { field = value; pushRateCaps() }
|
|
137
|
+
override var maxRollRateDegSec: Double? = null
|
|
138
|
+
set(value) { field = value; pushRateCaps() }
|
|
139
|
+
override var maxClimbRateMps: Double? = null
|
|
140
|
+
set(value) { field = value; pushRateCaps() }
|
|
141
|
+
override var maxGroundSpeedMps: Double? = null
|
|
142
|
+
set(value) { field = value; pushRateCaps() }
|
|
143
|
+
|
|
144
|
+
private fun pushRateCaps() {
|
|
145
|
+
val yaw = maxYawRateDegSec ?: 0.0
|
|
146
|
+
val pitch = maxPitchRateDegSec ?: 0.0
|
|
147
|
+
val roll = maxRollRateDegSec ?: 0.0
|
|
148
|
+
val climb = maxClimbRateMps ?: 0.0
|
|
149
|
+
val gnd = maxGroundSpeedMps ?: 0.0
|
|
150
|
+
postToRender { bridge?.setRateCaps(yaw, pitch, roll, climb, gnd) }
|
|
151
|
+
}
|
|
152
|
+
|
|
126
153
|
override var onMetrics: ((metrics: CesiumMetrics) -> Unit)? = null
|
|
127
154
|
|
|
128
155
|
override val view: View get() = surfaceView
|
|
129
156
|
|
|
130
|
-
private val frameCallback = Choreographer.FrameCallback {
|
|
157
|
+
private val frameCallback = Choreographer.FrameCallback {
|
|
131
158
|
renderLoopActive = false
|
|
132
|
-
if (pauseRendering || bridge == null) {
|
|
159
|
+
if (isShutdown || pauseRendering || bridge == null) {
|
|
133
160
|
return@FrameCallback
|
|
134
161
|
}
|
|
135
162
|
|
|
136
|
-
val dt = if (lastFrameTimeNanos > 0) {
|
|
137
|
-
(frameTimeNanos - lastFrameTimeNanos).coerceAtLeast(1_000_000L) / 1_000_000_000.0
|
|
138
|
-
} else {
|
|
139
|
-
1.0 / 60.0
|
|
140
|
-
}
|
|
141
|
-
lastFrameTimeNanos = frameTimeNanos
|
|
142
|
-
|
|
143
|
-
// Drop the tick if the previous frame's render hasn't finished — keeps the
|
|
144
|
-
// render thread from queuing up an unbounded backlog under stutter.
|
|
145
163
|
if (!renderInFlight.compareAndSet(false, true)) {
|
|
146
|
-
// Re-arm so we try again next vsync.
|
|
147
164
|
scheduleNextFrame(immediate = true)
|
|
148
165
|
return@FrameCallback
|
|
149
166
|
}
|
|
@@ -153,13 +170,13 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
153
170
|
renderInFlight.set(false)
|
|
154
171
|
return@FrameCallback
|
|
155
172
|
}
|
|
156
|
-
|
|
173
|
+
val nowSeconds = SystemClock.elapsedRealtimeNanos() / 1_000_000_000.0
|
|
174
|
+
rh.post { runFrameOnRenderThread(nowSeconds) }
|
|
157
175
|
}
|
|
158
176
|
|
|
159
|
-
///
|
|
160
|
-
///
|
|
161
|
-
|
|
162
|
-
private fun runFrameOnRenderThread(dt: Double) {
|
|
177
|
+
/// Run on the render thread immediately after the bridge is created, before
|
|
178
|
+
/// the first frame.
|
|
179
|
+
private fun runFrameOnRenderThread(nowSeconds: Double) {
|
|
163
180
|
val b = bridge
|
|
164
181
|
if (b == null) {
|
|
165
182
|
renderInFlight.set(false)
|
|
@@ -170,10 +187,11 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
170
187
|
val nextImmediate: Boolean
|
|
171
188
|
if (shouldRender) {
|
|
172
189
|
idleProbeAccumulator = 0.0
|
|
173
|
-
b.renderFrame(
|
|
190
|
+
b.renderFrame(nowSeconds)
|
|
174
191
|
nextImmediate = true
|
|
175
192
|
} else {
|
|
176
|
-
|
|
193
|
+
// Approximate dt for the idle probe; not used for motion.
|
|
194
|
+
idleProbeAccumulator += 1.0 / 60.0
|
|
177
195
|
if (idleProbeAccumulator >= 0.25) {
|
|
178
196
|
idleProbeAccumulator = 0.0
|
|
179
197
|
b.markNeedsRender()
|
|
@@ -184,7 +202,7 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
184
202
|
metricsFrameCounter++
|
|
185
203
|
if (metricsFrameCounter >= 20) {
|
|
186
204
|
metricsFrameCounter = 0
|
|
187
|
-
val cb = onMetrics
|
|
205
|
+
val cb = if (isShutdown) null else onMetrics
|
|
188
206
|
if (cb != null) {
|
|
189
207
|
cb(CesiumMetrics(
|
|
190
208
|
fps = b.getMetricsFps(),
|
|
@@ -201,9 +219,6 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
201
219
|
|
|
202
220
|
renderInFlight.set(false)
|
|
203
221
|
|
|
204
|
-
// Re-arm on the UI thread so Choreographer remains the source of truth
|
|
205
|
-
// for vsync timing. Posting a no-op runnable to surfaceView keeps us off
|
|
206
|
-
// the render thread for that small piece of bookkeeping.
|
|
207
222
|
surfaceView.post { scheduleNextFrame(immediate = nextImmediate) }
|
|
208
223
|
}
|
|
209
224
|
|
|
@@ -214,47 +229,38 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
214
229
|
val h = surfaceView.height
|
|
215
230
|
if (w <= 0 || h <= 0) return
|
|
216
231
|
|
|
217
|
-
// Spin up the render thread on first surfaceCreated. If we previously
|
|
218
|
-
// tore it down (e.g. on backgrounding), re-spawn cleanly.
|
|
219
232
|
ensureRenderThread()
|
|
220
233
|
val rh = renderHandler ?: return
|
|
221
234
|
|
|
222
235
|
val cacheDir = appContext.cacheDir.absolutePath
|
|
223
236
|
val cacertPath = extractCacert()
|
|
224
|
-
// Init runs on the render thread because Vulkan instance/device creation
|
|
225
|
-
// and Cesium Native init both touch state we'd rather only ever poke
|
|
226
|
-
// from one thread.
|
|
227
237
|
rh.post {
|
|
228
238
|
val b = CesiumBridgeJNI()
|
|
229
239
|
b.init(surface, w, h, cacheDir, cacertPath)
|
|
230
240
|
bridge = b
|
|
231
241
|
syncBridgePropsOnRenderThread(b)
|
|
232
242
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
243
|
+
val seed = pendingTeleport ?: initialCamera
|
|
244
|
+
b.teleport(
|
|
245
|
+
seed.latitude, seed.longitude, seed.altitude,
|
|
246
|
+
seed.heading, seed.pitch, seed.roll, seed.verticalFovDeg,
|
|
236
247
|
)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
val
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
pending.heading, pending.pitch, pending.roll,
|
|
254
|
-
)
|
|
255
|
-
}
|
|
256
|
-
b.setVerticalFovDeg(pending.verticalFovDeg)
|
|
257
|
-
lastPushedCamera = pending
|
|
248
|
+
pendingTeleport = null
|
|
249
|
+
|
|
250
|
+
val la = pendingLat
|
|
251
|
+
val lo = pendingLon
|
|
252
|
+
if (la != null && lo != null) {
|
|
253
|
+
b.setPosition(la, lo); pendingLat = null; pendingLon = null
|
|
254
|
+
}
|
|
255
|
+
pendingAltitude?.let { b.setAltitude(it); pendingAltitude = null }
|
|
256
|
+
pendingHeading?.let { b.setHeading(it); pendingHeading = null }
|
|
257
|
+
val p = pendingPitch; val r = pendingRoll
|
|
258
|
+
if (p != null && r != null) {
|
|
259
|
+
b.setAttitude(p, r); pendingPitch = null; pendingRoll = null
|
|
260
|
+
}
|
|
261
|
+
pendingVfov?.let { b.setVerticalFovDeg(it); pendingVfov = null }
|
|
262
|
+
pendingViewCorrection?.let { q ->
|
|
263
|
+
b.setViewCorrection(q.w, q.x, q.y, q.z); pendingViewCorrection = null
|
|
258
264
|
}
|
|
259
265
|
|
|
260
266
|
if (ionAccessToken.isNotEmpty()) {
|
|
@@ -265,9 +271,8 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
265
271
|
}
|
|
266
272
|
b.setMsaa(msaaSampleCount.toInt())
|
|
267
273
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// because Choreographer.postFrameCallback() requires it.
|
|
274
|
+
pushRateCaps()
|
|
275
|
+
|
|
271
276
|
surfaceView.post { scheduleNextFrame(immediate = true) }
|
|
272
277
|
}
|
|
273
278
|
}
|
|
@@ -276,30 +281,28 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
276
281
|
postToRender {
|
|
277
282
|
bridge?.resize(width, height)
|
|
278
283
|
bridge?.markNeedsRender()
|
|
279
|
-
// Re-arm the loop in case surfaceChanged fires before the bridge was
|
|
280
|
-
// created (rare, but possible on cold starts). Idempotent: scheduleNextFrame
|
|
281
|
-
// early-returns if the loop is already active.
|
|
282
284
|
surfaceView.post { scheduleNextFrame(immediate = true) }
|
|
283
285
|
}
|
|
284
286
|
}
|
|
285
287
|
|
|
286
288
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
|
289
|
+
// Flip the shutdown gate and drop the JS callback before any teardown
|
|
290
|
+
// work — any in-flight render-thread frame or queued choreographer tick
|
|
291
|
+
// will then bail out without touching the bridge or emitting metrics
|
|
292
|
+
// into a dying JS runtime.
|
|
293
|
+
isShutdown = true
|
|
294
|
+
onMetrics = null
|
|
295
|
+
|
|
287
296
|
val rh = renderHandler
|
|
288
297
|
val ht = renderThread
|
|
289
298
|
|
|
290
299
|
if (rh == null || ht == null) {
|
|
291
|
-
// Nothing to drain; clear bridge reference if it somehow exists.
|
|
292
300
|
bridge?.shutdown()
|
|
293
301
|
bridge?.destroy()
|
|
294
302
|
bridge = null
|
|
295
303
|
return
|
|
296
304
|
}
|
|
297
305
|
|
|
298
|
-
// Block the UI thread (briefly) until the render thread has shut the
|
|
299
|
-
// bridge down — surfaceDestroyed contract requires the surface to be
|
|
300
|
-
// released before this returns. We cap our wait so a stuck GPU teardown
|
|
301
|
-
// can't hang the UI thread indefinitely (matches our 1s shutdown
|
|
302
|
-
// semaphore policy on the Vulkan / Metal side).
|
|
303
306
|
val done = java.util.concurrent.CountDownLatch(1)
|
|
304
307
|
rh.post {
|
|
305
308
|
try {
|
|
@@ -313,8 +316,7 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
313
316
|
}
|
|
314
317
|
}
|
|
315
318
|
if (!done.await(1500, java.util.concurrent.TimeUnit.MILLISECONDS)) {
|
|
316
|
-
Log.w("CesiumView", "Render-thread shutdown did not complete in 1.5s; "
|
|
317
|
-
+ "tearing down handler thread anyway.")
|
|
319
|
+
Log.w("CesiumView", "Render-thread shutdown did not complete in 1.5s; tearing down handler thread anyway.")
|
|
318
320
|
}
|
|
319
321
|
stopRenderThread()
|
|
320
322
|
}
|
|
@@ -341,53 +343,35 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
341
343
|
ht.quitSafely()
|
|
342
344
|
}
|
|
343
345
|
|
|
344
|
-
/// Posts an action to the render thread. When the render thread isn't
|
|
345
|
-
/// running yet (early prop assignment, or after teardown), we silently drop
|
|
346
|
-
/// the action — bridges always take their initial config from the
|
|
347
|
-
/// `syncBridgePropsOnRenderThread` step inside surfaceCreated.
|
|
348
346
|
private fun postToRender(action: () -> Unit) {
|
|
349
347
|
renderHandler?.post(action)
|
|
350
348
|
}
|
|
351
349
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
/// GlobeCamera, none of which is mutated outside the render thread, so the
|
|
355
|
-
/// worst that can happen is observing a value from a frame slightly behind
|
|
356
|
-
/// what the render thread is encoding right now — acceptable for a UI-driven
|
|
357
|
-
/// camera read.
|
|
358
|
-
override fun getCameraState(): Promise<CameraState> {
|
|
350
|
+
// ── Camera readback ─────────────────────────────────────────────────────
|
|
351
|
+
override fun getActualCamera(): Promise<CameraState> {
|
|
359
352
|
val b = bridge ?: return Promise.resolved(initialCamera)
|
|
360
353
|
return Promise.resolved(CameraState(
|
|
361
|
-
latitude = b.
|
|
362
|
-
longitude = b.
|
|
363
|
-
altitude = b.
|
|
364
|
-
heading = b.
|
|
365
|
-
pitch = b.
|
|
366
|
-
roll = b.
|
|
367
|
-
verticalFovDeg = b.
|
|
354
|
+
latitude = b.getActualLatitude(),
|
|
355
|
+
longitude = b.getActualLongitude(),
|
|
356
|
+
altitude = b.getActualAltitude(),
|
|
357
|
+
heading = b.getActualHeading(),
|
|
358
|
+
pitch = b.getActualPitch(),
|
|
359
|
+
roll = b.getActualRoll(),
|
|
360
|
+
verticalFovDeg = b.getActualVerticalFovDeg(),
|
|
368
361
|
))
|
|
369
362
|
}
|
|
370
363
|
|
|
371
|
-
override fun
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
override fun setCameraQuaternion(camera: CameraState, viewCorrection: Quaternion) {
|
|
384
|
-
pendingRuntimeCamera = camera
|
|
385
|
-
pendingRuntimeViewCorrection = viewCorrection
|
|
386
|
-
postToRender {
|
|
387
|
-
pushCameraQuaternionIfChanged(camera, viewCorrection)
|
|
388
|
-
bridge?.markNeedsRender()
|
|
389
|
-
}
|
|
390
|
-
scheduleNextFrame(immediate = true)
|
|
364
|
+
override fun getDemandCamera(): Promise<CameraState> {
|
|
365
|
+
val b = bridge ?: return Promise.resolved(initialCamera)
|
|
366
|
+
return Promise.resolved(CameraState(
|
|
367
|
+
latitude = b.getDemandLatitude(),
|
|
368
|
+
longitude = b.getDemandLongitude(),
|
|
369
|
+
altitude = b.getDemandAltitude(),
|
|
370
|
+
heading = b.getDemandHeading(),
|
|
371
|
+
pitch = b.getDemandPitch(),
|
|
372
|
+
roll = b.getDemandRoll(),
|
|
373
|
+
verticalFovDeg = b.getDemandVerticalFovDeg(),
|
|
374
|
+
))
|
|
391
375
|
}
|
|
392
376
|
|
|
393
377
|
override fun getViewCorrection(): Promise<Quaternion> {
|
|
@@ -403,65 +387,63 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
403
387
|
)
|
|
404
388
|
}
|
|
405
389
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
390
|
+
// ── Per-DoF camera setters ──────────────────────────────────────────────
|
|
391
|
+
// These bypass the renderHandler hop: the underlying CameraIntegrator owns
|
|
392
|
+
// its own mutex and handles concurrent writers safely. Keeping the hot
|
|
393
|
+
// path off the render thread reduces latency for high-frequency
|
|
394
|
+
// (Reanimated worklet, 50 Hz IMU) drivers.
|
|
395
|
+
override fun setPosition(latitude: Double, longitude: Double) {
|
|
396
|
+
val b = bridge
|
|
397
|
+
if (b != null) {
|
|
398
|
+
b.setPosition(latitude, longitude)
|
|
399
|
+
scheduleNextFrame(immediate = true)
|
|
400
|
+
} else {
|
|
401
|
+
pendingLat = latitude; pendingLon = longitude
|
|
418
402
|
}
|
|
419
|
-
lastPushedCamera = camera
|
|
420
|
-
b.updateCamera(camera.latitude, camera.longitude, camera.altitude,
|
|
421
|
-
camera.heading, camera.pitch, camera.roll)
|
|
422
|
-
b.setVerticalFovDeg(camera.verticalFovDeg)
|
|
423
403
|
}
|
|
424
404
|
|
|
425
|
-
|
|
426
|
-
val b = bridge
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
405
|
+
override fun setAltitude(altitudeMeters: Double) {
|
|
406
|
+
val b = bridge
|
|
407
|
+
if (b != null) { b.setAltitude(altitudeMeters); scheduleNextFrame(immediate = true) }
|
|
408
|
+
else { pendingAltitude = altitudeMeters }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
override fun setHeading(headingDeg: Double) {
|
|
412
|
+
val b = bridge
|
|
413
|
+
if (b != null) { b.setHeading(headingDeg); scheduleNextFrame(immediate = true) }
|
|
414
|
+
else { pendingHeading = headingDeg }
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
override fun setAttitude(pitchDeg: Double, rollDeg: Double) {
|
|
418
|
+
val b = bridge
|
|
419
|
+
if (b != null) { b.setAttitude(pitchDeg, rollDeg); scheduleNextFrame(immediate = true) }
|
|
420
|
+
else { pendingPitch = pitchDeg; pendingRoll = rollDeg }
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
override fun setViewCorrection(q: Quaternion) {
|
|
424
|
+
val b = bridge
|
|
425
|
+
if (b != null) { b.setViewCorrection(q.w, q.x, q.y, q.z); scheduleNextFrame(immediate = true) }
|
|
426
|
+
else { pendingViewCorrection = q }
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
override fun setVerticalFov(deg: Double) {
|
|
430
|
+
val b = bridge
|
|
431
|
+
if (b != null) { b.setVerticalFovDeg(deg); scheduleNextFrame(immediate = true) }
|
|
432
|
+
else { pendingVfov = deg }
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
override fun teleport(camera: CameraState) {
|
|
436
|
+
val b = bridge
|
|
437
|
+
if (b != null) {
|
|
438
|
+
b.teleport(camera.latitude, camera.longitude, camera.altitude,
|
|
439
|
+
camera.heading, camera.pitch, camera.roll, camera.verticalFovDeg)
|
|
440
|
+
scheduleNextFrame(immediate = true)
|
|
441
|
+
} else {
|
|
442
|
+
pendingTeleport = camera
|
|
443
443
|
}
|
|
444
|
-
lastPushedCamera = camera
|
|
445
|
-
lastPushedViewCorrection = viewCorrection
|
|
446
|
-
b.updateCameraQuaternion(
|
|
447
|
-
camera.latitude,
|
|
448
|
-
camera.longitude,
|
|
449
|
-
camera.altitude,
|
|
450
|
-
camera.heading,
|
|
451
|
-
camera.pitch,
|
|
452
|
-
camera.roll,
|
|
453
|
-
viewCorrection.w,
|
|
454
|
-
viewCorrection.x,
|
|
455
|
-
viewCorrection.y,
|
|
456
|
-
viewCorrection.z,
|
|
457
|
-
)
|
|
458
|
-
b.setVerticalFovDeg(camera.verticalFovDeg)
|
|
459
444
|
}
|
|
460
445
|
|
|
461
|
-
///
|
|
462
|
-
/// the first frame. Matches Swift's syncBridgeOptionsFromProps and pushes
|
|
463
|
-
/// every prop into the freshly-built CesiumEngine so the consumer doesn't
|
|
464
|
-
/// have to re-set them after the surface comes up.
|
|
446
|
+
/// Render-thread setup, called once after the bridge is built.
|
|
465
447
|
private fun syncBridgePropsOnRenderThread(b: CesiumBridgeJNI) {
|
|
466
448
|
b.setMaxSSE(maximumScreenSpaceError)
|
|
467
449
|
b.setMaxSimLoads(maximumSimultaneousTileLoads.toInt())
|
|
@@ -483,12 +465,6 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
483
465
|
|
|
484
466
|
/**
|
|
485
467
|
* Schedule the next frame callback.
|
|
486
|
-
*
|
|
487
|
-
* - `immediate = true`: the engine wants to render this vsync (active pan,
|
|
488
|
-
* tile loading, prop change). Re-post on the next available vsync.
|
|
489
|
-
* - `immediate = false`: the engine is idle. Re-post in 250ms — matches the
|
|
490
|
-
* iOS low-refresh range and keeps the main thread mostly asleep while
|
|
491
|
-
* still picking up late tile completions / network responses.
|
|
492
468
|
*/
|
|
493
469
|
private fun scheduleNextFrame(immediate: Boolean) {
|
|
494
470
|
if (renderLoopActive || pauseRendering || bridge == null) return
|
|
@@ -504,15 +480,12 @@ class HybridCesiumView(private val appContext: Context) : HybridCesiumViewSpec()
|
|
|
504
480
|
val outFile = File(appContext.cacheDir, "cacert.pem")
|
|
505
481
|
try {
|
|
506
482
|
appContext.assets.open("cacert.pem").use { input ->
|
|
507
|
-
// Re-extract if the cached copy is missing or a different size to the
|
|
508
|
-
// bundled asset (handles the file being updated across app versions).
|
|
509
483
|
val assetSize = input.available().toLong()
|
|
510
484
|
if (!outFile.exists() || outFile.length() != assetSize) {
|
|
511
485
|
FileOutputStream(outFile).use { output -> input.copyTo(output) }
|
|
512
486
|
}
|
|
513
487
|
}
|
|
514
488
|
} catch (_: Exception) {
|
|
515
|
-
// cacert.pem not bundled as asset — libcurl will use system certs
|
|
516
489
|
return if (outFile.exists()) outFile.absolutePath else ""
|
|
517
490
|
}
|
|
518
491
|
return outFile.absolutePath
|