react-native-cesium 1.2.3 → 1.2.5
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 +199 -187
- package/cpp/engine/CameraIntegrator.cpp +374 -0
- package/cpp/engine/CameraIntegrator.hpp +146 -0
- package/cpp/engine/EngineTunables.hpp +90 -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 +256 -145
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts +85 -13
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/ReactNativeCesiumOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JFunc_void_CameraState.hpp +77 -0
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +108 -9
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +21 -3
- package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Func_void_CameraState.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +71 -3
- package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.cpp +3 -3
- package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +43 -28
- package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +89 -9
- package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +30 -0
- package/nitrogen/generated/ios/swift/Func_void_CameraState.swift +5 -5
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +15 -3
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +242 -16
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +21 -3
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +22 -4
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +66 -0
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +6 -0
- package/nitrogen/generated/shared/json/CesiumViewConfig.json +6 -0
- package/package.json +1 -1
- package/src/specs/cesium-view.nitro.ts +102 -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
|
@@ -25,8 +25,8 @@ namespace margelo::nitro::reactnativecesium { struct Quaternion; }
|
|
|
25
25
|
#include <optional>
|
|
26
26
|
#include "CesiumMetrics.hpp"
|
|
27
27
|
#include <functional>
|
|
28
|
-
#include <NitroModules/Promise.hpp>
|
|
29
28
|
#include "Quaternion.hpp"
|
|
29
|
+
#include <NitroModules/Promise.hpp>
|
|
30
30
|
|
|
31
31
|
namespace margelo::nitro::reactnativecesium {
|
|
32
32
|
|
|
@@ -99,14 +99,32 @@ namespace margelo::nitro::reactnativecesium {
|
|
|
99
99
|
virtual void setTaskProcessorThreads(std::optional<double> taskProcessorThreads) = 0;
|
|
100
100
|
virtual std::optional<double> getMinAltitudeAboveTerrain() = 0;
|
|
101
101
|
virtual void setMinAltitudeAboveTerrain(std::optional<double> minAltitudeAboveTerrain) = 0;
|
|
102
|
+
virtual std::optional<double> getMaxYawRateDegSec() = 0;
|
|
103
|
+
virtual void setMaxYawRateDegSec(std::optional<double> maxYawRateDegSec) = 0;
|
|
104
|
+
virtual std::optional<double> getMaxPitchRateDegSec() = 0;
|
|
105
|
+
virtual void setMaxPitchRateDegSec(std::optional<double> maxPitchRateDegSec) = 0;
|
|
106
|
+
virtual std::optional<double> getMaxRollRateDegSec() = 0;
|
|
107
|
+
virtual void setMaxRollRateDegSec(std::optional<double> maxRollRateDegSec) = 0;
|
|
108
|
+
virtual std::optional<double> getMaxClimbRateMps() = 0;
|
|
109
|
+
virtual void setMaxClimbRateMps(std::optional<double> maxClimbRateMps) = 0;
|
|
110
|
+
virtual std::optional<double> getMaxGroundSpeedMps() = 0;
|
|
111
|
+
virtual void setMaxGroundSpeedMps(std::optional<double> maxGroundSpeedMps) = 0;
|
|
102
112
|
virtual std::optional<std::function<void(const CesiumMetrics& /* metrics */)>> getOnMetrics() = 0;
|
|
103
113
|
virtual void setOnMetrics(const std::optional<std::function<void(const CesiumMetrics& /* metrics */)>>& onMetrics) = 0;
|
|
114
|
+
virtual std::optional<std::function<void(const CameraState& /* camera */)>> getOnActualCamera() = 0;
|
|
115
|
+
virtual void setOnActualCamera(const std::optional<std::function<void(const CameraState& /* camera */)>>& onActualCamera) = 0;
|
|
104
116
|
|
|
105
117
|
public:
|
|
106
118
|
// Methods
|
|
107
|
-
virtual
|
|
108
|
-
virtual void
|
|
109
|
-
virtual void
|
|
119
|
+
virtual void setPosition(double latitude, double longitude) = 0;
|
|
120
|
+
virtual void setAltitude(double altitudeMeters) = 0;
|
|
121
|
+
virtual void setHeading(double headingDeg) = 0;
|
|
122
|
+
virtual void setAttitude(double pitchDeg, double rollDeg) = 0;
|
|
123
|
+
virtual void setViewCorrection(const Quaternion& q) = 0;
|
|
124
|
+
virtual void setVerticalFov(double deg) = 0;
|
|
125
|
+
virtual void teleport(const CameraState& camera) = 0;
|
|
126
|
+
virtual std::shared_ptr<Promise<CameraState>> getActualCamera() = 0;
|
|
127
|
+
virtual std::shared_ptr<Promise<CameraState>> getDemandCamera() = 0;
|
|
110
128
|
virtual std::shared_ptr<Promise<Quaternion>> getViewCorrection() = 0;
|
|
111
129
|
|
|
112
130
|
protected:
|
|
@@ -246,6 +246,56 @@ namespace margelo::nitro::reactnativecesium::views {
|
|
|
246
246
|
throw std::runtime_error(std::string("CesiumView.minAltitudeAboveTerrain: ") + exc.what());
|
|
247
247
|
}
|
|
248
248
|
}()),
|
|
249
|
+
maxYawRateDegSec([&]() -> CachedProp<std::optional<double>> {
|
|
250
|
+
try {
|
|
251
|
+
const react::RawValue* rawValue = rawProps.at("maxYawRateDegSec", nullptr, nullptr);
|
|
252
|
+
if (rawValue == nullptr) return sourceProps.maxYawRateDegSec;
|
|
253
|
+
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
|
|
254
|
+
return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.maxYawRateDegSec);
|
|
255
|
+
} catch (const std::exception& exc) {
|
|
256
|
+
throw std::runtime_error(std::string("CesiumView.maxYawRateDegSec: ") + exc.what());
|
|
257
|
+
}
|
|
258
|
+
}()),
|
|
259
|
+
maxPitchRateDegSec([&]() -> CachedProp<std::optional<double>> {
|
|
260
|
+
try {
|
|
261
|
+
const react::RawValue* rawValue = rawProps.at("maxPitchRateDegSec", nullptr, nullptr);
|
|
262
|
+
if (rawValue == nullptr) return sourceProps.maxPitchRateDegSec;
|
|
263
|
+
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
|
|
264
|
+
return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.maxPitchRateDegSec);
|
|
265
|
+
} catch (const std::exception& exc) {
|
|
266
|
+
throw std::runtime_error(std::string("CesiumView.maxPitchRateDegSec: ") + exc.what());
|
|
267
|
+
}
|
|
268
|
+
}()),
|
|
269
|
+
maxRollRateDegSec([&]() -> CachedProp<std::optional<double>> {
|
|
270
|
+
try {
|
|
271
|
+
const react::RawValue* rawValue = rawProps.at("maxRollRateDegSec", nullptr, nullptr);
|
|
272
|
+
if (rawValue == nullptr) return sourceProps.maxRollRateDegSec;
|
|
273
|
+
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
|
|
274
|
+
return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.maxRollRateDegSec);
|
|
275
|
+
} catch (const std::exception& exc) {
|
|
276
|
+
throw std::runtime_error(std::string("CesiumView.maxRollRateDegSec: ") + exc.what());
|
|
277
|
+
}
|
|
278
|
+
}()),
|
|
279
|
+
maxClimbRateMps([&]() -> CachedProp<std::optional<double>> {
|
|
280
|
+
try {
|
|
281
|
+
const react::RawValue* rawValue = rawProps.at("maxClimbRateMps", nullptr, nullptr);
|
|
282
|
+
if (rawValue == nullptr) return sourceProps.maxClimbRateMps;
|
|
283
|
+
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
|
|
284
|
+
return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.maxClimbRateMps);
|
|
285
|
+
} catch (const std::exception& exc) {
|
|
286
|
+
throw std::runtime_error(std::string("CesiumView.maxClimbRateMps: ") + exc.what());
|
|
287
|
+
}
|
|
288
|
+
}()),
|
|
289
|
+
maxGroundSpeedMps([&]() -> CachedProp<std::optional<double>> {
|
|
290
|
+
try {
|
|
291
|
+
const react::RawValue* rawValue = rawProps.at("maxGroundSpeedMps", nullptr, nullptr);
|
|
292
|
+
if (rawValue == nullptr) return sourceProps.maxGroundSpeedMps;
|
|
293
|
+
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
|
|
294
|
+
return CachedProp<std::optional<double>>::fromRawValue(*runtime, value, sourceProps.maxGroundSpeedMps);
|
|
295
|
+
} catch (const std::exception& exc) {
|
|
296
|
+
throw std::runtime_error(std::string("CesiumView.maxGroundSpeedMps: ") + exc.what());
|
|
297
|
+
}
|
|
298
|
+
}()),
|
|
249
299
|
onMetrics([&]() -> CachedProp<std::optional<std::function<void(const CesiumMetrics& /* metrics */)>>> {
|
|
250
300
|
try {
|
|
251
301
|
const react::RawValue* rawValue = rawProps.at("onMetrics", nullptr, nullptr);
|
|
@@ -256,6 +306,16 @@ namespace margelo::nitro::reactnativecesium::views {
|
|
|
256
306
|
throw std::runtime_error(std::string("CesiumView.onMetrics: ") + exc.what());
|
|
257
307
|
}
|
|
258
308
|
}()),
|
|
309
|
+
onActualCamera([&]() -> CachedProp<std::optional<std::function<void(const CameraState& /* camera */)>>> {
|
|
310
|
+
try {
|
|
311
|
+
const react::RawValue* rawValue = rawProps.at("onActualCamera", nullptr, nullptr);
|
|
312
|
+
if (rawValue == nullptr) return sourceProps.onActualCamera;
|
|
313
|
+
const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
|
|
314
|
+
return CachedProp<std::optional<std::function<void(const CameraState& /* camera */)>>>::fromRawValue(*runtime, value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")), sourceProps.onActualCamera);
|
|
315
|
+
} catch (const std::exception& exc) {
|
|
316
|
+
throw std::runtime_error(std::string("CesiumView.onActualCamera: ") + exc.what());
|
|
317
|
+
}
|
|
318
|
+
}()),
|
|
259
319
|
hybridRef([&]() -> CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridCesiumViewSpec>& /* ref */)>>> {
|
|
260
320
|
try {
|
|
261
321
|
const react::RawValue* rawValue = rawProps.at("hybridRef", nullptr, nullptr);
|
|
@@ -291,7 +351,13 @@ namespace margelo::nitro::reactnativecesium::views {
|
|
|
291
351
|
case hashString("sqliteCacheMaxRows"): return true;
|
|
292
352
|
case hashString("taskProcessorThreads"): return true;
|
|
293
353
|
case hashString("minAltitudeAboveTerrain"): return true;
|
|
354
|
+
case hashString("maxYawRateDegSec"): return true;
|
|
355
|
+
case hashString("maxPitchRateDegSec"): return true;
|
|
356
|
+
case hashString("maxRollRateDegSec"): return true;
|
|
357
|
+
case hashString("maxClimbRateMps"): return true;
|
|
358
|
+
case hashString("maxGroundSpeedMps"): return true;
|
|
294
359
|
case hashString("onMetrics"): return true;
|
|
360
|
+
case hashString("onActualCamera"): return true;
|
|
295
361
|
case hashString("hybridRef"): return true;
|
|
296
362
|
default: return false;
|
|
297
363
|
}
|
|
@@ -66,7 +66,13 @@ namespace margelo::nitro::reactnativecesium::views {
|
|
|
66
66
|
CachedProp<std::optional<double>> sqliteCacheMaxRows;
|
|
67
67
|
CachedProp<std::optional<double>> taskProcessorThreads;
|
|
68
68
|
CachedProp<std::optional<double>> minAltitudeAboveTerrain;
|
|
69
|
+
CachedProp<std::optional<double>> maxYawRateDegSec;
|
|
70
|
+
CachedProp<std::optional<double>> maxPitchRateDegSec;
|
|
71
|
+
CachedProp<std::optional<double>> maxRollRateDegSec;
|
|
72
|
+
CachedProp<std::optional<double>> maxClimbRateMps;
|
|
73
|
+
CachedProp<std::optional<double>> maxGroundSpeedMps;
|
|
69
74
|
CachedProp<std::optional<std::function<void(const CesiumMetrics& /* metrics */)>>> onMetrics;
|
|
75
|
+
CachedProp<std::optional<std::function<void(const CameraState& /* camera */)>>> onActualCamera;
|
|
70
76
|
CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridCesiumViewSpec>& /* ref */)>>> hybridRef;
|
|
71
77
|
|
|
72
78
|
private:
|
|
@@ -26,7 +26,13 @@
|
|
|
26
26
|
"sqliteCacheMaxRows": true,
|
|
27
27
|
"taskProcessorThreads": true,
|
|
28
28
|
"minAltitudeAboveTerrain": true,
|
|
29
|
+
"maxYawRateDegSec": true,
|
|
30
|
+
"maxPitchRateDegSec": true,
|
|
31
|
+
"maxRollRateDegSec": true,
|
|
32
|
+
"maxClimbRateMps": true,
|
|
33
|
+
"maxGroundSpeedMps": true,
|
|
29
34
|
"onMetrics": true,
|
|
35
|
+
"onActualCamera": true,
|
|
30
36
|
"hybridRef": true
|
|
31
37
|
}
|
|
32
38
|
}
|
package/package.json
CHANGED
|
@@ -4,7 +4,10 @@ import type {
|
|
|
4
4
|
HybridViewMethods,
|
|
5
5
|
} from 'react-native-nitro-modules'
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Snapshot of the native camera for persistence / sync with React state.
|
|
9
|
+
* Returned by `getActualCamera()` and `getDemandCamera()`.
|
|
10
|
+
*/
|
|
8
11
|
export interface CameraState {
|
|
9
12
|
latitude: number
|
|
10
13
|
longitude: number
|
|
@@ -16,8 +19,9 @@ export interface CameraState {
|
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
|
-
* Unit quaternion (w + xi + yj + zk). Used for camera-space view correction
|
|
20
|
-
* Non-unit values are
|
|
22
|
+
* Unit quaternion (w + xi + yj + zk). Used for camera-space view correction
|
|
23
|
+
* applied after HPR (boresight / HUD alignment). Non-unit values are
|
|
24
|
+
* normalised on the native side.
|
|
21
25
|
*/
|
|
22
26
|
export interface Quaternion {
|
|
23
27
|
w: number
|
|
@@ -42,7 +46,11 @@ export interface CesiumMetrics {
|
|
|
42
46
|
export interface CesiumViewProps extends HybridViewProps {
|
|
43
47
|
ionAccessToken: string
|
|
44
48
|
ionAssetId: number
|
|
45
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Initial camera applied via `teleport(...)` once when the native bridge is
|
|
51
|
+
* created. Subsequent prop writes are ignored — use the per-DoF setters or
|
|
52
|
+
* `teleport(...)` for runtime updates.
|
|
53
|
+
*/
|
|
46
54
|
initialCamera: CameraState
|
|
47
55
|
pauseRendering: boolean
|
|
48
56
|
maximumScreenSpaceError: number
|
|
@@ -52,7 +60,7 @@ export interface CesiumViewProps extends HybridViewProps {
|
|
|
52
60
|
msaaSampleCount: number
|
|
53
61
|
ionImageryAssetId: number
|
|
54
62
|
|
|
55
|
-
// ── Optional perf / quality knobs
|
|
63
|
+
// ── Optional perf / quality knobs ────────────────────────────────────────
|
|
56
64
|
/** RAM budget for live tileset, in mebibytes. Default 256. */
|
|
57
65
|
maximumCachedMiB?: number
|
|
58
66
|
/** Pre-load ancestor tiles around the visible set (smoother panning, more loads). Default true. */
|
|
@@ -85,23 +93,103 @@ export interface CesiumViewProps extends HybridViewProps {
|
|
|
85
93
|
*/
|
|
86
94
|
minAltitudeAboveTerrain?: number
|
|
87
95
|
|
|
96
|
+
// ── Optional rate caps (flight-sim feel) ─────────────────────────────────
|
|
97
|
+
// All defaults: 0 = uncapped. Override per-screen to enforce a maximum
|
|
98
|
+
// angular / linear speed on the integrator's per-DoF velocity. Useful when
|
|
99
|
+
// your sensor feed is noisy and you would rather drop the change than
|
|
100
|
+
// visually whip the camera.
|
|
101
|
+
/** Maximum yaw rate (degrees per second). 0 = uncapped. */
|
|
102
|
+
maxYawRateDegSec?: number
|
|
103
|
+
/** Maximum pitch rate (degrees per second). 0 = uncapped. */
|
|
104
|
+
maxPitchRateDegSec?: number
|
|
105
|
+
/** Maximum roll rate (degrees per second). 0 = uncapped. */
|
|
106
|
+
maxRollRateDegSec?: number
|
|
107
|
+
/** Maximum climb / descent rate (metres per second). 0 = uncapped. */
|
|
108
|
+
maxClimbRateMps?: number
|
|
109
|
+
/** Maximum horizontal ground speed (metres per second). 0 = uncapped. */
|
|
110
|
+
maxGroundSpeedMps?: number
|
|
111
|
+
|
|
88
112
|
onMetrics?: (metrics: CesiumMetrics) => void
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Called at ~5 Hz with the post-integration actual camera — the same state
|
|
116
|
+
* returned by `getActualCamera()`. Only fires when at least one field has
|
|
117
|
+
* changed beyond its epsilon threshold since the last emission, so a
|
|
118
|
+
* stationary camera produces no bridge traffic. Use this to keep HUD overlay
|
|
119
|
+
* symbology aligned with the rendered view without polling from JS.
|
|
120
|
+
*/
|
|
121
|
+
onActualCamera?: (camera: CameraState) => void
|
|
89
122
|
}
|
|
90
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Per-DoF camera control. Every setter records the measurement with the
|
|
126
|
+
* native steady_clock time of arrival and feeds it into an α-β tracker
|
|
127
|
+
* (constant-velocity predictor) that owns both demand and actual state.
|
|
128
|
+
*
|
|
129
|
+
* All setters are synchronous and thread-safe. Calling them from a
|
|
130
|
+
* Reanimated worklet via a Nitro `hybridRef` is the supported path for
|
|
131
|
+
* high-frequency updates; the worklet thread takes the integrator's mutex
|
|
132
|
+
* briefly and returns immediately.
|
|
133
|
+
*
|
|
134
|
+
* Getters return Promises and should be called from the JS thread (e.g. a
|
|
135
|
+
* `useEffect` or a throttled HUD update), not from a worklet.
|
|
136
|
+
*/
|
|
91
137
|
export interface CesiumViewMethods extends HybridViewMethods {
|
|
92
|
-
/** Returns the current native camera state. */
|
|
93
|
-
getCameraState(): Promise<CameraState>
|
|
94
138
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
139
|
+
* New geographic position demand (latitude / longitude in degrees). Use
|
|
140
|
+
* for GPS or scripted position updates. Latitude is clamped to ±90 by the
|
|
141
|
+
* integrator; longitude is continuous (no wrap normalisation) so the
|
|
142
|
+
* shortest-arc residual maths stays correct across the antimeridian.
|
|
97
143
|
*/
|
|
98
|
-
|
|
144
|
+
setPosition(latitude: number, longitude: number): void
|
|
145
|
+
|
|
146
|
+
/** New altitude demand (metres above mean sea level). */
|
|
147
|
+
setAltitude(altitudeMeters: number): void
|
|
148
|
+
|
|
149
|
+
/** New heading demand (degrees, 0 = north, increasing clockwise). */
|
|
150
|
+
setHeading(headingDeg: number): void
|
|
151
|
+
|
|
99
152
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
153
|
+
* New attitude demand (pitch and roll in degrees). Bundled because almost
|
|
154
|
+
* every IMU emits them together — but you can pass the previous value for
|
|
155
|
+
* either axis if you only have one of them.
|
|
102
156
|
*/
|
|
103
|
-
|
|
104
|
-
|
|
157
|
+
setAttitude(pitchDeg: number, rollDeg: number): void
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* New camera-space rotation applied after HPR (unit quaternion). Use for
|
|
161
|
+
* boresight calibration or screen-fixed HUD alignment. SLERPed toward the
|
|
162
|
+
* latest demand each frame.
|
|
163
|
+
*/
|
|
164
|
+
setViewCorrection(q: Quaternion): void
|
|
165
|
+
|
|
166
|
+
/** New vertical field-of-view demand (degrees). Clamped to 20..100. */
|
|
167
|
+
setVerticalFov(deg: number): void
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Atomically reset every DoF to the given camera state (value, velocity
|
|
171
|
+
* and demand all snap to the target). Bypasses the integrator entirely;
|
|
172
|
+
* use for `Fly to coordinate` actions or initial seeding from saved
|
|
173
|
+
* state.
|
|
174
|
+
*/
|
|
175
|
+
teleport(camera: CameraState): void
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Most recent camera that was rendered (post-integration). This is what
|
|
179
|
+
* the user is currently looking at and is what a HUD overlay should
|
|
180
|
+
* mirror. Differs from `getDemandCamera()` while a glide is in progress
|
|
181
|
+
* or when the terrain-floor clamp has raised the altitude.
|
|
182
|
+
*/
|
|
183
|
+
getActualCamera(): Promise<CameraState>
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* What the consumer last asked for (per-DoF demand). Useful for
|
|
187
|
+
* diagnostics or for showing the requested camera alongside the rendered
|
|
188
|
+
* one.
|
|
189
|
+
*/
|
|
190
|
+
getDemandCamera(): Promise<CameraState>
|
|
191
|
+
|
|
192
|
+
/** Current view-correction quaternion (smoothed toward the latest demand). */
|
|
105
193
|
getViewCorrection(): Promise<Quaternion>
|
|
106
194
|
}
|
|
107
195
|
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
#include "CameraSmoother.hpp"
|
|
2
|
-
|
|
3
|
-
#include "EngineTunables.hpp"
|
|
4
|
-
|
|
5
|
-
#include <cmath>
|
|
6
|
-
|
|
7
|
-
namespace reactnativecesium {
|
|
8
|
-
|
|
9
|
-
namespace {
|
|
10
|
-
|
|
11
|
-
inline double lerpAngleDeg(double a, double b, double t) {
|
|
12
|
-
// Wraps difference to [-180, 180] before applying the mix factor so we
|
|
13
|
-
// always rotate the short way around the unit circle.
|
|
14
|
-
double diff = std::fmod(b - a + 540.0, 360.0) - 180.0;
|
|
15
|
-
return a + diff * t;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
inline double angleDeltaAbsDeg(double a, double b) {
|
|
19
|
-
return std::abs(std::fmod(b - a + 540.0, 360.0) - 180.0);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
inline double quatDotAbs(const glm::dquat& a, const glm::dquat& b) {
|
|
23
|
-
return std::abs(glm::dot(glm::normalize(a), glm::normalize(b)));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
} // namespace
|
|
27
|
-
|
|
28
|
-
CameraParams CameraSmoother::step(
|
|
29
|
-
const CameraParams& current, const CameraParams& target, double dt) {
|
|
30
|
-
CameraParams next = current;
|
|
31
|
-
|
|
32
|
-
// lat / lon / altitude / heading: snap directly to the (already linearly-
|
|
33
|
-
// interpolated) target from CameraTargetState::snapshot(). The constant-
|
|
34
|
-
// velocity motion is fully handled there; no additional smoothing is applied
|
|
35
|
-
// here so the movement stays linear rather than decelerating.
|
|
36
|
-
next.latitude = target.latitude;
|
|
37
|
-
next.longitude = target.longitude;
|
|
38
|
-
next.altitude = target.altitude;
|
|
39
|
-
next.heading = target.heading;
|
|
40
|
-
|
|
41
|
-
// pitch / roll / viewCorrection: exponential ease-out. These are almost
|
|
42
|
-
// always driven by high-rate attitude sensors (accelerometer, gyroscope)
|
|
43
|
-
// where the ease-out feel is appropriate and desired.
|
|
44
|
-
const double aPitch = 1.0 - std::exp(-tunables::kSmoothPitch * dt);
|
|
45
|
-
const double aRoll = 1.0 - std::exp(-tunables::kSmoothRoll * dt);
|
|
46
|
-
const double aViewQ = 1.0 - std::exp(-tunables::kSmoothViewCorr * dt);
|
|
47
|
-
|
|
48
|
-
next.pitch = lerpAngleDeg(current.pitch, target.pitch, aPitch);
|
|
49
|
-
next.roll = lerpAngleDeg(current.roll, target.roll, aRoll);
|
|
50
|
-
|
|
51
|
-
glm::dquat cq = glm::normalize(current.viewCorrection);
|
|
52
|
-
glm::dquat tq = glm::normalize(target.viewCorrection);
|
|
53
|
-
if (glm::dot(cq, tq) < 0.0) tq = -tq;
|
|
54
|
-
next.viewCorrection = glm::normalize(glm::slerp(cq, tq, aViewQ));
|
|
55
|
-
|
|
56
|
-
// Eventually-exact convergence for exponential DoFs: snap once sub-epsilon.
|
|
57
|
-
if (angleDeltaAbsDeg(next.pitch, target.pitch) <= tunables::kEpsAngleDeg)
|
|
58
|
-
next.pitch = target.pitch;
|
|
59
|
-
if (angleDeltaAbsDeg(next.roll, target.roll) <= tunables::kEpsAngleDeg)
|
|
60
|
-
next.roll = target.roll;
|
|
61
|
-
if ((1.0 - quatDotAbs(next.viewCorrection, target.viewCorrection))
|
|
62
|
-
<= tunables::kEpsQuatDot)
|
|
63
|
-
next.viewCorrection = target.viewCorrection;
|
|
64
|
-
|
|
65
|
-
return next;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
} // namespace reactnativecesium
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#pragma once
|
|
2
|
-
|
|
3
|
-
#include "GlobeCamera.hpp"
|
|
4
|
-
|
|
5
|
-
namespace reactnativecesium {
|
|
6
|
-
|
|
7
|
-
// Stateless helper. Given a current camera state and a target camera state,
|
|
8
|
-
// returns the next camera state for a frame of duration `dt` seconds.
|
|
9
|
-
//
|
|
10
|
-
// lat / lon / altitude / heading are snapped directly to the target.
|
|
11
|
-
// CameraTargetState::snapshot() has already linearly interpolated those DoFs
|
|
12
|
-
// to their "current" position based on each DoF's own EWMA inter-arrival
|
|
13
|
-
// interval, so snapping here gives constant-velocity motion between fixes
|
|
14
|
-
// with no ease-in/ease-out. At gesture rates (~60 Hz) the EWMA collapses to
|
|
15
|
-
// ~16 ms so the snap is indistinguishable from the previous behaviour.
|
|
16
|
-
//
|
|
17
|
-
// pitch / roll / viewCorrection use fixed-rate exponential decay
|
|
18
|
-
// `1 - exp(-k * dt)` with k from EngineTunables — appropriate for high-rate
|
|
19
|
-
// attitude sensors where the ease-out feel is desirable.
|
|
20
|
-
// Once a DoF drops below its matching epsilon it is snapped to exact equality.
|
|
21
|
-
class CameraSmoother {
|
|
22
|
-
public:
|
|
23
|
-
static CameraParams step(const CameraParams& current,
|
|
24
|
-
const CameraParams& target,
|
|
25
|
-
double dt);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
} // namespace reactnativecesium
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
#include "CameraTargetState.hpp"
|
|
2
|
-
|
|
3
|
-
#include <algorithm>
|
|
4
|
-
#include <cmath>
|
|
5
|
-
|
|
6
|
-
namespace reactnativecesium {
|
|
7
|
-
|
|
8
|
-
namespace {
|
|
9
|
-
|
|
10
|
-
inline double angleDeltaAbsDeg(double a, double b) {
|
|
11
|
-
return std::abs(std::fmod(b - a + 540.0, 360.0) - 180.0);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
inline double quatDotAbs(const glm::dquat& a, const glm::dquat& b) {
|
|
15
|
-
return std::abs(glm::dot(glm::normalize(a), glm::normalize(b)));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Shortest signed angular delta wrapped to [-180, 180].
|
|
19
|
-
inline double shortAngleDeltaDeg(double from, double to) {
|
|
20
|
-
return std::fmod(to - from + 540.0, 360.0) - 180.0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Advance an EWMA with a new sample, or seed it on first use.
|
|
24
|
-
inline void updateEwma(double& ewma, bool& active,
|
|
25
|
-
std::chrono::steady_clock::time_point& lastTime,
|
|
26
|
-
const std::chrono::steady_clock::time_point& now,
|
|
27
|
-
double seedSec) {
|
|
28
|
-
if (!active) {
|
|
29
|
-
ewma = seedSec;
|
|
30
|
-
lastTime = now;
|
|
31
|
-
active = true;
|
|
32
|
-
} else {
|
|
33
|
-
const double dt = std::chrono::duration<double>(now - lastTime).count();
|
|
34
|
-
if (dt > 0.0) {
|
|
35
|
-
ewma = ewma + tunables::kEwmaIntervalAlpha * (dt - ewma);
|
|
36
|
-
}
|
|
37
|
-
lastTime = now;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
} // namespace
|
|
42
|
-
|
|
43
|
-
// ── Public writers ────────────────────────────────────────────────────────────
|
|
44
|
-
|
|
45
|
-
void CameraTargetState::setAll(const CameraParams& target) {
|
|
46
|
-
{
|
|
47
|
-
std::lock_guard<std::mutex> g(mutex_);
|
|
48
|
-
target_ = target;
|
|
49
|
-
// Deliberately do NOT touch any interpolation state: setAll is init-only
|
|
50
|
-
// and the first rendered frame must snap to the requested position.
|
|
51
|
-
}
|
|
52
|
-
dirty_.store(true, std::memory_order_release);
|
|
53
|
-
forceRender_.store(true, std::memory_order_release);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
void CameraTargetState::setHpr(double lat, double lon, double alt,
|
|
57
|
-
double heading, double pitch, double roll) {
|
|
58
|
-
{
|
|
59
|
-
std::lock_guard<std::mutex> g(mutex_);
|
|
60
|
-
|
|
61
|
-
const auto now = std::chrono::steady_clock::now();
|
|
62
|
-
|
|
63
|
-
// ── lat / lon ─────────────────────────────────────────────────────────
|
|
64
|
-
const bool posChanged =
|
|
65
|
-
std::abs(lat - target_.latitude) > tunables::kEpsLatLon ||
|
|
66
|
-
std::abs(lon - target_.longitude) > tunables::kEpsLatLon;
|
|
67
|
-
|
|
68
|
-
if (posChanged) {
|
|
69
|
-
if (!hasPosInterp_) {
|
|
70
|
-
// First runtime change: seed "from" = destination for a clean snap,
|
|
71
|
-
// then enable interpolation for subsequent updates.
|
|
72
|
-
posFromLat_ = lat;
|
|
73
|
-
posFromLon_ = lon;
|
|
74
|
-
updateEwma(posEwmaIntervalSec_, hasPosInterp_, posUpdateTime_, now,
|
|
75
|
-
tunables::kSmoothPositionTauMax / tunables::kSmoothPositionAlpha);
|
|
76
|
-
} else {
|
|
77
|
-
posFromLat_ = target_.latitude;
|
|
78
|
-
posFromLon_ = target_.longitude;
|
|
79
|
-
updateEwma(posEwmaIntervalSec_, hasPosInterp_, posUpdateTime_, now,
|
|
80
|
-
tunables::kSmoothPositionTauMax / tunables::kSmoothPositionAlpha);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ── altitude ──────────────────────────────────────────────────────────
|
|
85
|
-
const bool altChanged =
|
|
86
|
-
std::abs(alt - target_.altitude) > tunables::kEpsAltitudeMeters;
|
|
87
|
-
|
|
88
|
-
if (altChanged) {
|
|
89
|
-
if (!hasAltInterp_) {
|
|
90
|
-
altFrom_ = alt;
|
|
91
|
-
updateEwma(altEwmaIntervalSec_, hasAltInterp_, altUpdateTime_, now,
|
|
92
|
-
tunables::kSmoothPositionTauMax / tunables::kSmoothPositionAlpha);
|
|
93
|
-
} else {
|
|
94
|
-
altFrom_ = target_.altitude;
|
|
95
|
-
updateEwma(altEwmaIntervalSec_, hasAltInterp_, altUpdateTime_, now,
|
|
96
|
-
tunables::kSmoothPositionTauMax / tunables::kSmoothPositionAlpha);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ── heading ───────────────────────────────────────────────────────────
|
|
101
|
-
const bool hdgChanged =
|
|
102
|
-
angleDeltaAbsDeg(heading, target_.heading) > tunables::kEpsAngleDeg;
|
|
103
|
-
|
|
104
|
-
if (hdgChanged) {
|
|
105
|
-
if (!hasHdgInterp_) {
|
|
106
|
-
hdgFrom_ = heading;
|
|
107
|
-
updateEwma(hdgEwmaIntervalSec_, hasHdgInterp_, hdgUpdateTime_, now,
|
|
108
|
-
tunables::kSmoothPositionTauMax / tunables::kSmoothPositionAlpha);
|
|
109
|
-
} else {
|
|
110
|
-
hdgFrom_ = target_.heading;
|
|
111
|
-
updateEwma(hdgEwmaIntervalSec_, hasHdgInterp_, hdgUpdateTime_, now,
|
|
112
|
-
tunables::kSmoothPositionTauMax / tunables::kSmoothPositionAlpha);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
target_.latitude = lat;
|
|
117
|
-
target_.longitude = lon;
|
|
118
|
-
target_.altitude = alt;
|
|
119
|
-
target_.heading = heading;
|
|
120
|
-
target_.pitch = pitch;
|
|
121
|
-
target_.roll = roll;
|
|
122
|
-
}
|
|
123
|
-
dirty_.store(true, std::memory_order_release);
|
|
124
|
-
forceRender_.store(true, std::memory_order_release);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
void CameraTargetState::setViewCorrection(const glm::dquat& q) {
|
|
128
|
-
{
|
|
129
|
-
std::lock_guard<std::mutex> g(mutex_);
|
|
130
|
-
target_.viewCorrection = q;
|
|
131
|
-
// Interpolation state is not touched — viewCorrection carries no
|
|
132
|
-
// positional information and must not affect any of the three EWMAs.
|
|
133
|
-
}
|
|
134
|
-
dirty_.store(true, std::memory_order_release);
|
|
135
|
-
forceRender_.store(true, std::memory_order_release);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── Snapshot (render-thread read) ─────────────────────────────────────────────
|
|
139
|
-
|
|
140
|
-
CameraParams CameraTargetState::snapshot() const {
|
|
141
|
-
const auto now = std::chrono::steady_clock::now();
|
|
142
|
-
std::lock_guard<std::mutex> g(mutex_);
|
|
143
|
-
|
|
144
|
-
CameraParams result = target_;
|
|
145
|
-
|
|
146
|
-
// ── lat / lon ─────────────────────────────────────────────────────────────
|
|
147
|
-
if (hasPosInterp_ && posEwmaIntervalSec_ > 0.0) {
|
|
148
|
-
const double dLat = target_.latitude - posFromLat_;
|
|
149
|
-
const double dLon = shortAngleDeltaDeg(posFromLon_, target_.longitude);
|
|
150
|
-
|
|
151
|
-
const bool snapThrough =
|
|
152
|
-
std::abs(dLat) > tunables::kPosSnapThroughDeg ||
|
|
153
|
-
std::abs(dLon) > tunables::kPosSnapThroughDeg;
|
|
154
|
-
|
|
155
|
-
if (!snapThrough) {
|
|
156
|
-
const double elapsed =
|
|
157
|
-
std::chrono::duration<double>(now - posUpdateTime_).count();
|
|
158
|
-
const double t = std::min(elapsed / posEwmaIntervalSec_, 1.0);
|
|
159
|
-
result.latitude = posFromLat_ + t * dLat;
|
|
160
|
-
result.longitude = posFromLon_ + t * dLon;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ── altitude ──────────────────────────────────────────────────────────────
|
|
165
|
-
if (hasAltInterp_ && altEwmaIntervalSec_ > 0.0) {
|
|
166
|
-
const double dAlt = target_.altitude - altFrom_;
|
|
167
|
-
|
|
168
|
-
if (std::abs(dAlt) <= tunables::kAltSnapThroughMeters) {
|
|
169
|
-
const double elapsed =
|
|
170
|
-
std::chrono::duration<double>(now - altUpdateTime_).count();
|
|
171
|
-
const double t = std::min(elapsed / altEwmaIntervalSec_, 1.0);
|
|
172
|
-
result.altitude = altFrom_ + t * dAlt;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ── heading ───────────────────────────────────────────────────────────────
|
|
177
|
-
if (hasHdgInterp_ && hdgEwmaIntervalSec_ > 0.0) {
|
|
178
|
-
const double dHdg = shortAngleDeltaDeg(hdgFrom_, target_.heading);
|
|
179
|
-
|
|
180
|
-
if (std::abs(dHdg) <= tunables::kHdgSnapThroughDeg) {
|
|
181
|
-
const double elapsed =
|
|
182
|
-
std::chrono::duration<double>(now - hdgUpdateTime_).count();
|
|
183
|
-
const double t = std::min(elapsed / hdgEwmaIntervalSec_, 1.0);
|
|
184
|
-
result.heading = hdgFrom_ + t * dHdg;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return result;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ── Epsilon check (shared) ────────────────────────────────────────────────────
|
|
192
|
-
|
|
193
|
-
bool CameraTargetState::deltaExceedsEpsilon(
|
|
194
|
-
const CameraParams& a, const CameraParams& b) {
|
|
195
|
-
return std::abs(a.latitude - b.latitude) > tunables::kEpsLatLon
|
|
196
|
-
|| std::abs(a.longitude - b.longitude) > tunables::kEpsLatLon
|
|
197
|
-
|| std::abs(a.altitude - b.altitude) > tunables::kEpsAltitudeMeters
|
|
198
|
-
|| angleDeltaAbsDeg(a.heading, b.heading) > tunables::kEpsAngleDeg
|
|
199
|
-
|| angleDeltaAbsDeg(a.pitch, b.pitch) > tunables::kEpsAngleDeg
|
|
200
|
-
|| angleDeltaAbsDeg(a.roll, b.roll) > tunables::kEpsAngleDeg
|
|
201
|
-
|| (1.0 - quatDotAbs(a.viewCorrection, b.viewCorrection))
|
|
202
|
-
> tunables::kEpsQuatDot;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
} // namespace reactnativecesium
|