react-native-cesium 1.2.18 → 1.2.20
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 +11 -8
- package/android/CMakeLists.txt +1 -0
- package/android/build.gradle +1 -1
- package/android/gradle.properties +4 -4
- package/android/src/main/cpp/CesiumBridgeAndroid.cpp +44 -18
- package/android/src/main/cpp/CesiumBridgeAndroid.h +9 -0
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/CesiumBridgeJNI.kt +3 -0
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +9 -0
- package/cpp/engine/CameraIntegrator.cpp +82 -47
- package/cpp/engine/CameraIntegrator.hpp +27 -8
- package/cpp/engine/CesiumEngine.cpp +183 -312
- package/cpp/engine/CesiumEngine.hpp +41 -28
- package/cpp/engine/EngineTunables.hpp +6 -5
- package/cpp/engine/GeoidConverter.cpp +32 -11
- package/cpp/engine/GeoidConverter.hpp +41 -0
- package/cpp/engine/GltfToMesh.cpp +14 -5
- package/cpp/engine/ResourcePreparer.cpp +3 -3
- package/cpp/engine/ResourcePreparer.hpp +1 -1
- package/cpp/engine/TerrainFloor.hpp +70 -0
- package/cpp/engine/TerrainHeight.hpp +113 -0
- package/cpp/engine/test/CameraIntegratorTest.cpp +264 -0
- package/cpp/engine/test/GeoidConverterTest.cpp +77 -0
- package/cpp/engine/test/build_and_run.sh +20 -6
- package/ios/CesiumBridge.h +5 -0
- package/ios/CesiumBridge.mm +61 -32
- package/ios/HybridCesiumView.swift +11 -0
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts +34 -9
- package/lib/typescript/src/specs/cesium-view.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JCameraState.hpp +1 -1
- package/nitrogen/generated/android/c++/JCesiumMetrics.hpp +1 -1
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.cpp +9 -0
- package/nitrogen/generated/android/c++/JHybridCesiumViewSpec.hpp +2 -0
- package/nitrogen/generated/android/c++/JQuaternion.hpp +1 -1
- package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.cpp +182 -131
- package/nitrogen/generated/android/c++/views/JHybridCesiumViewStateUpdater.hpp +6 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CameraState.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/CesiumMetrics.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Func_void_CameraState.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Func_void_CesiumMetrics.kt +0 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/HybridCesiumViewSpec.kt +8 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/Quaternion.kt +1 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewManager.kt +26 -10
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativecesium/views/HybridCesiumViewStateUpdater.kt +2 -2
- package/nitrogen/generated/ios/ReactNativeCesium-Swift-Cxx-Bridge.hpp +15 -0
- package/nitrogen/generated/ios/c++/HybridCesiumViewSpecSwift.hpp +7 -0
- package/nitrogen/generated/ios/c++/views/HybridCesiumViewComponent.mm +217 -161
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridCesiumViewSpec_cxx.swift +24 -0
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridCesiumViewSpec.hpp +2 -0
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.cpp +36 -335
- package/nitrogen/generated/shared/c++/views/HybridCesiumViewComponent.hpp +110 -77
- package/nitrogen/generated/shared/json/CesiumViewConfig.json +1 -0
- package/package.json +12 -12
- package/scripts/cesium/config.mjs +1 -1
- package/scripts/cesium/vcpkg-triplets/arm64-ios-simulator.cmake +3 -0
- package/scripts/cesium/vcpkg-triplets/arm64-ios.cmake +4 -0
- package/src/specs/cesium-view.nitro.ts +34 -9
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
#include "../CameraIntegrator.hpp"
|
|
13
13
|
#include "../EngineTunables.hpp"
|
|
14
|
+
#include "../TerrainHeight.hpp"
|
|
14
15
|
|
|
16
|
+
#include <algorithm>
|
|
15
17
|
#include <chrono>
|
|
16
18
|
#include <cmath>
|
|
17
19
|
#include <cstdio>
|
|
@@ -189,6 +191,257 @@ static void testTerrainClamp() {
|
|
|
189
191
|
CHECK(approxEq(demand.altitude, 100.0, 1e-6), "terrain_clamp.demand_preserved");
|
|
190
192
|
}
|
|
191
193
|
|
|
194
|
+
static void followFloors(const double* floors, int count, const char* label,
|
|
195
|
+
bool expectRiseThenFall) {
|
|
196
|
+
CameraIntegrator integ;
|
|
197
|
+
CameraParams seed;
|
|
198
|
+
seed.altitude = floors[0];
|
|
199
|
+
integ.teleport(seed);
|
|
200
|
+
// A demand under every floor, plus a downward velocity sample, must not
|
|
201
|
+
// pull the rendered camera through the surface.
|
|
202
|
+
integ.setAltitude(0.0);
|
|
203
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
204
|
+
integ.setAltitude(0.0);
|
|
205
|
+
|
|
206
|
+
double prev = 0.0;
|
|
207
|
+
bool havePrev = false;
|
|
208
|
+
bool sawPeak = false;
|
|
209
|
+
for (int i = 0; i < count; ++i) {
|
|
210
|
+
const double floor = floors[i];
|
|
211
|
+
integ.setAltitude(0.0);
|
|
212
|
+
integ.setTerrainFloor(floor);
|
|
213
|
+
const CameraParams actual = integ.step();
|
|
214
|
+
char buf[96];
|
|
215
|
+
std::snprintf(buf, sizeof(buf), "%s.exact_%d", label, i);
|
|
216
|
+
CHECK(approxEq(actual.altitude, floor, 1e-6), buf);
|
|
217
|
+
CHECK(approxEq(integ.getDemand().altitude, 0.0, 1e-9), label);
|
|
218
|
+
if (havePrev) {
|
|
219
|
+
if (!expectRiseThenFall) {
|
|
220
|
+
std::snprintf(buf, sizeof(buf), "%s.monotonic_%d", label, i);
|
|
221
|
+
if (floors[i] > floors[i - 1]) {
|
|
222
|
+
CHECK(actual.altitude > prev - 1e-6, buf);
|
|
223
|
+
} else {
|
|
224
|
+
CHECK(actual.altitude < prev + 1e-6, buf);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (expectRiseThenFall && floors[i] < floors[i - 1]) {
|
|
228
|
+
sawPeak = true;
|
|
229
|
+
std::snprintf(buf, sizeof(buf), "%s.descends_%d", label, i);
|
|
230
|
+
CHECK(actual.altitude < prev - 1e-6, buf);
|
|
231
|
+
}
|
|
232
|
+
if (expectRiseThenFall && sawPeak && floors[i] > floors[i - 1]) {
|
|
233
|
+
std::snprintf(buf, sizeof(buf), "%s.climbs_%d", label, i);
|
|
234
|
+
CHECK(actual.altitude > prev + 1e-6, buf);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
prev = actual.altitude;
|
|
238
|
+
havePrev = true;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
static void testUphillContact() {
|
|
243
|
+
const double floors[] = {100.0, 110.0, 120.0, 130.0};
|
|
244
|
+
followFloors(floors, 4, "uphill", false);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
static void testDownhillContact() {
|
|
248
|
+
const double floors[] = {130.0, 120.0, 110.0, 100.0};
|
|
249
|
+
followFloors(floors, 4, "downhill", false);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static void testCrest() {
|
|
253
|
+
const double floors[] = {100.0, 110.0, 120.0, 110.0, 100.0};
|
|
254
|
+
followFloors(floors, 5, "crest", true);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static void testValley() {
|
|
258
|
+
const double floors[] = {120.0, 110.0, 100.0, 110.0, 120.0};
|
|
259
|
+
followFloors(floors, 5, "valley", true);
|
|
260
|
+
|
|
261
|
+
// Descent velocity must not carry the camera into the rising face.
|
|
262
|
+
CameraIntegrator integ;
|
|
263
|
+
CameraParams seed;
|
|
264
|
+
seed.altitude = 100.0;
|
|
265
|
+
integ.teleport(seed);
|
|
266
|
+
integ.clearTerrainFloor();
|
|
267
|
+
integ.setAltitude(0.0);
|
|
268
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
269
|
+
integ.setAltitude(0.0);
|
|
270
|
+
CHECK(integ.getActual().altitude < 100.0, "valley.velocity_descended");
|
|
271
|
+
integ.setTerrainFloor(110.0);
|
|
272
|
+
CHECK(approxEq(integ.getActual().altitude, 110.0, 1e-6), "valley.climb_exact");
|
|
273
|
+
CHECK(approxEq(integ.step().altitude, 110.0, 1e-6), "valley.climb_holds");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
static void testSetAltitudeUnderFloor() {
|
|
277
|
+
CameraIntegrator integ;
|
|
278
|
+
CameraParams seed;
|
|
279
|
+
seed.altitude = 100.0;
|
|
280
|
+
integ.teleport(seed);
|
|
281
|
+
integ.setTerrainFloor(150.0);
|
|
282
|
+
integ.setAltitude(10.0);
|
|
283
|
+
CHECK(approxEq(integ.getActual().altitude, 150.0, 1e-6), "under_floor.actual");
|
|
284
|
+
CHECK(approxEq(integ.getDemand().altitude, 10.0, 1e-9), "under_floor.demand");
|
|
285
|
+
const CameraParams stepped = integ.step();
|
|
286
|
+
CHECK(approxEq(stepped.altitude, 150.0, 1e-6), "under_floor.after_step");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
static void testIdleOnFloor() {
|
|
290
|
+
CameraIntegrator integ;
|
|
291
|
+
CameraParams seed;
|
|
292
|
+
seed.altitude = 100.0;
|
|
293
|
+
integ.teleport(seed);
|
|
294
|
+
integ.setTerrainFloor(150.0);
|
|
295
|
+
integ.setAltitude(40.0);
|
|
296
|
+
const CameraParams stepped = integ.step();
|
|
297
|
+
CHECK(approxEq(stepped.altitude, 150.0, 1e-6), "idle_floor.actual");
|
|
298
|
+
CHECK(!integ.isActive(), "idle_floor.inactive");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
static void testFlyingAboveFloor() {
|
|
302
|
+
CameraIntegrator integ;
|
|
303
|
+
CameraParams seed;
|
|
304
|
+
seed.altitude = 500.0;
|
|
305
|
+
integ.teleport(seed);
|
|
306
|
+
integ.setTerrainFloor(100.0);
|
|
307
|
+
const CameraParams stepped = integ.step();
|
|
308
|
+
CHECK(approxEq(stepped.altitude, 500.0, 1e-3), "above_floor.untouched");
|
|
309
|
+
CHECK(approxEq(integ.getDemand().altitude, 500.0, 1e-9), "above_floor.demand");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
static double wrap180(double deg) {
|
|
313
|
+
double v = std::fmod(deg + 180.0, 360.0);
|
|
314
|
+
if (v < 0.0) v += 360.0;
|
|
315
|
+
return v - 180.0;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
static void testHeadingShortPull() {
|
|
319
|
+
CameraIntegrator integ;
|
|
320
|
+
CameraParams seed;
|
|
321
|
+
seed.heading = 350.0;
|
|
322
|
+
integ.teleport(seed);
|
|
323
|
+
integ.setHeading(10.0);
|
|
324
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
|
325
|
+
bool onShortArc = true;
|
|
326
|
+
for (int i = 0; i < 90; ++i) {
|
|
327
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
328
|
+
const double h = integ.step().heading;
|
|
329
|
+
if (!(h >= 330.0 || h <= 40.0)) onShortArc = false;
|
|
330
|
+
}
|
|
331
|
+
CHECK(onShortArc, "heading_pull.short_arc");
|
|
332
|
+
const double end = integ.getActual().heading;
|
|
333
|
+
const double err = std::abs(wrap180(end - 10.0));
|
|
334
|
+
CHECK(err < 2.0, "heading_pull.converged");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
static void testLongitudeShortPull() {
|
|
338
|
+
CameraIntegrator integ;
|
|
339
|
+
CameraParams seed;
|
|
340
|
+
seed.longitude = 179.0;
|
|
341
|
+
integ.teleport(seed);
|
|
342
|
+
integ.setPosition(seed.latitude, -179.0);
|
|
343
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
|
344
|
+
bool onShortArc = true;
|
|
345
|
+
for (int i = 0; i < 90; ++i) {
|
|
346
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
347
|
+
const double wrapped = wrap180(integ.step().longitude);
|
|
348
|
+
if (!(std::abs(wrapped) > 90.0)) onShortArc = false;
|
|
349
|
+
}
|
|
350
|
+
CHECK(onShortArc, "lon_pull.short_arc");
|
|
351
|
+
const double err = std::abs(wrap180(integ.getActual().longitude - (-179.0)));
|
|
352
|
+
CHECK(err < 2.0, "lon_pull.converged");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
static void testVerticalFovConverges() {
|
|
356
|
+
CameraIntegrator integ;
|
|
357
|
+
CameraParams seed;
|
|
358
|
+
seed.verticalFov = 60.0;
|
|
359
|
+
integ.teleport(seed);
|
|
360
|
+
integ.setVerticalFov(30.0);
|
|
361
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
|
362
|
+
const double first = integ.step().verticalFov;
|
|
363
|
+
CHECK(first < 59.0 && first > 40.0, "vfov.first_step_eases");
|
|
364
|
+
CameraParams mid;
|
|
365
|
+
for (int i = 0; i < 80; ++i) {
|
|
366
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
|
367
|
+
mid = integ.step();
|
|
368
|
+
}
|
|
369
|
+
CHECK(approxEq(mid.verticalFov, 30.0, 0.5), "vfov.converged");
|
|
370
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
|
371
|
+
const double held = integ.step().verticalFov;
|
|
372
|
+
CHECK(approxEq(held, 30.0, 0.5), "vfov.stays");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
static void testLeadAndTriangle() {
|
|
376
|
+
float out = 0.0f;
|
|
377
|
+
CHECK(resolveSurfaceEllipsoid(true, 100.0f, true, 90.0f, out) && out == 100.0f,
|
|
378
|
+
"lead.crest_no_sticky_peak");
|
|
379
|
+
CHECK(resolveSurfaceEllipsoid(true, 100.0f, true, 110.0f, out) && out == 110.0f,
|
|
380
|
+
"lead.valley_raises");
|
|
381
|
+
CHECK(resolveSurfaceEllipsoid(true, 120.0f, false, 0.0f, out) && out == 120.0f,
|
|
382
|
+
"lead.nadir_only");
|
|
383
|
+
CHECK(!resolveSurfaceEllipsoid(false, 0.0f, false, 0.0f, out), "lead.neither");
|
|
384
|
+
|
|
385
|
+
double lat = 0.0, lon = 0.0;
|
|
386
|
+
const bool slew = terrainLeadLatLon(0.0, 1.0, 0.0, 0.0, true, lat, lon);
|
|
387
|
+
CHECK(!slew, "lead.slew_ignored");
|
|
388
|
+
|
|
389
|
+
const bool step = terrainLeadLatLon(0.0, 0.0001, 0.0, 0.0, true, lat, lon);
|
|
390
|
+
CHECK(step, "lead.one_frame");
|
|
391
|
+
CHECK(approxEq(lat, 0.0, 1e-12), "lead.lat");
|
|
392
|
+
CHECK(approxEq(lon, 0.0002, 1e-9), "lead.lon_ahead");
|
|
393
|
+
|
|
394
|
+
float h = -1.0f;
|
|
395
|
+
const bool hit = horizontalTriangleHeight(
|
|
396
|
+
2.0, 2.0,
|
|
397
|
+
0.0, 0.0, 0.0f,
|
|
398
|
+
10.0, 0.0, 0.0f,
|
|
399
|
+
0.0, 10.0, 30.0f,
|
|
400
|
+
h);
|
|
401
|
+
CHECK(hit && approxEq(h, 6.0, 1e-3), "tri.interpolated");
|
|
402
|
+
|
|
403
|
+
float outside = 0.0f;
|
|
404
|
+
CHECK(!horizontalTriangleHeight(
|
|
405
|
+
20.0, 20.0,
|
|
406
|
+
0.0, 0.0, 0.0f,
|
|
407
|
+
10.0, 0.0, 0.0f,
|
|
408
|
+
0.0, 10.0, 30.0f,
|
|
409
|
+
outside),
|
|
410
|
+
"tri.outside");
|
|
411
|
+
|
|
412
|
+
float skirt = 0.0f;
|
|
413
|
+
CHECK(!horizontalTriangleHeight(
|
|
414
|
+
1.0, 0.0,
|
|
415
|
+
0.0, 0.0, 100.0f,
|
|
416
|
+
0.0, 0.0, 0.0f,
|
|
417
|
+
5.0, 0.0, 100.0f,
|
|
418
|
+
skirt),
|
|
419
|
+
"tri.skirt_rejected");
|
|
420
|
+
// 100 m edge, 1 mm of horizontal width: a float-error skirt, not terrain.
|
|
421
|
+
CHECK(!horizontalTriangleHeight(
|
|
422
|
+
0.0, 0.0005,
|
|
423
|
+
0.0, 0.0, 100.0f,
|
|
424
|
+
100.0, 0.0, 100.0f,
|
|
425
|
+
0.0, 0.001, 0.0f,
|
|
426
|
+
skirt),
|
|
427
|
+
"tri.thin_skirt_rejected");
|
|
428
|
+
|
|
429
|
+
float low = 0.0f, high = 0.0f;
|
|
430
|
+
const bool lowHit = horizontalTriangleHeight(
|
|
431
|
+
2.0, 2.0,
|
|
432
|
+
0.0, 0.0, 5.0f,
|
|
433
|
+
10.0, 0.0, 5.0f,
|
|
434
|
+
0.0, 10.0, 5.0f,
|
|
435
|
+
low);
|
|
436
|
+
const bool highHit = horizontalTriangleHeight(
|
|
437
|
+
2.0, 2.0,
|
|
438
|
+
0.0, 0.0, 20.0f,
|
|
439
|
+
10.0, 0.0, 20.0f,
|
|
440
|
+
0.0, 10.0, 20.0f,
|
|
441
|
+
high);
|
|
442
|
+
CHECK(lowHit && highHit && std::max(low, high) == 20.0f, "tri.overlap_keeps_higher");
|
|
443
|
+
}
|
|
444
|
+
|
|
192
445
|
int main() {
|
|
193
446
|
testStepInput();
|
|
194
447
|
testConstantVelocity();
|
|
@@ -196,6 +449,17 @@ int main() {
|
|
|
196
449
|
testLongPause();
|
|
197
450
|
testGestureEndBleed();
|
|
198
451
|
testTerrainClamp();
|
|
452
|
+
testUphillContact();
|
|
453
|
+
testDownhillContact();
|
|
454
|
+
testCrest();
|
|
455
|
+
testValley();
|
|
456
|
+
testSetAltitudeUnderFloor();
|
|
457
|
+
testIdleOnFloor();
|
|
458
|
+
testFlyingAboveFloor();
|
|
459
|
+
testHeadingShortPull();
|
|
460
|
+
testLongitudeShortPull();
|
|
461
|
+
testVerticalFovConverges();
|
|
462
|
+
testLeadAndTriangle();
|
|
199
463
|
if (g_failures == 0) {
|
|
200
464
|
std::fprintf(stderr, "\nALL TESTS PASSED.\n");
|
|
201
465
|
} else {
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Host check for the EGM96 MSL <-> ellipsoid conversion.
|
|
2
|
+
//
|
|
3
|
+
// clang++ -std=c++20 -I cpp -o /tmp/geoid cpp/engine/test/GeoidConverterTest.cpp \
|
|
4
|
+
// cpp/engine/GeoidConverter.cpp cpp/third_party/egm96/EGM96.c
|
|
5
|
+
//
|
|
6
|
+
// Or: bash cpp/engine/test/build_and_run.sh
|
|
7
|
+
|
|
8
|
+
#include "../GeoidConverter.hpp"
|
|
9
|
+
|
|
10
|
+
#include <cmath>
|
|
11
|
+
#include <cstdio>
|
|
12
|
+
|
|
13
|
+
using namespace reactnativecesium;
|
|
14
|
+
|
|
15
|
+
static int g_failures = 0;
|
|
16
|
+
|
|
17
|
+
static void check(bool ok, const char* label) {
|
|
18
|
+
if (!ok) {
|
|
19
|
+
std::fprintf(stderr, "FAIL [%s]\n", label);
|
|
20
|
+
++g_failures;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
std::fprintf(stderr, "PASS [%s]\n", label);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static void checkInverse(const char* label, double lat, double lon, double msl) {
|
|
27
|
+
const double hae = mslToEllipsoidMeters(lat, lon, msl);
|
|
28
|
+
const double back = ellipsoidToMslMeters(lat, lon, hae);
|
|
29
|
+
const double undulation = hae - msl;
|
|
30
|
+
if (!std::isfinite(hae) || !std::isfinite(back) || std::fabs(back - msl) > 1e-6) {
|
|
31
|
+
std::fprintf(stderr, "FAIL [%s]: msl=%.3f hae=%.3f back=%.3f\n", label, msl, hae, back);
|
|
32
|
+
++g_failures;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (std::fabs(undulation) < 1.0) {
|
|
36
|
+
std::fprintf(stderr, "FAIL [%s]: undulation %.3f m is unexpectedly ~0\n", label, undulation);
|
|
37
|
+
++g_failures;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
std::fprintf(stderr, "PASS [%s] undulation %.2f m\n", label, undulation);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static void checkDemandRoundTrip(const char* label, double lat, double lon, double msl) {
|
|
44
|
+
const double hae = mslToDemandAltitudeMeters(lat, lon, msl, true);
|
|
45
|
+
const double back = demandAltitudeToMslMeters(lat, lon, hae, true);
|
|
46
|
+
check(std::isfinite(hae) && std::isfinite(back) && std::fabs(back - msl) < 1e-6, label);
|
|
47
|
+
|
|
48
|
+
const double mslIdentity = demandAltitudeToMslMeters(lat, lon, msl, false);
|
|
49
|
+
const double haeIdentity = mslToDemandAltitudeMeters(lat, lon, msl, false);
|
|
50
|
+
check(mslIdentity == msl && haeIdentity == msl, "datum_msl_identity");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
int main() {
|
|
54
|
+
// UK geoid is about +45 m. Alps about +50 m. Indian Ocean is negative.
|
|
55
|
+
checkInverse("uk", 51.5, -0.1, 1000.0);
|
|
56
|
+
checkInverse("alps", 46.0, 8.0, 2000.0);
|
|
57
|
+
checkInverse("indian_ocean", -10.0, 80.0, 50.0);
|
|
58
|
+
|
|
59
|
+
const double nan = std::nan("");
|
|
60
|
+
check(ellipsoidToMslMeters(nan, 0.0, 10.0) == 10.0, "ellipsoid_non_finite");
|
|
61
|
+
check(mslToEllipsoidMeters(nan, 0.0, 10.0) == 10.0, "msl_non_finite");
|
|
62
|
+
|
|
63
|
+
checkDemandRoundTrip("demand_round_trip", 51.5, -0.1, 1000.0);
|
|
64
|
+
|
|
65
|
+
constexpr double kUkLat = 51.5;
|
|
66
|
+
constexpr double kUkLon = -0.1;
|
|
67
|
+
constexpr double kOceanLat = -10.0;
|
|
68
|
+
constexpr double kOceanLon = 80.0;
|
|
69
|
+
constexpr double kMsl = 1000.0;
|
|
70
|
+
const double hae = mslToEllipsoidMeters(kUkLat, kUkLon, kMsl);
|
|
71
|
+
const double moved = mslAfterHorizontalMove(kUkLat, kUkLon, kMsl, kOceanLat, kOceanLon);
|
|
72
|
+
const double movedHae = mslToEllipsoidMeters(kOceanLat, kOceanLon, moved);
|
|
73
|
+
check(std::isfinite(moved) && std::fabs(movedHae - hae) < 1e-6, "move_preserves_hae");
|
|
74
|
+
check(std::fabs(moved - kMsl) > 1.0, "move_changes_msl");
|
|
75
|
+
|
|
76
|
+
return g_failures == 0 ? 0 : 1;
|
|
77
|
+
}
|
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Compile and run
|
|
3
|
-
#
|
|
4
|
-
# not part of the
|
|
2
|
+
# Compile and run the host engine tests. GeoidConverterTest needs only EGM96.
|
|
3
|
+
# CameraIntegratorTest needs the iOS simulator-slice headers from the vendored
|
|
4
|
+
# Cesium Native XCFramework. Lightweight smoke tests — not part of the
|
|
5
|
+
# production build.
|
|
5
6
|
|
|
6
7
|
set -euo pipefail
|
|
7
8
|
|
|
8
9
|
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
|
|
9
10
|
HEADERS="$ROOT/vendor/ios/CesiumNative.xcframework/ios-arm64-simulator/Headers"
|
|
11
|
+
OUT="$(mktemp -d)"
|
|
12
|
+
echo "Building into $OUT..."
|
|
13
|
+
|
|
14
|
+
xcrun -sdk macosx clang -std=c11 -O0 -g \
|
|
15
|
+
-I"$ROOT/cpp" \
|
|
16
|
+
-c "$ROOT/cpp/third_party/egm96/EGM96.c" \
|
|
17
|
+
-o "$OUT/EGM96.o"
|
|
18
|
+
|
|
19
|
+
xcrun -sdk macosx clang++ -std=c++20 -O0 -g \
|
|
20
|
+
-I"$ROOT/cpp" \
|
|
21
|
+
-o "$OUT/geoid" \
|
|
22
|
+
"$ROOT/cpp/engine/test/GeoidConverterTest.cpp" \
|
|
23
|
+
"$ROOT/cpp/engine/GeoidConverter.cpp" \
|
|
24
|
+
"$OUT/EGM96.o"
|
|
25
|
+
|
|
26
|
+
"$OUT/geoid"
|
|
10
27
|
|
|
11
28
|
if [[ ! -d "$HEADERS" ]]; then
|
|
12
29
|
echo "Cesium Native headers not found at $HEADERS"
|
|
@@ -14,9 +31,6 @@ if [[ ! -d "$HEADERS" ]]; then
|
|
|
14
31
|
exit 1
|
|
15
32
|
fi
|
|
16
33
|
|
|
17
|
-
OUT="$(mktemp -d)"
|
|
18
|
-
echo "Building into $OUT..."
|
|
19
|
-
|
|
20
34
|
xcrun -sdk macosx clang++ -std=c++20 -O0 -g \
|
|
21
35
|
-DGLM_FORCE_DEPTH_ZERO_TO_ONE=1 \
|
|
22
36
|
-I"$ROOT/cpp" -I"$HEADERS" \
|
package/ios/CesiumBridge.h
CHANGED
|
@@ -33,6 +33,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
33
33
|
// instant. Pitch / roll are bundled because they typically come from a
|
|
34
34
|
// single IMU sample.
|
|
35
35
|
- (void)setPositionLatitude:(double)lat longitude:(double)lon;
|
|
36
|
+
/// When YES, altitudes passed to setAltitude: and teleport are WGS84
|
|
37
|
+
/// ellipsoidal heights. They are converted to MSL before the integrator, and
|
|
38
|
+
/// altitude readback is converted back. A horizontal move keeps that
|
|
39
|
+
/// ellipsoidal height.
|
|
40
|
+
- (void)setAltitudeDatumEllipsoid:(BOOL)ellipsoid;
|
|
36
41
|
- (void)setAltitude:(double)alt;
|
|
37
42
|
- (void)setHeadingDeg:(double)deg;
|
|
38
43
|
- (void)setAttitudePitch:(double)pitch roll:(double)roll;
|
package/ios/CesiumBridge.mm
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#include "engine/EngineTunables.hpp"
|
|
7
7
|
#include "engine/GeoidConverter.hpp"
|
|
8
8
|
#include "engine/MetricsAggregator.hpp"
|
|
9
|
+
#include "engine/TerrainFloor.hpp"
|
|
9
10
|
#include "metal/MetalBackend.h"
|
|
10
11
|
|
|
11
12
|
#include <glm/gtc/quaternion.hpp>
|
|
@@ -35,6 +36,9 @@
|
|
|
35
36
|
int _viewportHeight;
|
|
36
37
|
BOOL _initialized;
|
|
37
38
|
BOOL _suspended;
|
|
39
|
+
// Written when the prop changes; read from the caller thread inside
|
|
40
|
+
// setAltitude / setPosition / teleport / readback.
|
|
41
|
+
std::atomic<bool> _altitudeIsEllipsoid;
|
|
38
42
|
|
|
39
43
|
// Last wall-clock tick used to compute the metrics-only frame dt EMA. Kept
|
|
40
44
|
// per-instance (not a function-local static) so a hot reload that destroys
|
|
@@ -48,6 +52,8 @@
|
|
|
48
52
|
// Sentinel (epoch) means "no loading seen yet".
|
|
49
53
|
std::chrono::steady_clock::time_point _lastLoadingTime;
|
|
50
54
|
|
|
55
|
+
reactnativecesium::TerrainLeadState _terrainLead;
|
|
56
|
+
|
|
51
57
|
NSString* _cacheDir;
|
|
52
58
|
}
|
|
53
59
|
|
|
@@ -63,8 +69,9 @@
|
|
|
63
69
|
if (self) {
|
|
64
70
|
_viewportWidth = width;
|
|
65
71
|
_viewportHeight = height;
|
|
66
|
-
_initialized
|
|
67
|
-
_suspended
|
|
72
|
+
_initialized = NO;
|
|
73
|
+
_suspended = NO;
|
|
74
|
+
_altitudeIsEllipsoid.store(false, std::memory_order_relaxed);
|
|
68
75
|
_lastTickSec = 0.0;
|
|
69
76
|
_forceRender.store(true, std::memory_order_release);
|
|
70
77
|
_cacheDir = [cacheDir copy];
|
|
@@ -182,13 +189,45 @@
|
|
|
182
189
|
|
|
183
190
|
- (void)setPositionLatitude:(double)lat longitude:(double)lon {
|
|
184
191
|
if (!_initialized) return;
|
|
192
|
+
const bool ellipsoid = _altitudeIsEllipsoid.load(std::memory_order_acquire);
|
|
193
|
+
double adjustedMsl = 0.0;
|
|
194
|
+
bool adjustAltitude = false;
|
|
195
|
+
if (ellipsoid) {
|
|
196
|
+
const auto demand = _integrator->getDemand();
|
|
197
|
+
adjustedMsl = reactnativecesium::mslAfterHorizontalMove(
|
|
198
|
+
demand.latitude, demand.longitude, demand.altitude, lat, lon);
|
|
199
|
+
adjustAltitude = std::abs(adjustedMsl - demand.altitude) >
|
|
200
|
+
reactnativecesium::kHorizontalMoveMslEpsilonMeters;
|
|
201
|
+
}
|
|
185
202
|
_integrator->setPosition(lat, lon);
|
|
203
|
+
if (adjustAltitude) {
|
|
204
|
+
_integrator->setAltitude(adjustedMsl);
|
|
205
|
+
}
|
|
186
206
|
_forceRender.store(true, std::memory_order_release);
|
|
187
207
|
}
|
|
188
208
|
|
|
209
|
+
- (void)setAltitudeDatumEllipsoid:(BOOL)ellipsoid {
|
|
210
|
+
_altitudeIsEllipsoid.store(ellipsoid == YES, std::memory_order_release);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
- (double)mslAltitude:(double)alt latitude:(double)lat longitude:(double)lon {
|
|
214
|
+
return reactnativecesium::demandAltitudeToMslMeters(
|
|
215
|
+
lat, lon, alt, _altitudeIsEllipsoid.load(std::memory_order_acquire));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
- (double)demandAltitudeFromMsl:(double)msl
|
|
219
|
+
latitude:(double)lat
|
|
220
|
+
longitude:(double)lon {
|
|
221
|
+
return reactnativecesium::mslToDemandAltitudeMeters(
|
|
222
|
+
lat, lon, msl, _altitudeIsEllipsoid.load(std::memory_order_acquire));
|
|
223
|
+
}
|
|
224
|
+
|
|
189
225
|
- (void)setAltitude:(double)alt {
|
|
190
226
|
if (!_initialized) return;
|
|
191
|
-
_integrator->
|
|
227
|
+
const auto demand = _integrator->getDemand();
|
|
228
|
+
_integrator->setAltitude([self mslAltitude:alt
|
|
229
|
+
latitude:demand.latitude
|
|
230
|
+
longitude:demand.longitude]);
|
|
192
231
|
_forceRender.store(true, std::memory_order_release);
|
|
193
232
|
}
|
|
194
233
|
|
|
@@ -221,7 +260,7 @@
|
|
|
221
260
|
reactnativecesium::CameraParams p;
|
|
222
261
|
p.latitude = lat;
|
|
223
262
|
p.longitude = lon;
|
|
224
|
-
p.altitude = alt;
|
|
263
|
+
p.altitude = [self mslAltitude:alt latitude:lat longitude:lon];
|
|
225
264
|
p.heading = heading;
|
|
226
265
|
p.pitch = pitch;
|
|
227
266
|
p.roll = roll;
|
|
@@ -230,6 +269,7 @@
|
|
|
230
269
|
// jump, not a boresight reset. Use setViewCorrection(identity) to clear it.
|
|
231
270
|
p.viewCorrection = _integrator->getActual().viewCorrection;
|
|
232
271
|
_integrator->teleport(p);
|
|
272
|
+
reactnativecesium::noteTerrainSlew(*_integrator, _terrainLead);
|
|
233
273
|
// Apply directly to the engine so the next render uses the teleported value
|
|
234
274
|
// without one frame of "almost there" interpolation.
|
|
235
275
|
_engine->camera().setParams(p);
|
|
@@ -382,32 +422,9 @@
|
|
|
382
422
|
// The integrator owns its own clock now — see CameraIntegrator::step().
|
|
383
423
|
// `nowSeconds` is still used below for the FPS-EMA dt only.
|
|
384
424
|
auto actual = _integrator->step();
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// tracker smoothly interpolate. The tracker already provides adaptive
|
|
389
|
-
// exponential easing (alpha=0.6, beta=0.05 in EngineTunables.hpp).
|
|
390
|
-
const float minAbove = _engine->getConfig().minAltitudeAboveTerrain;
|
|
391
|
-
if (minAbove > 0.0f && _engine->terrainFloorKnown()) {
|
|
392
|
-
const double geoidN =
|
|
393
|
-
reactnativecesium::mslToEllipsoidMeters(actual.latitude, actual.longitude, 0.0);
|
|
394
|
-
const double floorEllipsoid = static_cast<double>(_engine->terrainFloorEllipsoidMeters());
|
|
395
|
-
const double floorMsl = floorEllipsoid - geoidN;
|
|
396
|
-
const double minMsl = floorMsl + static_cast<double>(minAbove);
|
|
397
|
-
|
|
398
|
-
// Continuously update demand to follow terrain
|
|
399
|
-
const double demandAlt = _integrator->getDemand().altitude;
|
|
400
|
-
if (demandAlt <= 0.0) {
|
|
401
|
-
_integrator->setAltitude(minMsl);
|
|
402
|
-
actual = _integrator->getActual();
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Safety floor: hard clamp if already below terrain (shouldn't happen with look-ahead)
|
|
406
|
-
if (actual.altitude < minMsl) {
|
|
407
|
-
_integrator->clampActualAltitude(minMsl);
|
|
408
|
-
actual = _integrator->getActual();
|
|
409
|
-
}
|
|
410
|
-
}
|
|
425
|
+
actual = reactnativecesium::applyTerrainFloor(
|
|
426
|
+
*_engine, *_integrator, actual, _terrainLead,
|
|
427
|
+
static_cast<double>(_engine->getConfig().minAltitudeAboveTerrain));
|
|
411
428
|
|
|
412
429
|
_engine->camera().setParams(actual);
|
|
413
430
|
|
|
@@ -447,7 +464,13 @@
|
|
|
447
464
|
// ── Actual camera readback (post-integration; what was rendered) ─────────
|
|
448
465
|
- (double)readActualLatitude { return _integrator ? _integrator->getActual().latitude : 0.0; }
|
|
449
466
|
- (double)readActualLongitude { return _integrator ? _integrator->getActual().longitude : 0.0; }
|
|
450
|
-
- (double)readActualAltitude
|
|
467
|
+
- (double)readActualAltitude {
|
|
468
|
+
if (!_integrator) return 0.0;
|
|
469
|
+
const auto actual = _integrator->getActual();
|
|
470
|
+
return [self demandAltitudeFromMsl:actual.altitude
|
|
471
|
+
latitude:actual.latitude
|
|
472
|
+
longitude:actual.longitude];
|
|
473
|
+
}
|
|
451
474
|
- (double)readActualHeading { return _integrator ? _integrator->getActual().heading : 0.0; }
|
|
452
475
|
- (double)readActualPitch { return _integrator ? _integrator->getActual().pitch : 0.0; }
|
|
453
476
|
- (double)readActualRoll { return _integrator ? _integrator->getActual().roll : 0.0; }
|
|
@@ -456,7 +479,13 @@
|
|
|
456
479
|
// ── Demand camera readback (what the consumer last asked for) ────────────
|
|
457
480
|
- (double)readDemandLatitude { return _integrator ? _integrator->getDemand().latitude : 0.0; }
|
|
458
481
|
- (double)readDemandLongitude { return _integrator ? _integrator->getDemand().longitude : 0.0; }
|
|
459
|
-
- (double)readDemandAltitude
|
|
482
|
+
- (double)readDemandAltitude {
|
|
483
|
+
if (!_integrator) return 0.0;
|
|
484
|
+
const auto demand = _integrator->getDemand();
|
|
485
|
+
return [self demandAltitudeFromMsl:demand.altitude
|
|
486
|
+
latitude:demand.latitude
|
|
487
|
+
longitude:demand.longitude];
|
|
488
|
+
}
|
|
460
489
|
- (double)readDemandHeading { return _integrator ? _integrator->getDemand().heading : 0.0; }
|
|
461
490
|
- (double)readDemandPitch { return _integrator ? _integrator->getDemand().pitch : 0.0; }
|
|
462
491
|
- (double)readDemandRoll { return _integrator ? _integrator->getDemand().roll : 0.0; }
|
|
@@ -72,6 +72,11 @@ class HybridCesiumView: HybridCesiumViewSpec {
|
|
|
72
72
|
var ionImageryAssetId: Double = 1 {
|
|
73
73
|
didSet { let v = Int64(ionImageryAssetId); postToRender { [weak self] in self?.bridge?.updateImageryAssetId(v) } }
|
|
74
74
|
}
|
|
75
|
+
/// `"msl"` (default) or `"ellipsoid"`. Ellipsoid heights are converted to MSL
|
|
76
|
+
/// before the camera integrator.
|
|
77
|
+
var altitudeDatum: String? = "msl" {
|
|
78
|
+
didSet { pushAltitudeDatum() }
|
|
79
|
+
}
|
|
75
80
|
|
|
76
81
|
// ── Optional perf / quality knobs ────────────────────────────────────────
|
|
77
82
|
var maximumCachedMiB: Double? {
|
|
@@ -460,6 +465,12 @@ class HybridCesiumView: HybridCesiumViewSpec {
|
|
|
460
465
|
// sqliteCacheMaxRows and taskProcessorThreads are applied at construction
|
|
461
466
|
// time via the CesiumBridge initializer; calling them here has no effect.
|
|
462
467
|
if let v = minAltitudeAboveTerrain { bridge?.setMinAltitudeAboveTerrain(Float(v)) }
|
|
468
|
+
bridge?.setAltitudeDatumEllipsoid(altitudeDatum == "ellipsoid")
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
private func pushAltitudeDatum() {
|
|
472
|
+
let ellipsoid = altitudeDatum == "ellipsoid"
|
|
473
|
+
postToRender { [weak self] in self?.bridge?.setAltitudeDatumEllipsoid(ellipsoid) }
|
|
463
474
|
}
|
|
464
475
|
|
|
465
476
|
// MARK: - Render Loop
|