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.
Files changed (39) hide show
  1. package/README.md +100 -27
  2. package/android/CMakeLists.txt +1 -2
  3. package/android/src/main/cpp/CesiumBridgeAndroid.cpp +264 -130
  4. package/android/src/main/cpp/CesiumBridgeAndroid.h +70 -27
  5. package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +68 -44
  6. package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +160 -187
  7. package/cpp/engine/CameraIntegrator.cpp +374 -0
  8. package/cpp/engine/CameraIntegrator.hpp +146 -0
  9. package/cpp/engine/EngineTunables.hpp +78 -31
  10. package/cpp/engine/GlobeCamera.cpp +17 -6
  11. package/cpp/engine/GlobeCamera.hpp +11 -8
  12. package/cpp/engine/test/CameraIntegratorTest.cpp +205 -0
  13. package/cpp/engine/test/build_and_run.sh +27 -0
  14. package/example/README.md +1 -1
  15. package/ios/CesiumBridge.h +53 -28
  16. package/ios/CesiumBridge.mm +172 -86
  17. package/ios/HybridCesiumView.swift +213 -145
  18. package/lib/typescript/src/specs/cesium-view.nitro.d.ts +77 -13
  19. package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
  20. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +90 -9
  21. package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +19 -3
  22. package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +20 -0
  23. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +57 -3
  24. package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +9 -9
  25. package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +82 -9
  26. package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +25 -0
  27. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +14 -3
  28. package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +210 -16
  29. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +19 -3
  30. package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +20 -4
  31. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +55 -0
  32. package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +5 -0
  33. package/nitrogen/generated/shared/json/CesiumViewConfig.json +5 -0
  34. package/package.json +1 -1
  35. package/src/specs/cesium-view.nitro.ts +93 -14
  36. package/cpp/engine/CameraSmoother.cpp +0 -68
  37. package/cpp/engine/CameraSmoother.hpp +0 -28
  38. package/cpp/engine/CameraTargetState.cpp +0 -205
  39. package/cpp/engine/CameraTargetState.hpp +0 -126
@@ -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
@@ -1,126 +0,0 @@
1
- #pragma once
2
-
3
- #include "EngineTunables.hpp"
4
- #include "GlobeCamera.hpp"
5
-
6
- #include <atomic>
7
- #include <chrono>
8
- #include <mutex>
9
-
10
- namespace reactnativecesium {
11
-
12
- // Thread-safe holder for the *demand* camera (what the JS side last asked for).
13
- //
14
- // Writers: the React Native JS / worklet thread, via setCamera / setCameraQuaternion.
15
- // Readers: the dedicated render thread (per frame), via shouldRenderNextFrame /
16
- // renderFrame.
17
- //
18
- // We use a small mutex around `target_` because the writes are uncontended and
19
- // the read side only happens once per frame; the cost is negligible compared to
20
- // the cost of *not* synchronising a 56-byte struct that contains a glm::dquat.
21
- //
22
- // `dirty_` and `forceRender_` are independent atomic flags so the read side can
23
- // poll without taking the lock when the camera has not moved.
24
- //
25
- // Per-DoF linear interpolation (lat/lon, altitude, heading)
26
- // ──────────────────────────────────────────────────────────
27
- // Each of lat/lon, altitude, and heading maintains its own independent EWMA
28
- // of inter-arrival times and only updates that EWMA when *its own value*
29
- // actually changes. snapshot() then linearly interpolates each DoF from its
30
- // previous value to its current target over its own EWMA interval.
31
- //
32
- // This means lat/lon, altitude, and heading each self-tune independently:
33
- // - 60 Hz gesture input → EWMA ~16 ms → interpolation near-instant (snap)
34
- // - 1 Hz GPS → EWMA ~1 s → constant-velocity glide between fixes
35
- // - 50 Hz barometer for altitude + 1 Hz GPS for lat/lon → altitude behaves
36
- // near-instant, lat/lon glides — with no special-casing needed.
37
- //
38
- // Special cases in snapshot():
39
- // - t >= 1: camera has reached the target, returns target_ directly.
40
- // - Snap-through guard: large jumps bypass interpolation so deliberate
41
- // teleports remain instant.
42
- // - First change per DoF: "from" = destination, so lerp(dest, dest, t) = dest
43
- // for all t — clean snap, no startup glide.
44
- // - setAll (init-only): never touches any interpolation state.
45
- //
46
- // pitch / roll / viewCorrection are NOT interpolated here; CameraSmoother
47
- // applies fixed-rate exponential curves to them (appropriate for high-rate
48
- // attitude sensors).
49
- class CameraTargetState {
50
- public:
51
- CameraTargetState() = default;
52
-
53
- // Replace the entire target. Used only for engine initialisation; does NOT
54
- // update any interpolation state so the first rendered frame snaps.
55
- void setAll(const CameraParams& target);
56
-
57
- // Update lat/lon/alt + heading/pitch/roll, leaving viewCorrection unchanged.
58
- // Independently tracks cadence for lat/lon, altitude, and heading.
59
- void setHpr(double lat, double lon, double alt,
60
- double heading, double pitch, double roll);
61
-
62
- void setViewCorrection(const glm::dquat& q);
63
-
64
- // Snapshot the demand target for this render frame. lat/lon, altitude, and
65
- // heading are linearly interpolated to their "current" position based on
66
- // each DoF's own EWMA interval. pitch/roll/viewCorrection are returned as-is
67
- // (CameraSmoother handles their exponential curves).
68
- CameraParams snapshot() const;
69
-
70
- // Lightweight, lock-free check used by the render thread to decide whether
71
- // it should bother taking a full snapshot at all.
72
- bool isDirty() const noexcept {
73
- return dirty_.load(std::memory_order_acquire);
74
- }
75
- void clearDirty() noexcept {
76
- dirty_.store(false, std::memory_order_release);
77
- }
78
-
79
- bool consumeForceRender() noexcept {
80
- return forceRender_.exchange(false, std::memory_order_acq_rel);
81
- }
82
- void requestForceRender() noexcept {
83
- forceRender_.store(true, std::memory_order_release);
84
- }
85
-
86
- // Compares snapshot to "current" (the engine's current camera) and returns
87
- // true if any DoF differs by more than the matching epsilon. Used to decide
88
- // whether to render this frame.
89
- static bool deltaExceedsEpsilon(const CameraParams& a, const CameraParams& b);
90
-
91
- private:
92
- mutable std::mutex mutex_;
93
- CameraParams target_;
94
- std::atomic<bool> dirty_{false};
95
- std::atomic<bool> forceRender_{true};
96
-
97
- // ── Per-DoF linear interpolation state (all protected by mutex_) ─────────
98
- // Each DoF has: a "from" value, a wall-clock timestamp of the last change,
99
- // an EWMA of inter-arrival times, and an "active" flag.
100
- //
101
- // Pattern per DoF:
102
- // First change: from = destination (snap), seed EWMA, set active.
103
- // Subsequent change: from = old target, update EWMA, reset clock.
104
- // snapshot(): t = clamp(elapsed/ewma, 0, 1); result = from + t*delta.
105
-
106
- // lat / lon
107
- double posFromLat_{0.0};
108
- double posFromLon_{0.0};
109
- std::chrono::steady_clock::time_point posUpdateTime_{};
110
- double posEwmaIntervalSec_{0.0};
111
- bool hasPosInterp_{false};
112
-
113
- // altitude
114
- double altFrom_{0.0};
115
- std::chrono::steady_clock::time_point altUpdateTime_{};
116
- double altEwmaIntervalSec_{0.0};
117
- bool hasAltInterp_{false};
118
-
119
- // heading
120
- double hdgFrom_{0.0};
121
- std::chrono::steady_clock::time_point hdgUpdateTime_{};
122
- double hdgEwmaIntervalSec_{0.0};
123
- bool hasHdgInterp_{false};
124
- };
125
-
126
- } // namespace reactnativecesium