Files
MapLibreParticleLab/test/util/camera.test.cpp
2026-01-20 09:57:48 +01:00

264 lines
11 KiB
C++

#include <mbgl/test/util.hpp>
#include <mbgl/map/camera.hpp>
#include <mbgl/math/angles.hpp>
#include <mbgl/util/mat3.hpp>
#include <mbgl/util/quaternion.hpp>
#include <gmock/gmock.h>
#include <numbers>
using namespace mbgl;
using namespace std::numbers;
static const double abs_double_error = 1e-7;
MATCHER_P(Vec3NearEquals1E7, vec, "") {
return std::fabs(vec[0] - arg[0]) <= abs_double_error && std::fabs(vec[1] - arg[1]) <= abs_double_error &&
std::fabs(vec[2] - arg[2]) <= abs_double_error;
}
TEST(FreeCameraOptions, SetLocation) {
FreeCameraOptions options;
options.setLocation({.location = {0.0, 0.0}, .altitude = util::EARTH_RADIUS_M * pi});
ASSERT_TRUE(options.position);
ASSERT_THAT(options.position.value(), Vec3NearEquals1E7(vec3{{0.5, 0.5, 0.5}}));
options.setLocation({.location = {25.0, -180.0}, .altitude = 1000.0});
ASSERT_TRUE(options.position);
ASSERT_THAT(options.position.value(), Vec3NearEquals1E7(vec3{{0.0, 0.4282409625, 0.000027532812465}}));
options.setLocation({.location = {util::LATITUDE_MAX, 0.0},
.altitude = util::EARTH_RADIUS_M * pi * std::cos(util::deg2rad(util::LATITUDE_MAX))});
ASSERT_TRUE(options.position);
ASSERT_THAT(options.position.value(), Vec3NearEquals1E7(vec3{{0.5, 0.0, 0.5}}));
options.setLocation({.location = {-util::LATITUDE_MAX, 0.0},
.altitude = util::EARTH_RADIUS_M * pi * std::cos(util::deg2rad(-util::LATITUDE_MAX))});
ASSERT_TRUE(options.position);
ASSERT_THAT(options.position.value(), Vec3NearEquals1E7(vec3{{0.5, 1.0, 0.5}}));
}
TEST(FreeCameraOptions, SetLocationNegativeAltitude) {
FreeCameraOptions options;
options.setLocation({.location = {0.0, 0.0}, .altitude = -100.0});
ASSERT_TRUE(options.position);
ASSERT_DOUBLE_EQ(options.position.value()[2], -100.0 / util::M2PI / util::EARTH_RADIUS_M);
}
TEST(FreeCameraOptions, SetLocationUnwrappedLocation) {
FreeCameraOptions options;
options.setLocation({.location = {0.0, -540.0}, .altitude = 0.0});
ASSERT_TRUE(options.position);
ASSERT_THAT(options.position.value(), Vec3NearEquals1E7(vec3{{-1.0, 0.5, 0.0}}));
}
TEST(FreeCameraOptions, GetLocation) {
FreeCameraOptions options;
LatLngAltitude latLngAltitude;
options.position = vec3{{0.5, 0.5, 0.5}};
ASSERT_TRUE(options.getLocation());
latLngAltitude = options.getLocation().value();
ASSERT_DOUBLE_EQ(latLngAltitude.location.latitude(), 0.0);
ASSERT_DOUBLE_EQ(latLngAltitude.location.longitude(), 0.0);
ASSERT_DOUBLE_EQ(latLngAltitude.altitude, 20037508.342789244);
options.position = vec3{{0.0, 0.4282409625, 0.000027532812465}};
ASSERT_TRUE(options.getLocation());
latLngAltitude = options.getLocation().value();
ASSERT_NEAR(latLngAltitude.location.latitude(), 25.0, 1e-7);
ASSERT_NEAR(latLngAltitude.location.longitude(), -180.0, 1e-7);
ASSERT_NEAR(latLngAltitude.altitude, 1000.0, 1e-7);
options.position = vec3{{0.5, 0.0, 0.5}};
ASSERT_TRUE(options.getLocation());
latLngAltitude = options.getLocation().value();
ASSERT_NEAR(latLngAltitude.location.latitude(), util::LATITUDE_MAX, 1e-7);
ASSERT_NEAR(latLngAltitude.location.longitude(), 0.0, 1e-7);
ASSERT_NEAR(latLngAltitude.altitude, 1728570.489074, 1e-6);
}
TEST(FreeCameraOptions, GetLocationInvalidPosition) {
FreeCameraOptions options;
// Mercator position not set. Should return nothing
ASSERT_FALSE(options.getLocation());
// Invalid latitude
options.position = vec3{{0.0, -0.1, 0.0}};
ASSERT_FALSE(options.getLocation());
options.position = vec3{{0.0, 2.0, 0.0}};
ASSERT_FALSE(options.getLocation());
}
TEST(FreeCameraOptions, GetLocationNegativeAltitude) {
FreeCameraOptions options;
options.position = vec3{{0.5, 0.5, -0.25}};
const auto latLngAltitude = options.getLocation();
ASSERT_TRUE(latLngAltitude);
ASSERT_DOUBLE_EQ(latLngAltitude->altitude, -0.5 * util::EARTH_RADIUS_M * pi);
}
TEST(FreeCameraOptions, GetLocationUnwrappedPosition) {
FreeCameraOptions options;
options.position = vec3{{1.25, 0.5, 0.0}};
const auto latLngAltitude = options.getLocation();
ASSERT_TRUE(latLngAltitude);
ASSERT_DOUBLE_EQ(latLngAltitude->location.longitude(), 270.0);
ASSERT_DOUBLE_EQ(latLngAltitude->location.latitude(), 0.0);
ASSERT_DOUBLE_EQ(latLngAltitude->altitude, 0.0);
}
static std::tuple<vec3, vec3, vec3> rotatedFrame(const std::array<double, 4>& quaternion) {
Quaternion q(quaternion);
return std::make_tuple(
q.transform({{1.0, 0.0, 0.0}}), q.transform({{0.0, -1.0, 0.0}}), q.transform({{0.0, 0.0, -1.0}}));
}
TEST(FreeCameraOptions, LookAtPoint) {
FreeCameraOptions options;
vec3 right, up, forward;
const double cosPi4 = 1.0 / std::numbers::sqrt2;
// Pitch: 45, bearing: 0
options.position = vec3{{0.5, 0.5, 0.5}};
options.lookAtPoint({util::LATITUDE_MAX, 0.0});
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -cosPi4, cosPi4}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, -cosPi4, -cosPi4}}));
// Look directly to east
options.position = vec3{{0.5, 0.5, 0.0}};
options.lookAtPoint({0.0, 30.0});
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{0.0, 1.0, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, 0.0, 1.0}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}}));
// Pitch: 0, bearing: 0
options.setLocation({.location = {60.1699, 24.9384}, .altitude = 100.0});
options.lookAtPoint({60.1699, 24.9384}, vec3{{0.0, -1.0, 0.0}});
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -1.0, 0.0}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, 0.0, -1.0}}));
// Pitch: 0, bearing: 45
options.setLocation({.location = {60.1699, 24.9384}, .altitude = 100.0});
options.lookAtPoint({60.1699, 24.9384}, vec3{{-1.0, -1.0, 0.0}});
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{cosPi4, -cosPi4, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{-cosPi4, -cosPi4, 0.0}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, 0.0, -1.0}}));
// Looking south, up vector almost same as forward vector
options.setLocation({.location = {37.7749, 122.4194}, .altitude = 0.0});
options.lookAtPoint({37.5, 122.4194}, vec3{{0.0, 1.0, 0.00001}});
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{-1.0, 0.0, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, 0.0, 1.0}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, 1.0, 0.0}}));
// Orientation with roll-component
options.setLocation({.location = {-33.8688, 151.2093}, .altitude = 0.0});
options.lookAtPoint({-33.8688, 160.0}, vec3{{0.0, -1.0, 0.1}});
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{0.0, 0.9714568644195829, 0.2372162738348117}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -0.23721627383481167, 0.97145686441958301}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}}));
// Up vector pointing downwards
options.position = vec3{{0.5, 0.5, 0.5}};
options.lookAtPoint({util::LATITUDE_MAX, 0.0}, vec3{{0.0, 0.0, -0.5}});
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -cosPi4, cosPi4}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, -cosPi4, -cosPi4}}));
}
TEST(FreeCameraOptions, LookAtPointInvalidInput) {
FreeCameraOptions options;
// Position not set
options.orientation = vec4{{0.0, 0.0, 0.0, 0.0}};
options.lookAtPoint({0.0, 0.0});
ASSERT_FALSE(options.orientation);
// Target same as position
options.orientation = vec4{{0.0, 0.0, 0.0, 0.0}};
options.position = vec3{{0.5, 0.5, 0.0}};
options.lookAtPoint({0.0, 0.0});
ASSERT_FALSE(options.orientation);
// Camera looking directly down without an explicit up vector
options.orientation = vec4{{0.0, 0.0, 0.0, 0.0}};
options.position = vec3{{0.5, 0.5, 0.5}};
options.lookAtPoint({0.0, 0.0});
ASSERT_FALSE(options.orientation);
// Zero up vector
options.orientation = vec4{{0.0, 0.0, 0.0, 0.0}};
options.lookAtPoint({0.0, 0.0}, vec3{{0.0, 0.0, 0.0}});
ASSERT_FALSE(options.orientation);
// Up vector same as direction
options.orientation = vec4{{0.0, 0.0, 0.0, 0.0}};
options.lookAtPoint({0.0, 0.0}, vec3{{0.0, 0.0, -1.0}});
ASSERT_FALSE(options.orientation);
}
TEST(FreeCameraOptions, SetRollPitchBearing) {
FreeCameraOptions options;
vec3 right, up, forward;
options.setRollPitchBearing(0.0, 0.0, 0.0);
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -1.0, 0.0}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, 0.0, -1.0}}));
options.setRollPitchBearing(0.0, 0.0, 180.0);
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{-1.0, 0.0, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, 1.0, 0.0}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, 0.0, -1.0}}));
const double cos60 = std::cos(util::deg2rad(60.0));
const double sin60 = std::sin(util::deg2rad(60.0));
options.setRollPitchBearing(0.0, 60.0, 0.0);
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{1.0, 0.0, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{0.0, -cos60, sin60}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{0.0, -sin60, -cos60}}));
options.setRollPitchBearing(0.0, 60.0, -450);
ASSERT_TRUE(options.orientation);
std::tie(right, up, forward) = rotatedFrame(options.orientation.value());
ASSERT_THAT(right, Vec3NearEquals1E7(vec3{{0.0, 1.0, 0.0}}));
ASSERT_THAT(up, Vec3NearEquals1E7(vec3{{cos60, 0.0, sin60}}));
ASSERT_THAT(forward, Vec3NearEquals1E7(vec3{{sin60, 0.0, -cos60}}));
}