core, android: add support for camera roll (#3011)
@@ -39,9 +39,11 @@ int main(int argc, char* argv[]) {
|
||||
args::ValueFlag<double> zoomValue(centerGroup, "number", "Zoom level", {'z', "zoom"});
|
||||
args::ValueFlag<double> lonValue(centerGroup, "degrees", "Longitude", {'x', "lon"});
|
||||
args::ValueFlag<double> latValue(centerGroup, "degrees", "Latitude", {'y', "lat"});
|
||||
|
||||
args::ValueFlag<double> altValue(argumentParser, "degrees", "Altitude", {'A', "alt"});
|
||||
args::ValueFlag<double> fovValue(argumentParser, "degrees", "FOV", {'f', "fov"});
|
||||
args::ValueFlag<double> bearingValue(argumentParser, "degrees", "Bearing", {'b', "bearing"});
|
||||
args::ValueFlag<double> pitchValue(argumentParser, "degrees", "Pitch", {'p', "pitch"});
|
||||
args::ValueFlag<double> rollValue(argumentParser, "degrees", "Roll", {'R', "roll"});
|
||||
args::ValueFlag<uint32_t> widthValue(argumentParser, "pixels", "Image width", {'w', "width"});
|
||||
args::ValueFlag<uint32_t> heightValue(argumentParser, "pixels", "Image height", {'h', "height"});
|
||||
|
||||
@@ -65,9 +67,12 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
const double lat = latValue ? args::get(latValue) : 0;
|
||||
const double lon = lonValue ? args::get(lonValue) : 0;
|
||||
const double alt = altValue ? args::get(altValue) : 0;
|
||||
const double zoom = zoomValue ? args::get(zoomValue) : 0;
|
||||
const double fov = fovValue ? args::get(fovValue) : mbgl::util::rad2deg(mbgl::util::DEFAULT_FOV);
|
||||
const double bearing = bearingValue ? args::get(bearingValue) : 0;
|
||||
const double pitch = pitchValue ? args::get(pitchValue) : 0;
|
||||
const double roll = rollValue ? args::get(rollValue) : 0;
|
||||
const double pixelRatio = pixelRatioValue ? args::get(pixelRatioValue) : 1;
|
||||
|
||||
const uint32_t width = widthValue ? args::get(widthValue) : 512;
|
||||
@@ -120,7 +125,14 @@ int main(int argc, char* argv[]) {
|
||||
LatLngBounds boundingBox = LatLngBounds::hull(LatLng(bounds[0], bounds[1]), LatLng(bounds[2], bounds[3]));
|
||||
map.jumpTo(map.cameraForLatLngBounds(boundingBox, EdgeInsets(), bearing, pitch));
|
||||
} else {
|
||||
map.jumpTo(CameraOptions().withCenter(LatLng{lat, lon}).withZoom(zoom).withBearing(bearing).withPitch(pitch));
|
||||
map.jumpTo(CameraOptions()
|
||||
.withCenter(LatLng{lat, lon})
|
||||
.withCenterAltitude(alt)
|
||||
.withZoom(zoom)
|
||||
.withBearing(bearing)
|
||||
.withPitch(pitch)
|
||||
.withRoll(roll)
|
||||
.withFov(fov));
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
|
||||
@@ -21,6 +21,10 @@ struct CameraOptions {
|
||||
center = o;
|
||||
return *this;
|
||||
}
|
||||
CameraOptions& withCenterAltitude(const std::optional<double>& o) {
|
||||
centerAltitude = o;
|
||||
return *this;
|
||||
}
|
||||
CameraOptions& withPadding(const std::optional<EdgeInsets>& p) {
|
||||
padding = p;
|
||||
return *this;
|
||||
@@ -41,10 +45,21 @@ struct CameraOptions {
|
||||
pitch = o;
|
||||
return *this;
|
||||
}
|
||||
CameraOptions& withRoll(const std::optional<double>& o) {
|
||||
roll = o;
|
||||
return *this;
|
||||
}
|
||||
CameraOptions& withFov(const std::optional<double>& o) {
|
||||
fov = o;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Coordinate at the center of the map. */
|
||||
std::optional<LatLng> center;
|
||||
|
||||
/** Altitude of the center of the map, in meters above sea level. */
|
||||
std::optional<double> centerAltitude;
|
||||
|
||||
/** Padding around the interior of the view that affects the frame of
|
||||
reference for `center`. */
|
||||
std::optional<EdgeInsets> padding;
|
||||
@@ -63,11 +78,18 @@ struct CameraOptions {
|
||||
/** Pitch toward the horizon measured in degrees , with 0 deg resulting in a
|
||||
two-dimensional map. */
|
||||
std::optional<double> pitch;
|
||||
|
||||
/** Camera roll, measured in degrees. */
|
||||
std::optional<double> roll;
|
||||
|
||||
/** Camera vertical field of view, measured in degrees. */
|
||||
std::optional<double> fov;
|
||||
};
|
||||
|
||||
constexpr bool operator==(const CameraOptions& a, const CameraOptions& b) {
|
||||
return a.center == b.center && a.padding == b.padding && a.anchor == b.anchor && a.zoom == b.zoom &&
|
||||
a.bearing == b.bearing && a.pitch == b.pitch;
|
||||
a.bearing == b.bearing && a.pitch == b.pitch && a.roll == b.roll && a.fov == b.fov &&
|
||||
a.centerAltitude == b.centerAltitude;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const CameraOptions& a, const CameraOptions& b) {
|
||||
@@ -134,7 +156,6 @@ struct FreeCameraOptions {
|
||||
0, 0]
|
||||
|
||||
Orientation can be set freely but certain constraints still apply
|
||||
- Orientation must be representable with only pitch and bearing.
|
||||
- Pitch has an upper limit */
|
||||
std::optional<vec4> orientation = std::nullopt;
|
||||
|
||||
@@ -151,9 +172,9 @@ struct FreeCameraOptions {
|
||||
bearing can't be deduced from the viewing direction */
|
||||
void lookAtPoint(const LatLng& location, const std::optional<vec3>& upVector = std::nullopt) noexcept;
|
||||
|
||||
/** Helper function for setting the orientation of the camera as a pitch and
|
||||
a bearing. Both values are in degrees */
|
||||
void setPitchBearing(double pitch, double bearing) noexcept;
|
||||
/** Helper function for setting the orientation of the camera as a roll, pitch, and
|
||||
a bearing. All values are in degrees */
|
||||
void setRollPitchBearing(double roll, double pitch, double bearing) noexcept;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -32,7 +32,13 @@ constexpr double LATITUDE_MAX = 85.051128779806604;
|
||||
constexpr double LONGITUDE_MAX = 180;
|
||||
constexpr double DEGREES_MAX = 360;
|
||||
constexpr double PITCH_MIN = 0.0;
|
||||
constexpr double PITCH_MAX = M_PI / 3;
|
||||
constexpr double DEFAULT_PITCH_MAX = M_PI / 3;
|
||||
constexpr double PITCH_MAX = M_PI;
|
||||
// This fov value is somewhat arbitrary. The altitude of the camera used
|
||||
// to be defined as 1.5 screen heights above the ground, which was an
|
||||
// arbitrary choice. This is the fov equivalent to that value calculated
|
||||
// with: `fov = 2 * arctan((height / 2) / (height * 1.5))`
|
||||
constexpr double DEFAULT_FOV = 0.6435011087932844;
|
||||
constexpr double MIN_ZOOM = 0.0;
|
||||
constexpr double MAX_ZOOM = 25.5;
|
||||
constexpr float MIN_ZOOM_F = MIN_ZOOM;
|
||||
|
||||
|
After Width: | Height: | Size: 368 B |
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 64,
|
||||
"height": 64
|
||||
}
|
||||
},
|
||||
"bearing": 45,
|
||||
"pitch": 45,
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sprite": "local://sprites/sprite",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "point",
|
||||
"icon-rotation-alignment": "map",
|
||||
"icon-image": "oneway"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 223 B |
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 64,
|
||||
"height": 64
|
||||
}
|
||||
},
|
||||
"bearing": 45,
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sprite": "local://sprites/sprite",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "point",
|
||||
"icon-rotation-alignment": "auto",
|
||||
"icon-image": "oneway"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 353 B |
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 64,
|
||||
"height": 64
|
||||
}
|
||||
},
|
||||
"bearing": 45,
|
||||
"pitch": 45,
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sprite": "local://sprites/sprite",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "point",
|
||||
"icon-rotation-alignment": "viewport",
|
||||
"icon-pitch-alignment": "map",
|
||||
"icon-image": "oneway"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 406 B |
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 64,
|
||||
"height": 64
|
||||
}
|
||||
},
|
||||
"bearing": 45,
|
||||
"pitch": 45,
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"sprite": "local://sprites/sprite",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "point",
|
||||
"icon-rotation-alignment": "map",
|
||||
"icon-pitch-alignment": "viewport",
|
||||
"icon-image": "oneway"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"height": 256,
|
||||
"operations": [
|
||||
[ "setBearing", 90 ],
|
||||
[ "setPitch", 40 ],
|
||||
[ "setRoll", 90 ],
|
||||
[ "wait" ]
|
||||
]
|
||||
}
|
||||
},
|
||||
"center": [
|
||||
13.418056,
|
||||
52.499167
|
||||
],
|
||||
"zoom": 14,
|
||||
"sources": {
|
||||
"maplibre": {
|
||||
"type": "vector",
|
||||
"maxzoom": 14,
|
||||
"tiles": [
|
||||
"local://tiles/{z}-{x}-{y}.mvt"
|
||||
]
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "line-placement-true",
|
||||
"type": "symbol",
|
||||
"source": "maplibre",
|
||||
"source-layer": "road",
|
||||
"layout": {
|
||||
"symbol-placement": "line",
|
||||
"text-allow-overlap": true,
|
||||
"text-ignore-placement": true,
|
||||
"text-field": "{class} {class}",
|
||||
"text-font": [
|
||||
"Open Sans Semibold",
|
||||
"Arial Unicode MS Bold"
|
||||
],
|
||||
"text-keep-upright": true
|
||||
},
|
||||
"paint": {
|
||||
"text-opacity": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"height": 256,
|
||||
"operations": [
|
||||
[ "setRoll", 180 ],
|
||||
[ "wait" ]
|
||||
]
|
||||
}
|
||||
},
|
||||
"center": [
|
||||
13.418056,
|
||||
52.499167
|
||||
],
|
||||
"zoom": 14,
|
||||
"sources": {
|
||||
"maplibre": {
|
||||
"type": "vector",
|
||||
"maxzoom": 14,
|
||||
"tiles": [
|
||||
"local://tiles/{z}-{x}-{y}.mvt"
|
||||
]
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "line-placement-true",
|
||||
"type": "symbol",
|
||||
"source": "maplibre",
|
||||
"source-layer": "road",
|
||||
"layout": {
|
||||
"symbol-placement": "line",
|
||||
"text-allow-overlap": true,
|
||||
"text-ignore-placement": true,
|
||||
"text-field": "{class} {class}",
|
||||
"text-font": [
|
||||
"Open Sans Semibold",
|
||||
"Arial Unicode MS Bold"
|
||||
],
|
||||
"text-keep-upright": true
|
||||
},
|
||||
"paint": {
|
||||
"text-opacity": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 94 KiB |
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"collisionDebug": true,
|
||||
"height": 512
|
||||
}
|
||||
},
|
||||
"center": [
|
||||
13.418056,
|
||||
52.499167
|
||||
],
|
||||
"zoom": 16,
|
||||
"pitch": 30,
|
||||
"roll": 180,
|
||||
"sources": {
|
||||
"maplibre": {
|
||||
"type": "vector",
|
||||
"maxzoom": 14,
|
||||
"tiles": [
|
||||
"local://tiles/{z}-{x}-{y}.mvt"
|
||||
]
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "background",
|
||||
"type": "background",
|
||||
"paint": {
|
||||
"background-color": "white"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "road",
|
||||
"type": "line",
|
||||
"source": "maplibre",
|
||||
"source-layer": "road",
|
||||
"paint": {
|
||||
"line-color": "#888",
|
||||
"line-width": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "text",
|
||||
"type": "symbol",
|
||||
"source": "maplibre",
|
||||
"source-layer": "road_label",
|
||||
"layout": {
|
||||
"symbol-placement": "line",
|
||||
"symbol-spacing": 60,
|
||||
"text-rotation-alignment": "map",
|
||||
"text-pitch-alignment": "auto",
|
||||
"text-field": "{class}",
|
||||
"text-font": [
|
||||
"Open Sans Semibold",
|
||||
"Arial Unicode MS Bold"
|
||||
]
|
||||
},
|
||||
"paint": {
|
||||
"text-opacity": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 973 B |
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 128,
|
||||
"height": 128
|
||||
}
|
||||
},
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-40,
|
||||
0
|
||||
],
|
||||
[
|
||||
40,
|
||||
0
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "line",
|
||||
"symbol-spacing": 20,
|
||||
"text-allow-overlap": true,
|
||||
"text-rotation-alignment": "auto",
|
||||
"text-field": "-->",
|
||||
"text-font": [
|
||||
"Open Sans Semibold",
|
||||
"Arial Unicode MS Bold"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 377 B |
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 128,
|
||||
"height": 128
|
||||
}
|
||||
},
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "point",
|
||||
"text-rotation-alignment": "auto",
|
||||
"text-field": "-->",
|
||||
"text-font": [
|
||||
"Open Sans Semibold",
|
||||
"Arial Unicode MS Bold"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 973 B |
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 128,
|
||||
"height": 128
|
||||
}
|
||||
},
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-40,
|
||||
0
|
||||
],
|
||||
[
|
||||
40,
|
||||
0
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "line",
|
||||
"symbol-spacing": 20,
|
||||
"text-allow-overlap": true,
|
||||
"text-rotation-alignment": "map",
|
||||
"text-field": "-->",
|
||||
"text-font": [
|
||||
"Open Sans Semibold",
|
||||
"Arial Unicode MS Bold"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 401 B |
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 128,
|
||||
"height": 128
|
||||
}
|
||||
},
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "point",
|
||||
"text-rotation-alignment": "map",
|
||||
"text-field": "-->",
|
||||
"text-font": [
|
||||
"Open Sans Semibold",
|
||||
"Arial Unicode MS Bold"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 874 B |
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 128,
|
||||
"height": 128
|
||||
}
|
||||
},
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "LineString",
|
||||
"coordinates": [
|
||||
[
|
||||
-40,
|
||||
0
|
||||
],
|
||||
[
|
||||
40,
|
||||
0
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "line",
|
||||
"symbol-spacing": 20,
|
||||
"text-allow-overlap": true,
|
||||
"text-rotation-alignment": "viewport",
|
||||
"text-field": "-->",
|
||||
"text-font": [
|
||||
"Open Sans Semibold",
|
||||
"Arial Unicode MS Bold"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 377 B |
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"version": 8,
|
||||
"metadata": {
|
||||
"test": {
|
||||
"width": 128,
|
||||
"height": 128
|
||||
}
|
||||
},
|
||||
"roll": 45,
|
||||
"sources": {
|
||||
"geojson": {
|
||||
"type": "geojson",
|
||||
"data": {
|
||||
"type": "Point",
|
||||
"coordinates": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
|
||||
"layers": [
|
||||
{
|
||||
"id": "symbol",
|
||||
"type": "symbol",
|
||||
"source": "geojson",
|
||||
"layout": {
|
||||
"symbol-placement": "point",
|
||||
"text-rotation-alignment": "viewport",
|
||||
"text-field": "-->",
|
||||
"text-font": [
|
||||
"Open Sans Semibold",
|
||||
"Arial Unicode MS Bold"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "camera_position.hpp"
|
||||
#include "../geometry/lat_lng.hpp"
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/math/angles.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace android {
|
||||
@@ -8,12 +10,19 @@ jni::Local<jni::Object<CameraPosition>> CameraPosition::New(jni::JNIEnv& env,
|
||||
mbgl::CameraOptions options,
|
||||
float pixelRatio) {
|
||||
static auto& javaClass = jni::Class<CameraPosition>::Singleton(env);
|
||||
static auto constructor =
|
||||
javaClass.GetConstructor<jni::Object<LatLng>, double, double, double, jni::Array<jni::jdouble>>(env);
|
||||
static auto constructor = javaClass.GetConstructor<jni::Object<LatLng>,
|
||||
double,
|
||||
double,
|
||||
double,
|
||||
double,
|
||||
double,
|
||||
double,
|
||||
jni::Array<jni::jdouble>>(env);
|
||||
|
||||
// wrap LatLng values coming from core
|
||||
auto center = options.center.value();
|
||||
center.wrap();
|
||||
double centerAltitude = options.centerAltitude.value_or(0);
|
||||
|
||||
// convert bearing, measured in radians counterclockwise from true north.
|
||||
// Wrapped to [−π rad, π rad). Android binding from 0 to 360 degrees
|
||||
@@ -27,6 +36,8 @@ jni::Local<jni::Object<CameraPosition>> CameraPosition::New(jni::JNIEnv& env,
|
||||
|
||||
// convert tilt, core ranges from [0 rad, 1,0472 rad], android ranges from 0 to 60
|
||||
double tilt_degrees = options.pitch.value_or(0);
|
||||
double roll_degrees = options.roll.value_or(0);
|
||||
double fov_degrees = options.fov.value_or(util::rad2deg(util::DEFAULT_FOV));
|
||||
|
||||
std::vector<jdouble> paddingVect;
|
||||
auto insets = options.padding.value_or(EdgeInsets{0, 0, 0, 0});
|
||||
@@ -37,8 +48,16 @@ jni::Local<jni::Object<CameraPosition>> CameraPosition::New(jni::JNIEnv& env,
|
||||
paddingVect.push_back(insets.bottom() * pixelRatio);
|
||||
padding.SetRegion<std::vector<jni::jdouble>>(env, 0, paddingVect);
|
||||
|
||||
return javaClass.New(
|
||||
env, constructor, LatLng::New(env, center), options.zoom.value_or(0), tilt_degrees, bearing_degrees, padding);
|
||||
return javaClass.New(env,
|
||||
constructor,
|
||||
LatLng::New(env, center),
|
||||
centerAltitude,
|
||||
options.zoom.value_or(0),
|
||||
tilt_degrees,
|
||||
bearing_degrees,
|
||||
roll_degrees,
|
||||
fov_degrees,
|
||||
padding);
|
||||
}
|
||||
|
||||
mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env,
|
||||
@@ -50,11 +69,15 @@ mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env,
|
||||
static auto tilt = javaClass.GetField<jni::jdouble>(env, "tilt");
|
||||
static auto zoom = javaClass.GetField<jni::jdouble>(env, "zoom");
|
||||
static auto paddingField = javaClass.GetField<jni::Array<jni::jdouble>>(env, "padding");
|
||||
static auto centerAltitude = javaClass.GetField<jni::jdouble>(env, "centerAltitude");
|
||||
static auto roll = javaClass.GetField<jni::jdouble>(env, "roll");
|
||||
static auto fov = javaClass.GetField<jni::jdouble>(env, "fov");
|
||||
|
||||
auto padding = position.Get(env, paddingField);
|
||||
auto center = LatLng::getLatLng(env, position.Get(env, target));
|
||||
|
||||
return mbgl::CameraOptions{center,
|
||||
position.Get(env, centerAltitude),
|
||||
padding && padding.Length(env) == 4 ? EdgeInsets{padding.Get(env, 1) * pixelRatio,
|
||||
padding.Get(env, 0) * pixelRatio,
|
||||
padding.Get(env, 3) * pixelRatio,
|
||||
@@ -63,7 +86,9 @@ mbgl::CameraOptions CameraPosition::getCameraOptions(jni::JNIEnv& env,
|
||||
{},
|
||||
position.Get(env, zoom),
|
||||
position.Get(env, bearing),
|
||||
position.Get(env, tilt)};
|
||||
position.Get(env, tilt),
|
||||
position.Get(env, roll),
|
||||
position.Get(env, fov)};
|
||||
}
|
||||
|
||||
void CameraPosition::registerNative(jni::JNIEnv& env) {
|
||||
|
||||
@@ -24,16 +24,21 @@ class CameraPosition
|
||||
* Constructs a CameraPosition.
|
||||
*
|
||||
* @param target The target location to align with the center of the screen.
|
||||
* @param centerAltitude The altitude of the target location to align with the center of the
|
||||
* screen, in meters above sea level.
|
||||
* @param zoom Zoom level at target. See zoom(float) for details of restrictions.
|
||||
* @param tilt The camera angle, in degrees, from the nadir (directly down). See tilt(float)
|
||||
* for details of restrictions.
|
||||
* @param bearing Direction that the camera is pointing in, in degrees clockwise from north.
|
||||
* This value will be normalized to be within 0 degrees inclusive and 360 degrees
|
||||
* exclusive.
|
||||
* @param roll Roll of the camera, in degrees clockwise.
|
||||
* @param fov Vertical field of view of the camera, in degrees.
|
||||
* @param padding Padding in pixels. Specified in left, top, right, bottom order.
|
||||
* @throws NullPointerException if target is null
|
||||
* @throws IllegalArgumentException if tilt is outside the range of 0 to 90 degrees inclusive.
|
||||
*/ @Keep internal constructor(
|
||||
*/
|
||||
@Keep internal constructor(
|
||||
|
||||
/**
|
||||
* The location that the camera is pointing at.
|
||||
@@ -42,6 +47,13 @@ class CameraPosition
|
||||
@JvmField
|
||||
val target: LatLng?,
|
||||
|
||||
/**
|
||||
* The altitude of the location that the camera is pointing at, in meters above sea level.
|
||||
*/
|
||||
@field:Keep
|
||||
@JvmField
|
||||
val centerAltitude: Double,
|
||||
|
||||
/**
|
||||
* Zoom level near the center of the screen. See [Builder.zoom] for the definition of the camera's
|
||||
* zoom level.
|
||||
@@ -65,6 +77,20 @@ class CameraPosition
|
||||
@JvmField
|
||||
val bearing: Double,
|
||||
|
||||
/**
|
||||
* Roll of the camera, in degrees clockwise.
|
||||
*/
|
||||
@field:Keep
|
||||
@JvmField
|
||||
val roll: Double,
|
||||
|
||||
/**
|
||||
* Vertical field of view of the camera, in degrees.
|
||||
*/
|
||||
@field:Keep
|
||||
@JvmField
|
||||
val fov: Double,
|
||||
|
||||
/**
|
||||
* Padding in pixels. Specified in left, top, right, bottom order.
|
||||
* See [Builder.padding] for the definition of the camera's padding.
|
||||
@@ -75,6 +101,22 @@ class CameraPosition
|
||||
|
||||
) : Parcelable {
|
||||
|
||||
/**
|
||||
* Constructs a CameraPosition.
|
||||
*
|
||||
* @param target The target location to align with the center of the screen.
|
||||
* @param zoom Zoom level at target. See zoom(float) for details of restrictions.
|
||||
* @param tilt The camera angle, in degrees, from the nadir (directly down). See tilt(float)
|
||||
* for details of restrictions.
|
||||
* @param bearing Direction that the camera is pointing in, in degrees clockwise from north.
|
||||
* This value will be normalized to be within 0 degrees inclusive and 360 degrees
|
||||
* exclusive.
|
||||
* @param padding Padding in pixels. Specified in left, top, right, bottom order.
|
||||
* @throws NullPointerException if target is null
|
||||
* @throws IllegalArgumentException if tilt is outside the range of 0 to 90 degrees inclusive.
|
||||
*/
|
||||
@Keep internal constructor(target: LatLng?, zoom: Double, tilt: Double, bearing: Double, padding: DoubleArray?) : this(target, 0.0, zoom, tilt, bearing, 0.0, MapLibreConstants.DEFAULT_FOV, padding)
|
||||
|
||||
/**
|
||||
* Constructs a CameraPosition.
|
||||
*
|
||||
@@ -112,7 +154,10 @@ class CameraPosition
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
out.writeDouble(bearing)
|
||||
out.writeParcelable(target, flags)
|
||||
out.writeDouble(centerAltitude)
|
||||
out.writeDouble(tilt)
|
||||
out.writeDouble(roll)
|
||||
out.writeDouble(fov)
|
||||
out.writeDouble(zoom)
|
||||
if (padding != null) {
|
||||
val length = padding.size
|
||||
@@ -131,7 +176,7 @@ class CameraPosition
|
||||
* @return A String with CameraPosition information.
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return ("Target: " + target + ", Zoom:" + zoom + ", Bearing:" + bearing + ", Tilt:" + tilt + ", Padding:" + Arrays.toString(padding))
|
||||
return ("Target: " + target + ", Center Altitude:" + centerAltitude + ", Zoom:" + zoom + ", Bearing:" + bearing + ", Tilt:" + tilt + ", Roll:" + roll + ", FOV:" + fov + ", Padding:" + Arrays.toString(padding))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,10 +197,16 @@ class CameraPosition
|
||||
val cameraPosition = other as CameraPosition
|
||||
if (target != null && target != cameraPosition.target) {
|
||||
return false
|
||||
} else if (centerAltitude != cameraPosition.centerAltitude) {
|
||||
return false
|
||||
} else if (zoom != cameraPosition.zoom) {
|
||||
return false
|
||||
} else if (tilt != cameraPosition.tilt) {
|
||||
return false
|
||||
} else if (roll != cameraPosition.roll) {
|
||||
return false
|
||||
} else if (fov != cameraPosition.fov) {
|
||||
return false
|
||||
} else if (bearing != cameraPosition.bearing) {
|
||||
return false
|
||||
} else if (!Arrays.equals(padding, cameraPosition.padding)) {
|
||||
@@ -178,8 +229,14 @@ class CameraPosition
|
||||
temp = java.lang.Double.doubleToLongBits(bearing)
|
||||
result = (temp xor (temp ushr 32)).toInt()
|
||||
result = 31 * result + (target?.hashCode() ?: 0)
|
||||
temp = java.lang.Double.doubleToLongBits(centerAltitude)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
temp = java.lang.Double.doubleToLongBits(tilt)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
temp = java.lang.Double.doubleToLongBits(roll)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
temp = java.lang.Double.doubleToLongBits(fov)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
temp = java.lang.Double.doubleToLongBits(zoom)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
result = 31 * result + Arrays.hashCode(padding)
|
||||
@@ -192,7 +249,10 @@ class CameraPosition
|
||||
class Builder {
|
||||
private var bearing = -1.0
|
||||
private var target: LatLng? = null
|
||||
private var centerAltitude = -1.0
|
||||
private var tilt = -1.0
|
||||
private var roll = -1.0
|
||||
private var fov = -1.0
|
||||
private var zoom = -1.0
|
||||
private var padding: DoubleArray? = null
|
||||
|
||||
@@ -210,7 +270,10 @@ class CameraPosition
|
||||
if (previous != null) {
|
||||
bearing = previous.bearing
|
||||
target = previous.target
|
||||
centerAltitude = previous.centerAltitude
|
||||
tilt = previous.tilt
|
||||
roll = previous.roll
|
||||
fov = previous.fov
|
||||
zoom = previous.zoom
|
||||
padding = previous.padding
|
||||
}
|
||||
@@ -227,7 +290,10 @@ class CameraPosition
|
||||
val lat = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraTargetLat, 0.0f).toDouble()
|
||||
val lng = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraTargetLng, 0.0f).toDouble()
|
||||
target = LatLng(lat, lng)
|
||||
centerAltitude = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraCenterAltitude, 0.0f).toDouble()
|
||||
tilt = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraTilt, 0.0f).toDouble()
|
||||
roll = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraRoll, 0.0f).toDouble()
|
||||
fov = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraFov, 36.86989764584402f).toDouble()
|
||||
zoom = typedArray.getFloat(R.styleable.maplibre_MapView_maplibre_cameraZoom, 0.0f).toDouble()
|
||||
}
|
||||
}
|
||||
@@ -241,7 +307,10 @@ class CameraPosition
|
||||
if (update != null) {
|
||||
bearing = update.bearing
|
||||
target = update.target
|
||||
centerAltitude = update.centerAltitude
|
||||
tilt = update.tilt
|
||||
roll = update.roll
|
||||
fov = update.fov
|
||||
zoom = update.zoom
|
||||
padding = update.padding
|
||||
}
|
||||
@@ -287,6 +356,17 @@ class CameraPosition
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the altitude of the location where the camera is pointing at in meters above sea level
|
||||
*
|
||||
* @param centerAltitude altitude of the location where the camera is pointing at
|
||||
* @return this
|
||||
*/
|
||||
fun centerAltitude(centerAltitude: Double): Builder {
|
||||
this.centerAltitude = centerAltitude
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tilt of the camera in degrees
|
||||
*
|
||||
@@ -302,6 +382,28 @@ class CameraPosition
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the roll of the camera in degrees
|
||||
*
|
||||
* @param roll Roll value of the camera
|
||||
* @return this
|
||||
*/
|
||||
fun roll(roll: Double): Builder {
|
||||
this.roll = roll
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the vertical field of view of the camera in degrees
|
||||
*
|
||||
* @param fov Vertical field of view of the camera
|
||||
* @return this
|
||||
*/
|
||||
fun fov(fov: Double): Builder {
|
||||
this.fov = fov
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the zoom of the camera
|
||||
*
|
||||
@@ -375,7 +477,10 @@ class CameraPosition
|
||||
} else {
|
||||
parcel.readParcelable(LatLng::class.java.classLoader)
|
||||
}
|
||||
val centerAltitude = parcel.readDouble()
|
||||
val tilt = parcel.readDouble()
|
||||
val roll = parcel.readDouble()
|
||||
val fov = parcel.readDouble()
|
||||
val zoom = parcel.readDouble()
|
||||
var padding: DoubleArray? = null
|
||||
val paddingSize = parcel.readInt()
|
||||
@@ -385,7 +490,7 @@ class CameraPosition
|
||||
padding[i] = parcel.readDouble()
|
||||
}
|
||||
}
|
||||
return CameraPosition(target, zoom, tilt, bearing, padding)
|
||||
return CameraPosition(target, centerAltitude, zoom, tilt, bearing, roll, fov, padding)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<CameraPosition?> {
|
||||
|
||||
@@ -22,7 +22,7 @@ object CameraUpdateFactory {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun newCameraPosition(cameraPosition: CameraPosition): CameraUpdate {
|
||||
return CameraPositionUpdate(cameraPosition.bearing, cameraPosition.target, cameraPosition.tilt, cameraPosition.zoom, cameraPosition.padding)
|
||||
return CameraPositionUpdate(cameraPosition.bearing, cameraPosition.target, cameraPosition.centerAltitude, cameraPosition.tilt, cameraPosition.roll, cameraPosition.fov, cameraPosition.zoom, cameraPosition.padding)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +34,7 @@ object CameraUpdateFactory {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun newLatLng(latLng: LatLng): CameraUpdate {
|
||||
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, null)
|
||||
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, -1.0, -1.0, -1.0, null)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,7 +133,7 @@ object CameraUpdateFactory {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun newLatLngZoom(latLng: LatLng, zoom: Double): CameraUpdate {
|
||||
return CameraPositionUpdate(-1.0, latLng, -1.0, zoom, null)
|
||||
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, -1.0, -1.0, zoom, null)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,7 +150,7 @@ object CameraUpdateFactory {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun newLatLngPadding(latLng: LatLng, left: Double, top: Double, right: Double, bottom: Double): CameraUpdate {
|
||||
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, doubleArrayOf(left, top, right, bottom))
|
||||
return CameraPositionUpdate(-1.0, latLng, -1.0, -1.0, -1.0, -1.0, -1.0, doubleArrayOf(left, top, right, bottom))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,7 +217,7 @@ object CameraUpdateFactory {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun bearingTo(bearing: Double): CameraUpdate {
|
||||
return CameraPositionUpdate(bearing, null, -1.0, -1.0, null)
|
||||
return CameraPositionUpdate(bearing, null, -1.0, -1.0, -1.0, -1.0, -1.0, null)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,7 +228,7 @@ object CameraUpdateFactory {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun tiltTo(tilt: Double): CameraUpdate {
|
||||
return CameraPositionUpdate(-1.0, null, tilt, -1.0, null)
|
||||
return CameraPositionUpdate(-1.0, null, -1.0, tilt, -1.0, -1.0, -1.0, null)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,7 +244,7 @@ object CameraUpdateFactory {
|
||||
*/
|
||||
@JvmStatic
|
||||
fun paddingTo(padding: DoubleArray?): CameraUpdate {
|
||||
return CameraPositionUpdate(-1.0, null, -1.0, -1.0, padding)
|
||||
return CameraPositionUpdate(-1.0, null, -1.0, -1.0, -1.0, -1.0, -1.0, padding)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,7 +265,7 @@ object CameraUpdateFactory {
|
||||
//
|
||||
// CameraUpdate types
|
||||
//
|
||||
class CameraPositionUpdate(val bearing: Double, val target: LatLng?, val tilt: Double, val zoom: Double, val padding: DoubleArray?) : CameraUpdate {
|
||||
class CameraPositionUpdate(val bearing: Double, val target: LatLng?, val centerAltitude: Double, val tilt: Double, val roll: Double, val fov: Double, val zoom: Double, val padding: DoubleArray?) : CameraUpdate {
|
||||
|
||||
override fun getCameraPosition(maplibreMap: MapLibreMap): CameraPosition {
|
||||
if (target == null) {
|
||||
@@ -289,9 +289,18 @@ object CameraUpdateFactory {
|
||||
if (java.lang.Double.compare(that.tilt, tilt) != 0) {
|
||||
return false
|
||||
}
|
||||
if (java.lang.Double.compare(that.roll, roll) != 0) {
|
||||
return false
|
||||
}
|
||||
if (java.lang.Double.compare(that.fov, fov) != 0) {
|
||||
return false
|
||||
}
|
||||
if (java.lang.Double.compare(that.zoom, zoom) != 0) {
|
||||
return false
|
||||
}
|
||||
if (java.lang.Double.compare(that.centerAltitude, centerAltitude) != 0) {
|
||||
return false
|
||||
}
|
||||
return if (if (target != null) target != that.target else that.target != null) {
|
||||
false
|
||||
} else {
|
||||
@@ -305,8 +314,14 @@ object CameraUpdateFactory {
|
||||
temp = java.lang.Double.doubleToLongBits(bearing)
|
||||
result = (temp xor (temp ushr 32)).toInt()
|
||||
result = 31 * result + (target?.hashCode() ?: 0)
|
||||
temp = java.lang.Double.doubleToLongBits(centerAltitude)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
temp = java.lang.Double.doubleToLongBits(tilt)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
temp = java.lang.Double.doubleToLongBits(roll)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
temp = java.lang.Double.doubleToLongBits(fov)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
temp = java.lang.Double.doubleToLongBits(zoom)
|
||||
result = 31 * result + (temp xor (temp ushr 32)).toInt()
|
||||
result = 31 * result + Arrays.hashCode(padding)
|
||||
@@ -314,7 +329,7 @@ object CameraUpdateFactory {
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return ("CameraPositionUpdate{" + "bearing=" + bearing + ", target=" + target + ", tilt=" + tilt + ", zoom=" + zoom + ", padding=" + Arrays.toString(padding) + '}')
|
||||
return ("CameraPositionUpdate{" + "bearing=" + bearing + ", target=" + target + ", centerAltitude=" + centerAltitude + ", tilt=" + tilt + ", roll=" + roll + ", fov=" + fov + ", zoom=" + zoom + ", padding=" + Arrays.toString(padding) + '}')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -168,6 +168,11 @@ public class MapLibreConstants {
|
||||
*/
|
||||
public static final double MINIMUM_DIRECTION = 0;
|
||||
|
||||
/**
|
||||
* The current default vertical field of view (matches mbgl::util::DEFAULT_FOV)
|
||||
*/
|
||||
public static final double DEFAULT_FOV = 36.86989764584402;
|
||||
|
||||
/**
|
||||
* The currently used minimum scale factor to clamp to when a quick zoom gesture occurs
|
||||
*
|
||||
|
||||
@@ -21,9 +21,12 @@
|
||||
<!--Camera-->
|
||||
<attr name="maplibre_cameraTargetLat" format="float"/>
|
||||
<attr name="maplibre_cameraTargetLng" format="float"/>
|
||||
<attr name="maplibre_cameraCenterAltitude" format="float"/>
|
||||
<attr name="maplibre_cameraZoom" format="float"/>
|
||||
<attr name="maplibre_cameraBearing" format="float"/>
|
||||
<attr name="maplibre_cameraTilt" format="float"/>
|
||||
<attr name="maplibre_cameraRoll" format="float"/>
|
||||
<attr name="maplibre_cameraFov" format="float"/>
|
||||
|
||||
<!--Zoom-->
|
||||
<attr name="maplibre_cameraZoomMax" format="float"/>
|
||||
|
||||
@@ -85,11 +85,12 @@ class CameraPositionTest : BaseTest() {
|
||||
fun testToString() {
|
||||
val latLng = LatLng(1.0, 2.0)
|
||||
val cameraPosition =
|
||||
CameraPosition(latLng, 3.0, 4.0, 5.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0))
|
||||
CameraPosition(latLng, 9.0, 3.0, 4.0, 5.0, 6.0, 7.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0))
|
||||
Assert.assertEquals(
|
||||
"toString should match",
|
||||
"Target: LatLng [latitude=1.0, longitude=2.0, altitude=0.0], Zoom:3.0, " +
|
||||
"Bearing:5.0, Tilt:4.0, Padding:[0.0, 500.0, 0.0, 0.0]",
|
||||
"Target: LatLng [latitude=1.0, longitude=2.0, altitude=0.0], " +
|
||||
"Center Altitude:9.0, Zoom:3.0, Bearing:5.0, Tilt:4.0, Roll:6.0, FOV:7.0, " +
|
||||
"Padding:[0.0, 500.0, 0.0, 0.0]",
|
||||
cameraPosition.toString()
|
||||
)
|
||||
}
|
||||
@@ -98,8 +99,8 @@ class CameraPositionTest : BaseTest() {
|
||||
fun testHashcode() {
|
||||
val latLng = LatLng(1.0, 2.0)
|
||||
val cameraPosition =
|
||||
CameraPosition(latLng, 3.0, 4.0, 5.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0))
|
||||
Assert.assertEquals("hashCode should match", -420915327, cameraPosition.hashCode().toLong())
|
||||
CameraPosition(latLng, 9.0, 3.0, 4.0, 5.0, 6.0, 7.0, doubleArrayOf(0.0, 500.0, 0.0, 0.0))
|
||||
Assert.assertEquals("hashCode should match", -1086105727, cameraPosition.hashCode().toLong())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -100,7 +100,7 @@ public abstract class CameraTest extends BaseTest {
|
||||
invoke(maplibreMap, (uiController, maplibreMap) -> {
|
||||
final LatLng moveTarget = new LatLng(1.0000000001, 1.0000000003);
|
||||
final float moveZoom = 15.5f;
|
||||
final float moveTilt = 45.5f;
|
||||
final float moveTilt = 35.5f;
|
||||
final float moveBearing = 12.5f;
|
||||
final double[] movePadding = new double[] {0, 500, 350, 1};
|
||||
|
||||
|
||||
@@ -55,9 +55,13 @@ int main(int argc, char* argv[]) {
|
||||
argumentParser, "directory", "Action journal log directory", {"actionJournalDir"});
|
||||
args::ValueFlag<double> lonValue(argumentParser, "degrees", "Longitude", {'x', "lon"});
|
||||
args::ValueFlag<double> latValue(argumentParser, "degrees", "Latitude", {'y', "lat"});
|
||||
args::ValueFlag<double> altValue(argumentParser, "degrees", "Altitude", {'a', "alt"});
|
||||
args::ValueFlag<double> fovValue(argumentParser, "degrees", "FOV", {'F', "fov"});
|
||||
args::ValueFlag<double> zoomValue(argumentParser, "number", "Zoom level", {'z', "zoom"});
|
||||
args::ValueFlag<double> bearingValue(argumentParser, "degrees", "Bearing", {'b', "bearing"});
|
||||
args::ValueFlag<double> pitchValue(argumentParser, "degrees", "Pitch", {'p', "pitch"});
|
||||
args::ValueFlag<double> rollValue(argumentParser, "degrees", "Roll", {'r', "roll"});
|
||||
args::ValueFlag<double> maxPitchValue(argumentParser, "degrees", "Max Pitch", {'P', "maxPitch"});
|
||||
|
||||
try {
|
||||
argumentParser.ParseCLI(argc, argv);
|
||||
@@ -79,9 +83,13 @@ int main(int argc, char* argv[]) {
|
||||
settings.online = !offlineFlag;
|
||||
if (lonValue) settings.longitude = args::get(lonValue);
|
||||
if (latValue) settings.latitude = args::get(latValue);
|
||||
if (altValue) settings.altitude = args::get(altValue);
|
||||
if (fovValue) settings.fov = args::get(fovValue);
|
||||
if (maxPitchValue) settings.maxPitch = args::get(maxPitchValue);
|
||||
if (zoomValue) settings.zoom = args::get(zoomValue);
|
||||
if (bearingValue) settings.bearing = args::get(bearingValue);
|
||||
if (pitchValue) settings.pitch = args::get(pitchValue);
|
||||
if (rollValue) settings.roll = args::get(rollValue);
|
||||
|
||||
const bool fullscreen = fullscreenFlag ? args::get(fullscreenFlag) : false;
|
||||
const bool benchmark = benchmarkFlag ? args::get(benchmarkFlag) : false;
|
||||
@@ -147,6 +155,8 @@ int main(int argc, char* argv[]) {
|
||||
clientOptions,
|
||||
actionJournalOptions);
|
||||
|
||||
map.setBounds(mbgl::BoundOptions().withMaxPitch(settings.maxPitch));
|
||||
|
||||
backend.setMap(&map);
|
||||
|
||||
if (!style.empty() && style.find("://") == std::string::npos) {
|
||||
@@ -155,9 +165,12 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
map.jumpTo(mbgl::CameraOptions()
|
||||
.withCenter(mbgl::LatLng{settings.latitude, settings.longitude})
|
||||
.withCenterAltitude(settings.altitude)
|
||||
.withZoom(settings.zoom)
|
||||
.withBearing(settings.bearing)
|
||||
.withPitch(settings.pitch));
|
||||
.withPitch(settings.pitch)
|
||||
.withRoll(settings.roll)
|
||||
.withFov(settings.fov));
|
||||
map.setDebug(mbgl::MapDebugOptions(settings.debug));
|
||||
|
||||
if (testDirValue) view->setTestDirectory(args::get(testDirValue));
|
||||
@@ -237,15 +250,21 @@ int main(int argc, char* argv[]) {
|
||||
mbgl::CameraOptions camera = map.getCameraOptions();
|
||||
settings.latitude = camera.center->latitude();
|
||||
settings.longitude = camera.center->longitude();
|
||||
settings.altitude = *camera.centerAltitude;
|
||||
settings.zoom = *camera.zoom;
|
||||
settings.bearing = *camera.bearing;
|
||||
settings.pitch = *camera.pitch;
|
||||
settings.roll = *camera.roll;
|
||||
settings.fov = *camera.fov;
|
||||
settings.debug = mbgl::EnumType(map.getDebug());
|
||||
settings.save();
|
||||
mbgl::Log::Info(mbgl::Event::General,
|
||||
"Exit location: --lat=\"" + std::to_string(settings.latitude) + "\" --lon=\"" +
|
||||
std::to_string(settings.longitude) + "\" --zoom=\"" + std::to_string(settings.zoom) +
|
||||
"\" --bearing=\"" + std::to_string(settings.bearing) + "\"");
|
||||
std::to_string(settings.longitude) + "\" --alt=\"" + std::to_string(settings.altitude) +
|
||||
"\" --zoom=\"" + std::to_string(settings.zoom) + "\" --bearing=\"" +
|
||||
std::to_string(settings.bearing) + "\" --roll=\"" + std::to_string(settings.roll) +
|
||||
"\" --fov=\"" + std::to_string(settings.fov) + "\" --maxPitch=\"" +
|
||||
std::to_string(settings.maxPitch) + "\"");
|
||||
|
||||
view = nullptr;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "settings_json.hpp"
|
||||
#include <fstream>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/math/angles.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -12,9 +14,13 @@ void Settings_JSON::load() {
|
||||
if (file) {
|
||||
file >> longitude;
|
||||
file >> latitude;
|
||||
file >> altitude;
|
||||
file >> zoom;
|
||||
file >> bearing;
|
||||
file >> pitch;
|
||||
file >> roll;
|
||||
file >> fov;
|
||||
file >> maxPitch;
|
||||
file >> debug;
|
||||
file >> online;
|
||||
}
|
||||
@@ -25,9 +31,13 @@ void Settings_JSON::save() {
|
||||
if (file) {
|
||||
file << longitude << std::endl;
|
||||
file << latitude << std::endl;
|
||||
file << altitude << std::endl;
|
||||
file << zoom << std::endl;
|
||||
file << bearing << std::endl;
|
||||
file << pitch << std::endl;
|
||||
file << roll << std::endl;
|
||||
file << fov << std::endl;
|
||||
file << maxPitch << std::endl;
|
||||
file << debug << std::endl;
|
||||
file << online << std::endl;
|
||||
}
|
||||
@@ -36,9 +46,13 @@ void Settings_JSON::save() {
|
||||
void Settings_JSON::clear() {
|
||||
longitude = 0;
|
||||
latitude = 0;
|
||||
altitude = 0;
|
||||
zoom = 0;
|
||||
bearing = 0;
|
||||
pitch = 0;
|
||||
roll = 0;
|
||||
fov = util::rad2deg(util::DEFAULT_FOV);
|
||||
maxPitch = util::rad2deg(util::DEFAULT_PITCH_MAX);
|
||||
debug = 0;
|
||||
online = true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/map/mode.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/math/angles.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -14,9 +16,13 @@ public:
|
||||
public:
|
||||
double longitude = 0;
|
||||
double latitude = 0;
|
||||
double altitude = 0;
|
||||
double zoom = 0;
|
||||
double bearing = 0;
|
||||
double pitch = 0;
|
||||
double roll = 0;
|
||||
double fov = util::rad2deg(mbgl::util::DEFAULT_FOV);
|
||||
double maxPitch = util::rad2deg(mbgl::util::DEFAULT_PITCH_MAX);
|
||||
bool axonometric = false;
|
||||
double xSkew = 0.0;
|
||||
double ySkew = 1.0;
|
||||
|
||||
@@ -155,6 +155,7 @@ struct TestMetadata {
|
||||
bool ignoreProbing = true;
|
||||
|
||||
mbgl::Size size{512u, 512u};
|
||||
std::optional<float> maxPitch;
|
||||
float pixelRatio = 1.0f;
|
||||
double allowed = 0.00015; // diff
|
||||
std::string description;
|
||||
|
||||
@@ -510,6 +510,11 @@ TestMetadata parseTestMetadata(const TestPaths& paths) {
|
||||
}
|
||||
}
|
||||
|
||||
if (testValue.HasMember("maxPitch")) {
|
||||
assert(testValue["maxPitch"].IsNumber());
|
||||
metadata.maxPitch = testValue["maxPitch"].GetFloat();
|
||||
}
|
||||
|
||||
if (testValue.HasMember("pixelRatio")) {
|
||||
assert(testValue["pixelRatio"].IsNumber());
|
||||
metadata.pixelRatio = testValue["pixelRatio"].GetFloat();
|
||||
@@ -618,6 +623,8 @@ const std::string setCenterOp("setCenter");
|
||||
const std::string setZoomOp("setZoom");
|
||||
const std::string setBearingOp("setBearing");
|
||||
const std::string setPitchOp("setPitch");
|
||||
const std::string setRollOp("setRoll");
|
||||
const std::string setCenterAltitudeOp("setCenterElevation");
|
||||
const std::string setFilterOp("setFilter");
|
||||
const std::string setLayerZoomRangeOp("setLayerZoomRange");
|
||||
const std::string setLightOp("setLight");
|
||||
@@ -805,6 +812,24 @@ TestOperations parseTestOperations(TestMetadata& metadata) {
|
||||
ctx.getMap().jumpTo(mbgl::CameraOptions().withPitch(pitch));
|
||||
return true;
|
||||
});
|
||||
} else if (operationArray[0].GetString() == setRollOp) {
|
||||
// setRoll
|
||||
assert(operationArray.Size() >= 2u);
|
||||
assert(operationArray[1].IsNumber());
|
||||
double roll = operationArray[1].GetDouble();
|
||||
result.emplace_back([roll](TestContext& ctx) {
|
||||
ctx.getMap().jumpTo(mbgl::CameraOptions().withRoll(roll));
|
||||
return true;
|
||||
});
|
||||
} else if (operationArray[0].GetString() == setCenterAltitudeOp) {
|
||||
// setCenterAltitude
|
||||
assert(operationArray.Size() >= 2u);
|
||||
assert(operationArray[1].IsNumber());
|
||||
double centerAltitude = operationArray[1].GetDouble();
|
||||
result.emplace_back([centerAltitude](TestContext& ctx) {
|
||||
ctx.getMap().jumpTo(mbgl::CameraOptions().withCenterAltitude(centerAltitude));
|
||||
return true;
|
||||
});
|
||||
} else if (operationArray[0].GetString() == setFilterOp) {
|
||||
// setFilter
|
||||
assert(operationArray.Size() >= 3u);
|
||||
|
||||
@@ -43,6 +43,7 @@ extern const std::string setCenterOp;
|
||||
extern const std::string setZoomOp;
|
||||
extern const std::string setBearingOp;
|
||||
extern const std::string setPitchOp;
|
||||
extern const std::string setRollOp;
|
||||
extern const std::string setFilterOp;
|
||||
extern const std::string setLayerZoomRangeOp;
|
||||
extern const std::string setLightOp;
|
||||
|
||||
@@ -704,7 +704,11 @@ TestRunner::Impl::Impl(const TestMetadata& metadata,
|
||||
.withPixelRatio(metadata.pixelRatio)
|
||||
.withCrossSourceCollisions(metadata.crossSourceCollisions),
|
||||
resourceOptions,
|
||||
clientOptions) {}
|
||||
clientOptions) {
|
||||
if (metadata.maxPitch) {
|
||||
map.setBounds(BoundOptions().withMaxPitch(*metadata.maxPitch));
|
||||
}
|
||||
}
|
||||
|
||||
TestRunner::Impl::~Impl() {}
|
||||
|
||||
|
||||
@@ -65,6 +65,44 @@ namespace mbgl {
|
||||
* Steps 3 and 4 are done in the shaders for all labels.
|
||||
*/
|
||||
|
||||
void getTileSkewVectors(const TransformState& state, vec2& vecEast, vec2& vecSouth) {
|
||||
const double cosRoll = cos(state.getRoll());
|
||||
const double sinRoll = sin(state.getRoll());
|
||||
const double cosPitch = cos(state.getPitch());
|
||||
const double cosBearing = cos(-state.getBearing());
|
||||
const double sinBearing = sin(-state.getBearing());
|
||||
vecSouth[0] = -cosBearing * cosPitch * sinRoll - sinBearing * cosRoll;
|
||||
vecSouth[1] = -sinBearing * cosPitch * sinRoll + cosBearing * cosRoll;
|
||||
const double vecSouthLen = std::hypot(vecSouth[0], vecSouth[1]);
|
||||
if (vecSouthLen < 1.0e-9) {
|
||||
vecSouth = {0, 0};
|
||||
} else {
|
||||
vecSouth[0] /= vecSouthLen;
|
||||
vecSouth[1] /= vecSouthLen;
|
||||
}
|
||||
vecEast[0] = cosBearing * cosPitch * cosRoll - sinBearing * sinRoll;
|
||||
vecEast[1] = sinBearing * cosPitch * cosRoll + cosBearing * sinRoll;
|
||||
const double vecEastLen = std::hypot(vecEast[0], vecEast[1]);
|
||||
if (vecEastLen < 1.0e-9) {
|
||||
vecEast = {0, 0};
|
||||
} else {
|
||||
vecEast[0] /= vecEastLen;
|
||||
vecEast[1] /= vecEastLen;
|
||||
}
|
||||
}
|
||||
|
||||
mat4 getTileSkewMatrix(const TransformState& state) {
|
||||
vec2 vecEast, vecSouth;
|
||||
getTileSkewVectors(state, vecEast, vecSouth);
|
||||
mat4 m;
|
||||
matrix::identity(m);
|
||||
m[0] = vecEast[0];
|
||||
m[1] = vecEast[1];
|
||||
m[4] = vecSouth[0];
|
||||
m[5] = vecSouth[1];
|
||||
return m;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a matrix for converting from tile units to the correct label coordinate space.
|
||||
*/
|
||||
@@ -78,7 +116,9 @@ mat4 getLabelPlaneMatrix(const mat4& posMatrix,
|
||||
if (pitchWithMap) {
|
||||
matrix::scale(m, m, 1 / pixelsToTileUnits, 1 / pixelsToTileUnits, 1);
|
||||
if (!rotateWithMap) {
|
||||
matrix::rotate_z(m, m, state.getBearing());
|
||||
mat4 skew = getTileSkewMatrix(state);
|
||||
matrix::invert(skew, skew);
|
||||
matrix::multiply(m, m, skew);
|
||||
}
|
||||
} else {
|
||||
matrix::scale(m, m, state.getSize().width / 2.0, -(state.getSize().height / 2.0), 1.0);
|
||||
@@ -102,7 +142,7 @@ mat4 getGlCoordMatrix(const mat4& posMatrix,
|
||||
matrix::multiply(m, m, posMatrix);
|
||||
matrix::scale(m, m, pixelsToTileUnits, pixelsToTileUnits, 1);
|
||||
if (!rotateWithMap) {
|
||||
matrix::rotate_z(m, m, -state.getBearing());
|
||||
matrix::multiply(m, m, getTileSkewMatrix(state));
|
||||
}
|
||||
} else {
|
||||
matrix::scale(m, m, 1, -1, 1);
|
||||
|
||||
@@ -62,6 +62,7 @@ void Transform::resize(const Size size) {
|
||||
double scale{state.getScale()};
|
||||
double x{state.getX()};
|
||||
double y{state.getY()};
|
||||
double z{state.getZ()};
|
||||
|
||||
double lat;
|
||||
double lon;
|
||||
@@ -80,7 +81,7 @@ void Transform::resize(const Size size) {
|
||||
return;
|
||||
}
|
||||
state.constrain(scale, x, y);
|
||||
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y));
|
||||
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y).withZ(z));
|
||||
|
||||
observer.onCameraDidChange(MapObserver::CameraChangeMode::Immediate);
|
||||
}
|
||||
@@ -125,8 +126,11 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
|
||||
|
||||
double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing();
|
||||
double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch();
|
||||
double fov = camera.fov ? util::deg2rad(*camera.fov) : getFieldOfView();
|
||||
double centerAlt = camera.centerAltitude.value_or(state.getCenterAltitude());
|
||||
double roll = camera.roll ? util::deg2rad(*camera.roll) : getRoll();
|
||||
|
||||
if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch)) {
|
||||
if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch) || std::isnan(roll) || std::isnan(fov)) {
|
||||
if (animation.transitionFinishFn) {
|
||||
animation.transitionFinishFn();
|
||||
}
|
||||
@@ -146,6 +150,7 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
|
||||
startLatLng.unwrapForShortestPath(latLng);
|
||||
}
|
||||
}
|
||||
const double startCenterAlt = state.getCenterAltitude();
|
||||
|
||||
const Point<double> startPoint = Projection::project(startLatLng, state.getScale());
|
||||
const Point<double> endPoint = Projection::project(latLng, state.getScale());
|
||||
@@ -153,6 +158,7 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
|
||||
// Constrain camera options.
|
||||
zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
|
||||
pitch = util::clamp(pitch, state.getMinPitch(), state.getMaxPitch());
|
||||
fov = util::clamp(fov, state.getMinFieldOfView(), state.getMaxFieldOfView());
|
||||
|
||||
// Minimize rotation by taking the shorter path around the circle.
|
||||
bearing = _normalizeAngle(bearing, state.getBearing());
|
||||
@@ -161,6 +167,8 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
|
||||
const double startZoom = state.getZoom();
|
||||
const double startBearing = state.getBearing();
|
||||
const double startPitch = state.getPitch();
|
||||
const double startRoll = state.getRoll();
|
||||
const double startFov = state.getFieldOfView();
|
||||
state.setProperties(TransformStateProperties()
|
||||
.withPanningInProgress(unwrappedLatLng != startLatLng)
|
||||
.withScalingInProgress(zoom != startZoom)
|
||||
@@ -175,6 +183,7 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
|
||||
LatLng frameLatLng = Projection::unproject(framePoint, state.zoomScale(startZoom));
|
||||
double frameZoom = util::interpolate(startZoom, zoom, t);
|
||||
state.setLatLngZoom(frameLatLng, frameZoom);
|
||||
state.setCenterAltitude(util::interpolate(startCenterAlt, centerAlt, t));
|
||||
if (bearing != startBearing) {
|
||||
state.setBearing(util::wrap(util::interpolate(startBearing, bearing, t), -pi, pi));
|
||||
}
|
||||
@@ -190,6 +199,12 @@ void Transform::easeTo(const CameraOptions& inputCamera, const AnimationOptions&
|
||||
if (pitch != startPitch || maxPitch < startPitch) {
|
||||
state.setPitch(std::min(maxPitch, util::interpolate(startPitch, pitch, t)));
|
||||
}
|
||||
if (roll != startRoll) {
|
||||
state.setRoll(util::interpolate(startRoll, roll, t));
|
||||
}
|
||||
if (fov != startFov) {
|
||||
state.setFieldOfView(util::interpolate(startFov, fov, t));
|
||||
}
|
||||
},
|
||||
duration);
|
||||
}
|
||||
@@ -212,11 +227,14 @@ void Transform::flyTo(const CameraOptions& inputCamera,
|
||||
|
||||
const EdgeInsets& padding = camera.padding.value_or(state.getEdgeInsets());
|
||||
const LatLng& latLng = camera.center.value_or(getLatLng(LatLng::Unwrapped)).wrapped();
|
||||
|
||||
const double centerAlt = camera.centerAltitude.value_or(state.getCenterAltitude());
|
||||
double bearing = camera.bearing ? util::deg2rad(-*camera.bearing) : getBearing();
|
||||
double pitch = camera.pitch ? util::deg2rad(*camera.pitch) : getPitch();
|
||||
double roll = camera.roll ? util::deg2rad(*camera.roll) : getRoll();
|
||||
double fov = camera.fov ? util::deg2rad(*camera.fov) : getFieldOfView();
|
||||
|
||||
if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch) || state.getSize().isEmpty()) {
|
||||
if (std::isnan(zoom) || std::isnan(bearing) || std::isnan(pitch) || std::isnan(roll) || std::isnan(fov) ||
|
||||
state.getSize().isEmpty()) {
|
||||
if (animation.transitionFinishFn) {
|
||||
animation.transitionFinishFn();
|
||||
}
|
||||
@@ -226,6 +244,7 @@ void Transform::flyTo(const CameraOptions& inputCamera,
|
||||
// Determine endpoints.
|
||||
LatLng startLatLng = getLatLng(LatLng::Unwrapped).wrapped();
|
||||
startLatLng.unwrapForShortestPath(latLng);
|
||||
const double startCenterAlt = state.getCenterAltitude();
|
||||
|
||||
const Point<double> startPoint = Projection::project(startLatLng, state.getScale());
|
||||
const Point<double> endPoint = Projection::project(latLng, state.getScale());
|
||||
@@ -233,6 +252,7 @@ void Transform::flyTo(const CameraOptions& inputCamera,
|
||||
// Constrain camera options.
|
||||
zoom = util::clamp(zoom, state.getMinZoom(), state.getMaxZoom());
|
||||
pitch = util::clamp(pitch, state.getMinPitch(), state.getMaxPitch());
|
||||
fov = util::clamp(fov, state.getMinFieldOfView(), state.getMaxFieldOfView());
|
||||
|
||||
// Minimize rotation by taking the shorter path around the circle.
|
||||
bearing = _normalizeAngle(bearing, state.getBearing());
|
||||
@@ -240,6 +260,8 @@ void Transform::flyTo(const CameraOptions& inputCamera,
|
||||
const double startZoom = state.scaleZoom(state.getScale());
|
||||
const double startBearing = state.getBearing();
|
||||
const double startPitch = state.getPitch();
|
||||
const double startRoll = state.getRoll();
|
||||
const double startFov = state.getFieldOfView();
|
||||
|
||||
/// w₀: Initial visible span, measured in pixels at the initial scale.
|
||||
/// Known henceforth as a <i>screenful</i>.
|
||||
@@ -353,6 +375,7 @@ void Transform::flyTo(const CameraOptions& inputCamera,
|
||||
// Convert to geographic coordinates and set the new viewpoint.
|
||||
LatLng frameLatLng = Projection::unproject(framePoint, startScale);
|
||||
state.setLatLngZoom(frameLatLng, frameZoom);
|
||||
state.setCenterAltitude(util::interpolate(startCenterAlt, centerAlt, us));
|
||||
if (bearing != startBearing) {
|
||||
state.setBearing(util::wrap(util::interpolate(startBearing, bearing, k), -pi, pi));
|
||||
}
|
||||
@@ -364,10 +387,15 @@ void Transform::flyTo(const CameraOptions& inputCamera,
|
||||
util::interpolate(startEdgeInsets.bottom(), padding.bottom(), k),
|
||||
util::interpolate(startEdgeInsets.right(), padding.right(), k)});
|
||||
}
|
||||
double maxPitch = getMaxPitchForEdgeInsets(state.getEdgeInsets());
|
||||
|
||||
if (pitch != startPitch || maxPitch < startPitch) {
|
||||
state.setPitch(std::min(maxPitch, util::interpolate(startPitch, pitch, k)));
|
||||
if (pitch != startPitch) {
|
||||
state.setPitch(util::interpolate(startPitch, pitch, k));
|
||||
}
|
||||
if (roll != startRoll) {
|
||||
state.setPitch(util::interpolate(startRoll, roll, k));
|
||||
}
|
||||
if (fov != startFov) {
|
||||
state.setFieldOfView(util::interpolate(startFov, fov, k));
|
||||
}
|
||||
},
|
||||
duration);
|
||||
@@ -472,6 +500,14 @@ double Transform::getPitch() const {
|
||||
return state.getPitch();
|
||||
}
|
||||
|
||||
double Transform::getRoll() const {
|
||||
return state.getRoll();
|
||||
}
|
||||
|
||||
double Transform::getFieldOfView() const {
|
||||
return state.getFieldOfView();
|
||||
}
|
||||
|
||||
// MARK: - North Orientation
|
||||
|
||||
void Transform::setNorthOrientation(NorthOrientation orientation) {
|
||||
@@ -479,8 +515,9 @@ void Transform::setNorthOrientation(NorthOrientation orientation) {
|
||||
double scale{state.getScale()};
|
||||
double x{state.getX()};
|
||||
double y{state.getY()};
|
||||
double z{state.getZ()};
|
||||
state.constrain(scale, x, y);
|
||||
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y));
|
||||
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y).withZ(z));
|
||||
}
|
||||
|
||||
NorthOrientation Transform::getNorthOrientation() const {
|
||||
@@ -494,8 +531,9 @@ void Transform::setConstrainMode(mbgl::ConstrainMode mode) {
|
||||
double scale{state.getScale()};
|
||||
double x{state.getX()};
|
||||
double y{state.getY()};
|
||||
double z{state.getZ()};
|
||||
state.constrain(scale, x, y);
|
||||
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y));
|
||||
state.setProperties(TransformStateProperties().withScale(scale).withX(x).withY(y).withZ(z));
|
||||
}
|
||||
|
||||
ConstrainMode Transform::getConstrainMode() const {
|
||||
@@ -676,22 +714,16 @@ LatLng Transform::screenCoordinateToLatLng(const ScreenCoordinate& point, LatLng
|
||||
|
||||
double Transform::getMaxPitchForEdgeInsets(const EdgeInsets& insets) const {
|
||||
double centerOffsetY = 0.5 * (insets.top() - insets.bottom()); // See TransformState::getCenterOffset.
|
||||
if (centerOffsetY == 0.0) {
|
||||
return state.getMaxPitch();
|
||||
}
|
||||
|
||||
const auto height = state.getSize().height;
|
||||
assert(height);
|
||||
// For details, see description at
|
||||
// https://github.com/mapbox/mapbox-gl-native/pull/15195 The definition of
|
||||
// half of TransformState::fov with no inset, is: fov = arctan((height / 2)
|
||||
// / (height * 1.5)). We use half of fov, as it is field of view above
|
||||
// perspective center. With inset, this angle changes and
|
||||
// tangentOfFovAboveCenterAngle = (h/2 + centerOffsetY) / (height
|
||||
// * 1.5). 1.03 is a bit extra added to prevent parallel ground to viewport
|
||||
// clipping plane.
|
||||
const double tangentOfFovAboveCenterAngle = 1.03 * (height / 2.0 + centerOffsetY) / (1.5 * height);
|
||||
// Half of fov is the field of view above perspective center.
|
||||
const double tangentOfFovAboveCenterAngle = (0.5 + centerOffsetY / height) * 2.0 * tan(getFieldOfView() / 2.0);
|
||||
const double fovAboveCenter = std::atan(tangentOfFovAboveCenterAngle);
|
||||
return pi * 0.5 - fovAboveCenter;
|
||||
// e.g. Maximum pitch of 60 degrees is when perspective center's offset from
|
||||
// the top is 84% of screen height.
|
||||
return state.getMaxPitch() + getFieldOfView() / 2.0 - fovAboveCenter;
|
||||
}
|
||||
|
||||
FreeCameraOptions Transform::getFreeCameraOptions() const {
|
||||
|
||||
@@ -91,6 +91,8 @@ public:
|
||||
// Pitch
|
||||
|
||||
double getPitch() const;
|
||||
double getRoll() const;
|
||||
double getFieldOfView() const;
|
||||
|
||||
// North Orientation
|
||||
void setNorthOrientation(NorthOrientation);
|
||||
|
||||
@@ -15,6 +15,14 @@ using namespace std::numbers;
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
/*
|
||||
* The maximum angle to use for the Mercator horizon. This must be less than 90
|
||||
* to prevent errors in `MercatorTransform::_calcMatrices()`. It shouldn't be too close
|
||||
* to 90, or the distance to the horizon will become very large, unnecessarily increasing
|
||||
* the number of tiles needed to render the map.
|
||||
*/
|
||||
const double maxMercatorHorizonAngle = util::deg2rad(89.25);
|
||||
|
||||
namespace {
|
||||
LatLng latLngFromMercator(Point<double> mercatorCoordinate, LatLng::WrapMode wrapMode = LatLng::WrapMode::Unwrapped) {
|
||||
return {util::rad2deg(2 * std::atan(std::exp(pi - mercatorCoordinate.y * 2 * pi)) - (pi / 2)),
|
||||
@@ -46,15 +54,24 @@ void TransformState::setProperties(const TransformStateProperties& properties) {
|
||||
if (properties.y) {
|
||||
setY(*properties.y);
|
||||
}
|
||||
if (properties.z) {
|
||||
setZ(*properties.z);
|
||||
}
|
||||
if (properties.scale) {
|
||||
setScale(*properties.scale);
|
||||
}
|
||||
if (properties.bearing) {
|
||||
setBearing(*properties.bearing);
|
||||
}
|
||||
if (properties.fov) {
|
||||
setFieldOfView(*properties.fov);
|
||||
}
|
||||
if (properties.pitch) {
|
||||
setPitch(*properties.pitch);
|
||||
}
|
||||
if (properties.roll) {
|
||||
setRoll(*properties.roll);
|
||||
}
|
||||
if (properties.xSkew) {
|
||||
setXSkew(*properties.xSkew);
|
||||
}
|
||||
@@ -116,6 +133,9 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
|
||||
const double cameraToCenterDistance = getCameraToCenterDistance();
|
||||
const ScreenCoordinate offset = getCenterOffset();
|
||||
|
||||
const double limitedPitch = util::clamp(getPitch(), 0.0, maxMercatorHorizonAngle);
|
||||
const double cameraToSeaLevelDistance = cameraToCenterDistance + std::abs(z) / std::cos(limitedPitch);
|
||||
|
||||
// Find the Z distance from the viewport center point
|
||||
// [width/2 + offset.x, height/2 + offset.y] to the top edge; to point
|
||||
// [width/2 + offset.x, 0] in Z units.
|
||||
@@ -123,12 +143,13 @@ void TransformState::getProjMatrix(mat4& projMatrix, uint16_t nearZ, bool aligne
|
||||
// (the distance between[width/2, height/2] and [width/2 + 1, height/2])
|
||||
// See https://github.com/mapbox/mapbox-gl-native/pull/15195 for details.
|
||||
// See TransformState::fov description: fov = 2 * arctan((height / 2) / (height * 1.5)).
|
||||
const double tanFovAboveCenter =
|
||||
((size.height - frustumOffset.top()) * 0.5 + (offset.y - (frustumOffset.top() / 2))) / (size.height * 1.5);
|
||||
const double tanMultiple = tanFovAboveCenter * std::tan(getPitch());
|
||||
const double tanFovAboveCenter = (0.5 + (offset.y - frustumOffset.top()) / size.height) * 2.0 *
|
||||
std::tan(fov / 2.0) *
|
||||
(std::abs(std::cos(roll)) + std::abs(std::sin(roll)) * size.width / size.height);
|
||||
const double tanMultiple = util::clamp(tanFovAboveCenter * std::tan(limitedPitch), 0.0, 0.99);
|
||||
assert(tanMultiple < 1);
|
||||
// Calculate z distance of the farthest fragment that should be rendered.
|
||||
const double furthestDistance = cameraToCenterDistance / (1 - tanMultiple);
|
||||
const double furthestDistance = cameraToSeaLevelDistance / (1 - tanMultiple);
|
||||
// Add a bit extra to avoid precision problems when a fragment's distance is exactly `furthestDistance`
|
||||
const double farZ = furthestDistance * 1.01;
|
||||
|
||||
@@ -208,13 +229,13 @@ void TransformState::updateCameraState() const {
|
||||
const double dy = 0.5 * worldSize - y;
|
||||
|
||||
// Set camera orientation and move it to a proper distance from the map
|
||||
camera.setOrientation(getPitch(), getBearing());
|
||||
camera.setOrientation(getRoll(), getPitch(), getBearing());
|
||||
|
||||
const vec3 forward = camera.forward();
|
||||
const vec3 orbitPosition = {{-forward[0] * cameraToCenterDistance,
|
||||
-forward[1] * cameraToCenterDistance,
|
||||
-forward[2] * cameraToCenterDistance}};
|
||||
vec3 cameraPosition = {{dx + orbitPosition[0], dy + orbitPosition[1], orbitPosition[2]}};
|
||||
vec3 cameraPosition = {{dx + orbitPosition[0], dy + orbitPosition[1], z + orbitPosition[2]}};
|
||||
|
||||
cameraPosition[0] /= worldSize;
|
||||
cameraPosition[1] /= worldSize;
|
||||
@@ -230,26 +251,39 @@ void TransformState::updateStateFromCamera() {
|
||||
const double dx = forward[0];
|
||||
const double dy = forward[1];
|
||||
const double dz = forward[2];
|
||||
assert(position[2] > 0.0 && dz < 0.0);
|
||||
|
||||
// Compute bearing and pitch
|
||||
double newBearing;
|
||||
double newPitch;
|
||||
camera.getOrientation(newPitch, newBearing);
|
||||
double newRoll;
|
||||
camera.getOrientation(newRoll, newPitch, newBearing);
|
||||
newPitch = util::clamp(newPitch, minPitch, maxPitch);
|
||||
|
||||
// Compute zoom level from the camera altitude
|
||||
const double centerDistance = getCameraToCenterDistance();
|
||||
const double zoom = util::log2(centerDistance / (position[2] / std::cos(newPitch) * util::tileSize_D));
|
||||
const double newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale);
|
||||
double zoom;
|
||||
double newScale;
|
||||
double travel;
|
||||
if (dz < -1.0e-9 && position[2] > 1.0e-9 && newPitch <= maxMercatorHorizonAngle) {
|
||||
zoom = util::log2(centerDistance / (position[2] / std::cos(newPitch) * util::tileSize_D));
|
||||
newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale);
|
||||
travel = -position[2] / dz;
|
||||
} else {
|
||||
zoom = 14;
|
||||
newScale = util::clamp(std::pow(2.0, zoom), min_scale, max_scale);
|
||||
travel = centerDistance / newScale / util::tileSize_D;
|
||||
}
|
||||
|
||||
// Compute center point of the map
|
||||
const double travel = -position[2] / dz;
|
||||
const Point<double> mercatorPoint = {position[0] + dx * travel, position[1] + dy * travel};
|
||||
|
||||
setLatLngZoom(latLngFromMercator(mercatorPoint), scaleZoom(newScale));
|
||||
|
||||
const double mercatorZ = position[2] + dz * travel;
|
||||
double alt_m = mercatorZ * Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), 0) * util::tileSize_D;
|
||||
setCenterAltitude(alt_m);
|
||||
setBearing(newBearing);
|
||||
setPitch(newPitch);
|
||||
setRoll(newRoll);
|
||||
}
|
||||
|
||||
FreeCameraOptions TransformState::getFreeCameraOptions() const {
|
||||
@@ -291,11 +325,6 @@ bool TransformState::setCameraOrientation(const Quaternion& orientation_) {
|
||||
const vec3 forward = unitQuat.transform({{0.0, 0.0, -1.0}});
|
||||
const vec3 up = unitQuat.transform({{0.0, -1.0, 0.0}});
|
||||
|
||||
if (up[2] < 0.0) {
|
||||
// Camera is upside down and not recoverable
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::optional<Quaternion> updatedOrientation = util::Camera::orientationFromFrame(forward, up);
|
||||
if (!updatedOrientation) return false;
|
||||
|
||||
@@ -443,10 +472,13 @@ void TransformState::setViewportMode(ViewportMode val) {
|
||||
CameraOptions TransformState::getCameraOptions(const std::optional<EdgeInsets>& padding) const {
|
||||
return CameraOptions()
|
||||
.withCenter(getLatLng())
|
||||
.withCenterAltitude(getCenterAltitude())
|
||||
.withPadding(padding ? padding : edgeInsets)
|
||||
.withZoom(getZoom())
|
||||
.withBearing(util::rad2deg(-bearing))
|
||||
.withPitch(util::rad2deg(pitch));
|
||||
.withPitch(util::rad2deg(pitch))
|
||||
.withRoll(util::rad2deg(roll))
|
||||
.withFov(util::rad2deg(fov));
|
||||
}
|
||||
|
||||
// MARK: - EdgeInsets
|
||||
@@ -464,6 +496,10 @@ LatLng TransformState::getLatLng(LatLng::WrapMode wrapMode) const {
|
||||
return {util::rad2deg(2 * std::atan(std::exp(y / Cc)) - 0.5 * pi), -x / Bc, wrapMode};
|
||||
}
|
||||
|
||||
double TransformState::getCenterAltitude() const {
|
||||
return z * Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), getZoom());
|
||||
}
|
||||
|
||||
double TransformState::pixel_x() const {
|
||||
const double center = (size.width - Projection::worldSize(scale)) / 2;
|
||||
return center + x;
|
||||
@@ -554,6 +590,14 @@ double TransformState::getMaxPitch() const {
|
||||
return maxPitch;
|
||||
}
|
||||
|
||||
double TransformState::getMinFieldOfView() const {
|
||||
return minFov;
|
||||
}
|
||||
|
||||
double TransformState::getMaxFieldOfView() const {
|
||||
return maxFov;
|
||||
}
|
||||
|
||||
// MARK: - Scale
|
||||
double TransformState::getScale() const {
|
||||
return scale;
|
||||
@@ -590,6 +634,17 @@ void TransformState::setY(double val) {
|
||||
}
|
||||
}
|
||||
|
||||
double TransformState::getZ() const {
|
||||
return z;
|
||||
}
|
||||
|
||||
void TransformState::setZ(double val) {
|
||||
if (z != val) {
|
||||
z = val;
|
||||
requestMatricesUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Rotation
|
||||
|
||||
double TransformState::getBearing() const {
|
||||
@@ -607,6 +662,24 @@ float TransformState::getFieldOfView() const {
|
||||
return static_cast<float>(fov);
|
||||
}
|
||||
|
||||
void TransformState::setFieldOfView(double val) {
|
||||
if (fov != val) {
|
||||
fov = val;
|
||||
requestMatricesUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
double TransformState::getRoll() const {
|
||||
return roll;
|
||||
}
|
||||
|
||||
void TransformState::setRoll(double val) {
|
||||
if (roll != val) {
|
||||
roll = val;
|
||||
requestMatricesUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
float TransformState::getCameraToCenterDistance() const {
|
||||
return static_cast<float>(0.5 * size.height / std::tan(fov / 2.0));
|
||||
}
|
||||
@@ -730,7 +803,7 @@ TileCoordinate TransformState::screenCoordinateToTileCoordinate(const ScreenCoor
|
||||
|
||||
double z0 = coord0[2] / w0;
|
||||
double z1 = coord1[2] / w1;
|
||||
double t = z0 == z1 ? 0 : (targetZ - z0) / (z1 - z0);
|
||||
double t = z0 <= z1 ? 0 : (targetZ - z0) / (z1 - z0);
|
||||
|
||||
Point<double> p = util::interpolate(p0, p1, t) / scale * static_cast<double>(1 << atZoom);
|
||||
return {.p = {p.x, p.y}, .z = static_cast<double>(atZoom)};
|
||||
@@ -957,6 +1030,11 @@ void TransformState::setLatLngZoom(const LatLng& latLng, double zoom) {
|
||||
setScalePoint(newScale, point);
|
||||
}
|
||||
|
||||
void TransformState::setCenterAltitude(double alt_m) {
|
||||
z = alt_m / Projection::getMetersPerPixelAtLatitude(getLatLng().latitude(), getZoom());
|
||||
requestMatricesUpdate = true;
|
||||
}
|
||||
|
||||
void TransformState::setScalePoint(const double newScale, const ScreenCoordinate& point) {
|
||||
double constrainedScale = newScale;
|
||||
ScreenCoordinate constrainedPoint = point;
|
||||
|
||||
@@ -29,10 +29,18 @@ struct TransformStateProperties {
|
||||
y = val;
|
||||
return *this;
|
||||
}
|
||||
TransformStateProperties& withZ(const std::optional<double>& val) {
|
||||
z = val;
|
||||
return *this;
|
||||
}
|
||||
TransformStateProperties& withScale(const std::optional<double>& val) {
|
||||
scale = val;
|
||||
return *this;
|
||||
}
|
||||
TransformStateProperties& withFov(const std::optional<double>& val) {
|
||||
fov = val;
|
||||
return *this;
|
||||
}
|
||||
TransformStateProperties& withBearing(const std::optional<double>& val) {
|
||||
bearing = val;
|
||||
return *this;
|
||||
@@ -41,6 +49,10 @@ struct TransformStateProperties {
|
||||
pitch = val;
|
||||
return *this;
|
||||
}
|
||||
TransformStateProperties& withRoll(const std::optional<double>& val) {
|
||||
roll = val;
|
||||
return *this;
|
||||
}
|
||||
TransformStateProperties& withXSkew(const std::optional<double>& val) {
|
||||
xSkew = val;
|
||||
return *this;
|
||||
@@ -92,9 +104,12 @@ struct TransformStateProperties {
|
||||
|
||||
std::optional<double> x;
|
||||
std::optional<double> y;
|
||||
std::optional<double> bearing;
|
||||
std::optional<double> z;
|
||||
std::optional<double> fov;
|
||||
std::optional<double> scale;
|
||||
std::optional<double> bearing;
|
||||
std::optional<double> pitch;
|
||||
std::optional<double> roll;
|
||||
std::optional<double> xSkew;
|
||||
std::optional<double> ySkew;
|
||||
std::optional<bool> axonometric;
|
||||
@@ -147,6 +162,7 @@ public:
|
||||
|
||||
// Position
|
||||
LatLng getLatLng(LatLng::WrapMode = LatLng::Unwrapped) const;
|
||||
double getCenterAltitude() const;
|
||||
double pixel_x() const;
|
||||
double pixel_y() const;
|
||||
|
||||
@@ -164,6 +180,8 @@ public:
|
||||
void setX(double);
|
||||
double getY() const;
|
||||
void setY(double);
|
||||
double getZ() const;
|
||||
void setZ(double);
|
||||
|
||||
// Bounds
|
||||
void setLatLngBounds(LatLngBounds);
|
||||
@@ -176,14 +194,19 @@ public:
|
||||
double getMinPitch() const;
|
||||
void setMaxPitch(double);
|
||||
double getMaxPitch() const;
|
||||
double getMinFieldOfView() const;
|
||||
double getMaxFieldOfView() const;
|
||||
|
||||
// Rotation
|
||||
double getBearing() const;
|
||||
void setBearing(double);
|
||||
float getFieldOfView() const;
|
||||
void setFieldOfView(double);
|
||||
float getCameraToCenterDistance() const;
|
||||
double getPitch() const;
|
||||
void setPitch(double);
|
||||
double getRoll() const;
|
||||
void setRoll(double);
|
||||
|
||||
double getXSkew() const;
|
||||
void setXSkew(double);
|
||||
@@ -222,6 +245,7 @@ public:
|
||||
point on screen. */
|
||||
void moveLatLng(const LatLng&, const ScreenCoordinate&);
|
||||
void setLatLngZoom(const LatLng& latLng, double zoom);
|
||||
void setCenterAltitude(double alt_m);
|
||||
|
||||
void constrain(double& scale, double& x, double& y) const;
|
||||
bool constrainScreen(double& scale_, double& x_, double& y_) const;
|
||||
@@ -248,7 +272,9 @@ private:
|
||||
|
||||
// Limit the amount of pitch
|
||||
double minPitch = util::PITCH_MIN;
|
||||
double maxPitch = util::PITCH_MAX;
|
||||
double maxPitch = util::DEFAULT_PITCH_MAX;
|
||||
double minFov = util::deg2rad(0.1);
|
||||
double maxFov = util::deg2rad(150.0);
|
||||
|
||||
NorthOrientation orientation = NorthOrientation::Upwards;
|
||||
|
||||
@@ -283,15 +309,12 @@ private:
|
||||
bool gestureInProgress = false;
|
||||
|
||||
// map position
|
||||
double x = 0, y = 0;
|
||||
double x = 0, y = 0, z = 0;
|
||||
double bearing = 0;
|
||||
double scale = 1;
|
||||
// This fov value is somewhat arbitrary. The altitude of the camera used
|
||||
// to be defined as 1.5 screen heights above the ground, which was an
|
||||
// arbitrary choice. This is the fov equivalent to that value calculated
|
||||
// with: `fov = 2 * arctan((height / 2) / (height * 1.5))`
|
||||
double fov = 0.6435011087932844;
|
||||
double fov = util::DEFAULT_FOV;
|
||||
double pitch = 0.0;
|
||||
double roll = 0.0;
|
||||
double xSkew = 0.0;
|
||||
double ySkew = 1.0;
|
||||
bool axonometric = false;
|
||||
|
||||
@@ -69,6 +69,13 @@ StyleParseResult Parser::parse(const std::string& json) {
|
||||
}
|
||||
}
|
||||
|
||||
if (document.HasMember("centerAltitude")) {
|
||||
const JSValue& value = document["centerAltitude"];
|
||||
if (value.IsNumber()) {
|
||||
centerAltitude = value.GetDouble();
|
||||
}
|
||||
}
|
||||
|
||||
if (document.HasMember("zoom")) {
|
||||
const JSValue& value = document["zoom"];
|
||||
if (value.IsNumber()) {
|
||||
@@ -90,6 +97,13 @@ StyleParseResult Parser::parse(const std::string& json) {
|
||||
}
|
||||
}
|
||||
|
||||
if (document.HasMember("roll")) {
|
||||
const JSValue& value = document["roll"];
|
||||
if (value.IsNumber()) {
|
||||
roll = value.GetDouble();
|
||||
}
|
||||
}
|
||||
|
||||
if (document.HasMember("transition")) {
|
||||
parseTransition(document["transition"]);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ public:
|
||||
double zoom = 0;
|
||||
double bearing = 0;
|
||||
double pitch = 0;
|
||||
double roll = 0;
|
||||
double centerAltitude = 0;
|
||||
|
||||
// Statically evaluate layer properties to determine what font stacks are used.
|
||||
std::set<FontStack> fontStacks() const;
|
||||
|
||||
@@ -115,9 +115,11 @@ void Style::Impl::parse(const std::string& json_) {
|
||||
|
||||
name = parser.name;
|
||||
defaultCamera.center = parser.latLng;
|
||||
defaultCamera.centerAltitude = parser.centerAltitude;
|
||||
defaultCamera.zoom = parser.zoom;
|
||||
defaultCamera.bearing = parser.bearing;
|
||||
defaultCamera.pitch = parser.pitch;
|
||||
defaultCamera.roll = parser.roll;
|
||||
|
||||
setLight(std::make_unique<Light>(parser.light));
|
||||
|
||||
|
||||
@@ -14,20 +14,6 @@ using namespace std::numbers;
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
namespace {
|
||||
double vec2Len(const vec2& v) noexcept {
|
||||
return std::sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
};
|
||||
|
||||
double vec2Dot(const vec2& a, const vec2& b) noexcept {
|
||||
return a[0] * b[0] + a[1] * b[1];
|
||||
};
|
||||
|
||||
vec2 vec2Scale(const vec2& v, double s) noexcept {
|
||||
return vec2{{v[0] * s, v[1] * s}};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace util {
|
||||
namespace {
|
||||
|
||||
@@ -66,12 +52,13 @@ vec3 toMercator(const LatLng& location, double altitudeMeters) noexcept {
|
||||
altitudeMeters * pixelsPerMeter / worldSize}};
|
||||
}
|
||||
|
||||
Quaternion orientationFromPitchBearing(double pitch, double bearing) noexcept {
|
||||
Quaternion orientationFromRollPitchBearing(double roll, double pitch, double bearing) noexcept {
|
||||
// Both angles have to be negated to achieve CW rotation around the axis of rotation
|
||||
const Quaternion rotBearing = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, -bearing);
|
||||
const Quaternion rotPitch = Quaternion::fromAxisAngle({{1.0, 0.0, 0.0}}, -pitch);
|
||||
Quaternion rotBearing = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, -bearing);
|
||||
Quaternion rotPitch = Quaternion::fromAxisAngle({{1.0, 0.0, 0.0}}, -pitch);
|
||||
Quaternion rotRoll = Quaternion::fromAxisAngle({{0.0, 0.0, 1.0}}, roll);
|
||||
|
||||
return rotBearing.multiply(rotPitch);
|
||||
return rotBearing.multiply(rotPitch).multiply(rotRoll);
|
||||
}
|
||||
|
||||
void updateTransform(mat4& transform, const Quaternion& orientation) noexcept {
|
||||
@@ -170,16 +157,24 @@ vec3 Camera::up() const noexcept {
|
||||
return {{-column[0], -column[1], -column[2]}};
|
||||
}
|
||||
|
||||
void Camera::getOrientation(double& pitch, double& bearing) const noexcept {
|
||||
void Camera::getOrientation(double& roll, double& pitch, double& bearing) const noexcept {
|
||||
const vec3 f = forward();
|
||||
const vec3 r = right();
|
||||
const vec3 u = up();
|
||||
|
||||
bearing = std::atan2(-r[1], r[0]);
|
||||
pitch = std::atan2(std::sqrt(f[0] * f[0] + f[1] * f[1]), -f[2]);
|
||||
const double f01 = std::sqrt(f[0] * f[0] + f[1] * f[1]);
|
||||
pitch = std::atan2(f01, -f[2]);
|
||||
if (f01 > 1.0e-3) {
|
||||
bearing = -std::atan2(f[0], -f[1]);
|
||||
roll = -std::atan2(r[2], u[2]);
|
||||
} else {
|
||||
bearing = std::atan2(-r[1], r[0]);
|
||||
roll = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::setOrientation(double pitch, double bearing) noexcept {
|
||||
orientation = orientationFromPitchBearing(pitch, bearing);
|
||||
void Camera::setOrientation(double roll, double pitch, double bearing) noexcept {
|
||||
orientation = orientationFromRollPitchBearing(roll, pitch, bearing);
|
||||
updateTransform(transform, orientation);
|
||||
}
|
||||
|
||||
@@ -193,32 +188,27 @@ void Camera::setPosition(const vec3& position) noexcept {
|
||||
}
|
||||
|
||||
std::optional<Quaternion> Camera::orientationFromFrame(const vec3& forward, const vec3& up) noexcept {
|
||||
vec3 upVector = up;
|
||||
|
||||
const vec2 xyForward = {{forward[0], forward[1]}};
|
||||
vec2 xyUp = {{up[0], up[1]}};
|
||||
const double epsilon = 1e-15;
|
||||
|
||||
// Remove roll-component of the resulting orientation by projecting
|
||||
// the up-vector to the forward vector on xy-plane
|
||||
if (vec2Len(xyForward) >= epsilon) {
|
||||
const vec2 xyDir = vec2Scale(xyForward, 1.0 / vec2Len(xyForward));
|
||||
|
||||
xyUp = vec2Scale(xyDir, vec2Dot(xyUp, xyDir));
|
||||
upVector[0] = xyUp[0];
|
||||
upVector[1] = xyUp[1];
|
||||
}
|
||||
|
||||
const vec3 right = vec3Cross(upVector, forward);
|
||||
const vec3 right = vec3Cross(up, forward);
|
||||
|
||||
if (vec3Length(right) < epsilon) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const double bearing = std::atan2(-right[1], right[0]);
|
||||
const double pitch = std::atan2(std::sqrt(forward[0] * forward[0] + forward[1] * forward[1]), -forward[2]);
|
||||
const double f01 = std::sqrt(forward[0] * forward[0] + forward[1] * forward[1]);
|
||||
const double pitch = std::atan2(f01, -forward[2]);
|
||||
double bearing;
|
||||
double roll;
|
||||
if (f01 > 1.0e-3) {
|
||||
bearing = -std::atan2(forward[0], -forward[1]);
|
||||
roll = -std::atan2(right[2], up[2]);
|
||||
} else {
|
||||
bearing = std::atan2(-right[1], right[0]);
|
||||
roll = 0.0;
|
||||
}
|
||||
|
||||
return util::orientationFromPitchBearing(pitch, bearing);
|
||||
return util::orientationFromRollPitchBearing(roll, pitch, bearing);
|
||||
}
|
||||
} // namespace util
|
||||
|
||||
@@ -258,14 +248,16 @@ void FreeCameraOptions::lookAtPoint(const LatLng& location, const std::optional<
|
||||
// Flip z-component of the up vector if it's pointing downwards
|
||||
up[2] = std::abs(up[2]);
|
||||
|
||||
const auto newOrientation = util::Camera::orientationFromFrame(forward, up);
|
||||
if (newOrientation) {
|
||||
orientation = newOrientation.value().m;
|
||||
auto q = util::Camera::orientationFromFrame(forward, up);
|
||||
|
||||
if (q) {
|
||||
orientation = q->m;
|
||||
}
|
||||
}
|
||||
|
||||
void FreeCameraOptions::setPitchBearing(double pitch, double bearing) noexcept {
|
||||
orientation = util::orientationFromPitchBearing(util::deg2rad(pitch), util::deg2rad(bearing)).m;
|
||||
void FreeCameraOptions::setRollPitchBearing(double roll, double pitch, double bearing) noexcept {
|
||||
orientation =
|
||||
util::orientationFromRollPitchBearing(util::deg2rad(roll), util::deg2rad(pitch), util::deg2rad(bearing)).m;
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -25,14 +25,13 @@ public:
|
||||
vec3 up() const noexcept;
|
||||
|
||||
const Quaternion& getOrientation() const noexcept { return orientation; }
|
||||
void getOrientation(double& pitch, double& bearing) const noexcept;
|
||||
void setOrientation(double pitch, double bearing) noexcept;
|
||||
void getOrientation(double& roll, double& pitch, double& bearing) const noexcept;
|
||||
void setOrientation(double roll, double pitch, double bearing) noexcept;
|
||||
void setOrientation(const Quaternion& orientation_) noexcept;
|
||||
void setPosition(const vec3& position) noexcept;
|
||||
|
||||
// Computes orientation using forward and up vectors of the provided
|
||||
// coordinate frame. Only bearing and pitch components will be used. Does
|
||||
// not return a value if input is invalid
|
||||
// coordinate frame. Does not return a value if input is invalid
|
||||
static std::optional<Quaternion> orientationFromFrame(const vec3& forward, const vec3& up) noexcept;
|
||||
|
||||
private:
|
||||
|
||||
@@ -916,7 +916,6 @@ TEST(Transform, MinMaxPitch) {
|
||||
transform.setMaxPitch(70);
|
||||
transform.jumpTo(CameraOptions().withPitch(70));
|
||||
ASSERT_DOUBLE_EQ(transform.getState().getMaxPitch(), transform.getPitch());
|
||||
ASSERT_DOUBLE_EQ(util::deg2rad(60), transform.getPitch());
|
||||
|
||||
transform.setMaxPitch(45);
|
||||
transform.jumpTo(CameraOptions().withPitch(60));
|
||||
@@ -1012,6 +1011,27 @@ TEST(Transform, FreeCameraOptionsInvalidZ) {
|
||||
EXPECT_LE(transform.getFreeCameraOptions().position.value()[2], 1.0);
|
||||
}
|
||||
|
||||
TEST(Transform, FreeCameraOptionsHighPitch) {
|
||||
Transform transform;
|
||||
transform.resize({100, 100});
|
||||
transform.setMaxPitch(180.0);
|
||||
FreeCameraOptions options;
|
||||
|
||||
options.position = vec3{{0.1, 0.1, 0.1}};
|
||||
options.orientation = Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, util::deg2rad(-90.0)).m;
|
||||
transform.setFreeCameraOptions(options);
|
||||
EXPECT_DOUBLE_EQ(util::deg2rad(90.0), transform.getState().getPitch());
|
||||
EXPECT_DOUBLE_EQ(0.0, transform.getState().getBearing());
|
||||
EXPECT_THAT(transform.getFreeCameraOptions().position.value(), Vec3NearEquals1E5(vec3{{0.1, 0.1, 0.1}}));
|
||||
|
||||
options.position = vec3{{0.1, 0.1, 0.1}};
|
||||
options.orientation = Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, util::deg2rad(-135.0)).m;
|
||||
transform.setFreeCameraOptions(options);
|
||||
EXPECT_DOUBLE_EQ(util::deg2rad(135.0), transform.getState().getPitch());
|
||||
EXPECT_DOUBLE_EQ(0.0, transform.getState().getBearing());
|
||||
EXPECT_THAT(transform.getFreeCameraOptions().position.value(), Vec3NearEquals1E5(vec3{{0.1, 0.1, 0.1}}));
|
||||
}
|
||||
|
||||
TEST(Transform, FreeCameraOptionsInvalidOrientation) {
|
||||
// Invalid orientations that cannot be clamped into a valid range
|
||||
Transform transform;
|
||||
@@ -1023,14 +1043,21 @@ TEST(Transform, FreeCameraOptionsInvalidOrientation) {
|
||||
EXPECT_EQ(Quaternion::identity.m, transform.getFreeCameraOptions().orientation);
|
||||
|
||||
// Gimbal lock. Both forward and up vectors are on xy-plane
|
||||
transform.setMaxPitch(180.0);
|
||||
options.orientation = Quaternion::fromAxisAngle(vec3{{0.0, 1.0, 0.0}}, pi / 2).m;
|
||||
transform.setFreeCameraOptions(options);
|
||||
EXPECT_EQ(Quaternion::identity.m, transform.getFreeCameraOptions().orientation);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
EXPECT_NEAR(Quaternion::fromEulerAngles(pi / 2, pi / 2, pi / 2).m[i],
|
||||
transform.getFreeCameraOptions().orientation.value()[i],
|
||||
1.0e-12);
|
||||
}
|
||||
|
||||
// Camera is upside down
|
||||
options.orientation = Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, pi / 2 + pi / 4).m;
|
||||
transform.setFreeCameraOptions(options);
|
||||
EXPECT_EQ(Quaternion::identity.m, transform.getFreeCameraOptions().orientation);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
EXPECT_NEAR(options.orientation.value()[i], transform.getFreeCameraOptions().orientation.value()[i], 1.0e-12);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Transform, FreeCameraOptionsSetOrientation) {
|
||||
@@ -1056,6 +1083,7 @@ TEST(Transform, FreeCameraOptionsSetOrientation) {
|
||||
transform.setFreeCameraOptions(options);
|
||||
EXPECT_DOUBLE_EQ(util::deg2rad(-56.0), transform.getState().getBearing());
|
||||
EXPECT_DOUBLE_EQ(0.0, transform.getState().getPitch());
|
||||
EXPECT_DOUBLE_EQ(0.0, transform.getState().getRoll());
|
||||
EXPECT_DOUBLE_EQ(0.0, transform.getState().getX());
|
||||
EXPECT_NEAR(152.192378, transform.getState().getY(), 1e-6);
|
||||
|
||||
@@ -1067,6 +1095,15 @@ TEST(Transform, FreeCameraOptionsSetOrientation) {
|
||||
EXPECT_DOUBLE_EQ(util::deg2rad(30.0), transform.getState().getPitch());
|
||||
EXPECT_NEAR(1.308930, transform.getState().getX(), 1e-6);
|
||||
EXPECT_NEAR(56.813889, transform.getState().getY(), 1e-6);
|
||||
|
||||
options.orientation = Quaternion::fromAxisAngle(vec3{{0.0, 0.0, 1.0}}, util::deg2rad(-33.0))
|
||||
.multiply(Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, util::deg2rad(-22.0)))
|
||||
.multiply(Quaternion::fromAxisAngle(vec3{{0.0, 0.0, 1.0}}, util::deg2rad(11.0)))
|
||||
.m;
|
||||
transform.setFreeCameraOptions(options);
|
||||
EXPECT_DOUBLE_EQ(util::deg2rad(33.0), transform.getState().getBearing());
|
||||
EXPECT_DOUBLE_EQ(util::deg2rad(22.0), transform.getState().getPitch());
|
||||
EXPECT_FLOAT_EQ(util::deg2rad(11.0), transform.getState().getRoll());
|
||||
}
|
||||
|
||||
static std::tuple<vec3, vec3, vec3> rotatedFrame(const std::array<double, 4>& quaternion) {
|
||||
@@ -1075,21 +1112,6 @@ static std::tuple<vec3, vec3, vec3> rotatedFrame(const std::array<double, 4>& qu
|
||||
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(Transform, FreeCameraOptionsClampPitch) {
|
||||
Transform transform;
|
||||
transform.resize({100, 100});
|
||||
FreeCameraOptions options;
|
||||
vec3 right, up, forward;
|
||||
|
||||
options.orientation = Quaternion::fromAxisAngle(vec3{{1.0, 0.0, 0.0}}, util::deg2rad(-85.0)).m;
|
||||
transform.setFreeCameraOptions(options);
|
||||
EXPECT_DOUBLE_EQ(util::PITCH_MAX, transform.getState().getPitch());
|
||||
std::tie(right, up, forward) = rotatedFrame(transform.getFreeCameraOptions().orientation.value());
|
||||
EXPECT_THAT(right, Vec3NearEquals1E5(vec3{{1.0, 0.0, 0.0}}));
|
||||
EXPECT_THAT(up, Vec3NearEquals1E5(vec3{{0, -0.5, 0.866025}}));
|
||||
EXPECT_THAT(forward, Vec3NearEquals1E5(vec3{{0, -0.866025, -0.5}}));
|
||||
}
|
||||
|
||||
TEST(Transform, FreeCameraOptionsClampToBounds) {
|
||||
Transform transform;
|
||||
transform.resize({100, 100});
|
||||
@@ -1130,29 +1152,6 @@ TEST(Transform, FreeCameraOptionsInvalidState) {
|
||||
EXPECT_THAT(options.position.value(), Vec3NearEquals1E5(vec3{{0.0, 0.0, 0.0}}));
|
||||
}
|
||||
|
||||
TEST(Transform, FreeCameraOptionsOrientationRoll) {
|
||||
Transform transform;
|
||||
FreeCameraOptions options;
|
||||
transform.resize({100, 100});
|
||||
|
||||
const auto orientationWithoutRoll = Quaternion::fromEulerAngles(-pi / 4, 0.0, 0.0);
|
||||
const auto orientationWithRoll = orientationWithoutRoll.multiply(Quaternion::fromEulerAngles(0.0, 0.0, pi / 4));
|
||||
|
||||
options.orientation = orientationWithRoll.m;
|
||||
transform.setFreeCameraOptions(options);
|
||||
options = transform.getFreeCameraOptions();
|
||||
|
||||
EXPECT_NEAR(options.orientation.value()[0], orientationWithoutRoll.x, 1e-9);
|
||||
EXPECT_NEAR(options.orientation.value()[1], orientationWithoutRoll.y, 1e-9);
|
||||
EXPECT_NEAR(options.orientation.value()[2], orientationWithoutRoll.z, 1e-9);
|
||||
EXPECT_NEAR(options.orientation.value()[3], orientationWithoutRoll.w, 1e-9);
|
||||
|
||||
EXPECT_NEAR(util::deg2rad(45.0), transform.getState().getPitch(), 1e-9);
|
||||
EXPECT_NEAR(0.0, transform.getState().getBearing(), 1e-9);
|
||||
EXPECT_NEAR(0.0, transform.getState().getX(), 1e-9);
|
||||
EXPECT_NEAR(150.0, transform.getState().getY(), 1e-9);
|
||||
}
|
||||
|
||||
TEST(Transform, FreeCameraOptionsStateSynchronization) {
|
||||
Transform transform;
|
||||
transform.resize({100, 100});
|
||||
@@ -1172,20 +1171,49 @@ TEST(Transform, FreeCameraOptionsStateSynchronization) {
|
||||
transform.jumpTo(CameraOptions().withPitch(20.0).withBearing(77.0).withCenter(LatLng{-20.0, 20.0}));
|
||||
EXPECT_THAT(transform.getFreeCameraOptions().position.value(),
|
||||
Vec3NearEquals1E5(vec3{{0.457922, 0.57926, 0.275301}}));
|
||||
|
||||
// Invalid pitch
|
||||
transform.jumpTo(CameraOptions().withPitch(-10.0).withBearing(0.0));
|
||||
std::tie(right, up, forward) = rotatedFrame(transform.getFreeCameraOptions().orientation.value());
|
||||
EXPECT_THAT(transform.getFreeCameraOptions().position.value(),
|
||||
Vec3NearEquals1E5(vec3{{0.555556, 0.556719, 0.292969}}));
|
||||
EXPECT_THAT(right, Vec3NearEquals1E5(vec3{{1.0, 0.0, 0.0}}));
|
||||
EXPECT_THAT(up, Vec3NearEquals1E5(vec3{{0.0, -1.0, 0.0}}));
|
||||
EXPECT_THAT(forward, Vec3NearEquals1E5(vec3{{0.0, 0.0, -1.0}}));
|
||||
|
||||
transform.jumpTo(CameraOptions().withPitch(85.0).withBearing(0.0).withCenter(LatLng{-80.0, 0.0}));
|
||||
std::tie(right, up, forward) = rotatedFrame(transform.getFreeCameraOptions().orientation.value());
|
||||
EXPECT_THAT(transform.getFreeCameraOptions().position.value(), Vec3NearEquals1E5(vec3{{0.5, 1.14146, 0.146484}}));
|
||||
EXPECT_THAT(right, Vec3NearEquals1E5(vec3{{1.0, 0.0, 0.0}}));
|
||||
EXPECT_THAT(up, Vec3NearEquals1E5(vec3{{0, -0.5, 0.866025}}));
|
||||
EXPECT_THAT(forward, Vec3NearEquals1E5(vec3{{0, -0.866025, -0.5}}));
|
||||
}
|
||||
|
||||
TEST(Camera, SetOrientation) {
|
||||
util::Camera camera;
|
||||
const double bearing = 0.22;
|
||||
const double pitch = 0.34;
|
||||
const double roll = 0.0;
|
||||
double bearing_ = 0;
|
||||
double pitch_ = 0;
|
||||
double roll_ = 0;
|
||||
camera.setOrientation(roll, pitch, bearing);
|
||||
camera.getOrientation(roll_, pitch_, bearing_);
|
||||
EXPECT_NEAR(bearing, bearing_, 1.0e-9);
|
||||
EXPECT_NEAR(pitch, pitch_, 1.0e-9);
|
||||
EXPECT_NEAR(roll, roll_, 1.0e-9);
|
||||
}
|
||||
|
||||
TEST(Camera, SetOrientationWithRoll) {
|
||||
util::Camera camera;
|
||||
const double bearing = 0.22;
|
||||
const double pitch = 0.34;
|
||||
const double roll = 0.45;
|
||||
double bearing_ = 0;
|
||||
double pitch_ = 0;
|
||||
double roll_ = 0;
|
||||
camera.setOrientation(roll, pitch, bearing);
|
||||
camera.getOrientation(roll_, pitch_, bearing_);
|
||||
EXPECT_NEAR(bearing, bearing_, 1.0e-9);
|
||||
EXPECT_NEAR(pitch, pitch_, 1.0e-9);
|
||||
EXPECT_NEAR(roll, roll_, 1.0e-9);
|
||||
}
|
||||
|
||||
TEST(Camera, SetOrientationWithRollNoPitch) {
|
||||
util::Camera camera;
|
||||
const double bearing = 0.22;
|
||||
const double pitch = 0.0;
|
||||
const double roll = 0.45;
|
||||
double bearing_ = 0;
|
||||
double pitch_ = 0;
|
||||
double roll_ = 0;
|
||||
camera.setOrientation(roll, pitch, bearing);
|
||||
camera.getOrientation(roll_, pitch_, bearing_);
|
||||
EXPECT_NEAR(bearing - roll, bearing_, 1.0e-9);
|
||||
EXPECT_NEAR(pitch, pitch_, 1.0e-9);
|
||||
EXPECT_NEAR(0.0, roll_, 1.0e-9);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ TEST(Style, Properties) {
|
||||
ASSERT_EQ("", style.getName());
|
||||
ASSERT_EQ((LatLng{20, 10}), *style.getDefaultCamera().center);
|
||||
|
||||
style.loadJSON(R"STYLE({"centerAltitude": 999})STYLE");
|
||||
ASSERT_EQ("", style.getName());
|
||||
ASSERT_EQ(999, *style.getDefaultCamera().centerAltitude);
|
||||
|
||||
style.loadJSON(R"STYLE({"bearing": 24})STYLE");
|
||||
ASSERT_EQ("", style.getName());
|
||||
ASSERT_EQ(LatLng{}, *style.getDefaultCamera().center);
|
||||
@@ -47,6 +51,10 @@ TEST(Style, Properties) {
|
||||
ASSERT_EQ("", style.getName());
|
||||
ASSERT_EQ(60, *style.getDefaultCamera().pitch);
|
||||
|
||||
style.loadJSON(R"STYLE({"roll": 99})STYLE");
|
||||
ASSERT_EQ("", style.getName());
|
||||
ASSERT_EQ(99, *style.getDefaultCamera().roll);
|
||||
|
||||
style.loadJSON(R"STYLE({})STYLE");
|
||||
ASSERT_EQ(Milliseconds(300), *style.getTransitionOptions().duration);
|
||||
ASSERT_EQ(std::optional<Duration>{}, style.getTransitionOptions().delay);
|
||||
|
||||
@@ -180,8 +180,8 @@ TEST(FreeCameraOptions, LookAtPoint) {
|
||||
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(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
|
||||
@@ -226,18 +226,18 @@ TEST(FreeCameraOptions, LookAtPointInvalidInput) {
|
||||
ASSERT_FALSE(options.orientation);
|
||||
}
|
||||
|
||||
TEST(FreeCameraOptions, SetPitchBearing) {
|
||||
TEST(FreeCameraOptions, SetRollPitchBearing) {
|
||||
FreeCameraOptions options;
|
||||
vec3 right, up, forward;
|
||||
|
||||
options.setPitchBearing(0.0, 0.0);
|
||||
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.setPitchBearing(0.0, 180.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}}));
|
||||
@@ -247,14 +247,14 @@ TEST(FreeCameraOptions, SetPitchBearing) {
|
||||
const double cos60 = std::cos(util::deg2rad(60.0));
|
||||
const double sin60 = std::sin(util::deg2rad(60.0));
|
||||
|
||||
options.setPitchBearing(60.0, 0.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.setPitchBearing(60.0, -450);
|
||||
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}}));
|
||||
|
||||
@@ -78,9 +78,8 @@ TEST(TileCover, PitchOverAllowedByContentInsets) {
|
||||
// Top padding of 376 leads to capped pitch. See Transform::getMaxPitchForEdgeInsets.
|
||||
EXPECT_LE(transform.getPitch() + 0.001, util::deg2rad(60));
|
||||
|
||||
EXPECT_EQ(
|
||||
(std::vector<OverscaledTileID>{{3, 4, 3}, {3, 3, 3}, {3, 4, 4}, {3, 3, 4}, {3, 4, 2}, {3, 5, 3}, {3, 5, 2}}),
|
||||
util::tileCover({transform.getState()}, 3));
|
||||
EXPECT_EQ((std::vector<OverscaledTileID>{{3, 4, 3}, {3, 3, 3}, {3, 4, 4}, {3, 3, 4}}),
|
||||
util::tileCover({transform.getState()}, 3));
|
||||
}
|
||||
|
||||
TEST(TileCover, PitchWithLargerResultSet) {
|
||||
@@ -102,17 +101,17 @@ TEST(TileCover, PitchWithLargerResultSet) {
|
||||
EXPECT_EQ((std::vector<OverscaledTileID>{{5, 15, 16},
|
||||
{5, 15, 17},
|
||||
{5, 14, 16},
|
||||
{5, 14, 17},
|
||||
{5, 16, 16},
|
||||
{5, 16, 17},
|
||||
{5, 15, 15},
|
||||
{5, 14, 17},
|
||||
{5, 16, 17},
|
||||
{5, 14, 15},
|
||||
{5, 15, 18},
|
||||
{5, 14, 18},
|
||||
{5, 16, 15},
|
||||
{5, 15, 18},
|
||||
{5, 13, 16},
|
||||
{5, 13, 17},
|
||||
{5, 14, 18},
|
||||
{5, 16, 18},
|
||||
{5, 13, 17},
|
||||
{5, 13, 18},
|
||||
{5, 15, 19}}),
|
||||
(std::vector<OverscaledTileID>{cover.begin(), cover.begin() + 16}));
|
||||
|
||||