summaryrefslogtreecommitdiff
path: root/src/mbgl/renderer/layers/render_location_indicator_layer.cpp
diff options
context:
space:
mode:
authorGali Nelle <galinelle.mapbox@gmail.com>2020-03-25 17:35:44 +0200
committergalinelle <paolo.angelelli@mapbox.com>2020-04-08 14:00:26 +0300
commit9a55c282fecfdd76b1acdf64cef0ce2ed99472ef (patch)
treed2c412ef6ac4782d28cea66b761deeb5fff10cc3 /src/mbgl/renderer/layers/render_location_indicator_layer.cpp
parent7f53cec17b047a1804952a8da543dc10321e1dae (diff)
downloadqtlocation-mapboxgl-9a55c282fecfdd76b1acdf64cef0ce2ed99472ef.tar.gz
Add LocationIndicatorLayer
New key is "G" in mbgl-glfw, cycling between no puck, centered in the viewport and positioned in Tokyo.
Diffstat (limited to 'src/mbgl/renderer/layers/render_location_indicator_layer.cpp')
-rw-r--r--src/mbgl/renderer/layers/render_location_indicator_layer.cpp786
1 files changed, 786 insertions, 0 deletions
diff --git a/src/mbgl/renderer/layers/render_location_indicator_layer.cpp b/src/mbgl/renderer/layers/render_location_indicator_layer.cpp
new file mode 100644
index 0000000000..20c3bb8cfe
--- /dev/null
+++ b/src/mbgl/renderer/layers/render_location_indicator_layer.cpp
@@ -0,0 +1,786 @@
+#include <array>
+#include <mapbox/cheap_ruler.hpp>
+#include <mbgl/gfx/backend_scope.hpp>
+#include <mbgl/gfx/renderer_backend.hpp>
+#include <mbgl/gl/context.hpp>
+#include <mbgl/gl/renderable_resource.hpp>
+#include <mbgl/map/transform_state.hpp>
+#include <mbgl/platform/gl_functions.hpp>
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/renderer/layers/render_location_indicator_layer.hpp>
+#include <mbgl/renderer/paint_parameters.hpp>
+#include <mbgl/style/layers/location_indicator_layer.hpp>
+#include <mbgl/style/layers/location_indicator_layer_impl.hpp>
+#include <mbgl/style/layers/location_indicator_layer_properties.hpp>
+#include <mbgl/util/mat4.hpp>
+
+#include <mapbox/eternal.hpp>
+#include <mbgl/gl/context.hpp>
+#include <mbgl/gl/defines.hpp>
+#include <mbgl/gl/texture.hpp>
+#include <mbgl/gl/texture_resource.hpp>
+#include <mbgl/gl/types.hpp>
+#include <mbgl/platform/gl_functions.hpp>
+#include <mbgl/renderer/image_manager.hpp>
+
+using namespace mbgl::platform;
+namespace mbgl {
+
+struct LocationIndicatorRenderParameters {
+ LocationIndicatorRenderParameters() = default;
+ explicit LocationIndicatorRenderParameters(const TransformParameters& tp) : state(&tp.state) {}
+ LocationIndicatorRenderParameters(const LocationIndicatorRenderParameters& o) = default;
+ LocationIndicatorRenderParameters& operator=(const LocationIndicatorRenderParameters& o) = default;
+
+ double width = 0.0;
+ double height = 0.0;
+ double latitude = 0.0;
+ double longitude = 0.0;
+ double zoom = 0.0;
+ double bearing = 0.0;
+ double pitch = 0.0;
+ std::array<double, 16> projectionMatrix;
+ const TransformState* state = nullptr;
+ ImageManager* imageManager = nullptr;
+ // some testing defaults, for before it gets updated via props
+ double puckBearing = 0.0;
+ LatLng puckPosition = {0, 0};
+ double errorRadiusMeters;
+ mbgl::Color errorRadiusColor{0, 0, 0, 0};
+ mbgl::Color errorRadiusBorderColor{0, 0, 0, 0};
+ int puckSizePx = 0;
+ int puckHatSizePx = 0;
+ int puckShadowSizePx = 0;
+ float puckLayersDisplacement = 0;
+ float perspectiveCompensation = 0;
+ std::string puckImagePath;
+ std::string puckShadowImagePath;
+ std::string puckHatImagePath;
+};
+
+class RenderLocationIndicatorImpl {
+protected:
+ struct vec2 {
+ GLfloat x = 0.0f;
+ GLfloat y = 0.0f;
+
+ vec2(GLfloat x_, GLfloat y_) : x(x_), y(y_) {}
+ vec2() = default;
+ explicit vec2(const Point<double>& p) : x(p.x), y(p.y) {}
+ vec2(const vec2& o) = default;
+ vec2(vec2&& o) = default;
+ vec2& operator=(vec2&& o) = default;
+ vec2& operator=(const vec2& o) = default;
+ float length() const { return std::sqrt(x * x + y * y); }
+ vec2 normalized() const {
+ const float size = length();
+ return {x / size, y / size};
+ }
+ void normalize() { *this = normalized(); }
+ vec2 mirrored(const vec2& mirror) const {
+ float k = dot(mirror) / mirror.length();
+ return 2.0 * k * mirror - (*this);
+ }
+ float dot(const vec2& v2) const { return x * v2.x + y * v2.y; }
+ vec2 rotated(float degrees) const {
+ const float cs = std::cos(degrees * util::DEG2RAD);
+ const float sn = std::sin(degrees * util::DEG2RAD);
+ return vec2{x * cs + y * sn, x * sn + y * cs}.normalized();
+ }
+ float bearing() const {
+ const vec2 norm = normalized();
+
+ // From theta to bearing
+ return util::wrap<float>(M_PI_2 - std::atan2(-norm.y, norm.x), 0, M_PI * 2.0) * util::RAD2DEG;
+ }
+ Point<double> toPoint() const { return {x, y}; }
+
+ friend vec2 operator-(const vec2& v) { return {-v.x, -v.y}; }
+ friend vec2 operator*(double a, const vec2& v) { return {GLfloat(v.x * a), GLfloat(v.y * a)}; }
+ friend vec2 operator+(const vec2& v1, const vec2& v2) { return {v1.x + v2.x, v1.y + v2.y}; }
+ friend vec2 operator-(const vec2& v1, const vec2& v2) { return {v1.x - v2.x, v1.y - v2.y}; }
+ };
+
+ struct Shader {
+ virtual ~Shader() { release(); }
+ void release() {
+ if (!program) return;
+ MBGL_CHECK_ERROR(glDetachShader(program, vertexShader));
+ MBGL_CHECK_ERROR(glDetachShader(program, fragmentShader));
+ MBGL_CHECK_ERROR(glDeleteShader(vertexShader));
+ MBGL_CHECK_ERROR(glDeleteShader(fragmentShader));
+ MBGL_CHECK_ERROR(glDeleteProgram(program));
+ program = vertexShader = fragmentShader = 0;
+ }
+ void initialize(const GLchar* const& vsSource, const GLchar* const& fsSource) {
+ if (program) return;
+ program = MBGL_CHECK_ERROR(glCreateProgram());
+ vertexShader = MBGL_CHECK_ERROR(glCreateShader(GL_VERTEX_SHADER));
+ fragmentShader = MBGL_CHECK_ERROR(glCreateShader(GL_FRAGMENT_SHADER));
+ MBGL_CHECK_ERROR(glShaderSource(vertexShader, 1, &vsSource, nullptr));
+ MBGL_CHECK_ERROR(glCompileShader(vertexShader));
+ MBGL_CHECK_ERROR(glAttachShader(program, vertexShader));
+ MBGL_CHECK_ERROR(glShaderSource(fragmentShader, 1, &fsSource, nullptr));
+ MBGL_CHECK_ERROR(glCompileShader(fragmentShader));
+ MBGL_CHECK_ERROR(glAttachShader(program, fragmentShader));
+ MBGL_CHECK_ERROR(glLinkProgram(program));
+ pullLocations();
+ }
+ virtual void bind() { MBGL_CHECK_ERROR(glUseProgram(program)); }
+ void detach() { MBGL_CHECK_ERROR(glUseProgram(0)); }
+ virtual void pullLocations(){};
+
+ GLuint program = 0;
+ GLuint vertexShader = 0;
+ GLuint fragmentShader = 0;
+ };
+
+ struct SimpleShader : public Shader {
+ // Note that custom layers need to draw geometry with a z value of 1 to take advantage of
+ // depth-based fragment culling.
+ const GLchar* vertexShaderSource = R"MBGL_SHADER(
+#ifdef GL_ES
+precision highp float;
+#endif
+
+attribute vec2 a_pos;
+uniform mat4 u_matrix;
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+}
+)MBGL_SHADER";
+
+ const GLchar* fragmentShaderSource = R"MBGL_SHADER(
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform vec4 u_color;
+void main() {
+ gl_FragColor = u_color;
+}
+)MBGL_SHADER";
+
+ void initialize() { Shader::initialize(SimpleShader::vertexShaderSource, SimpleShader::fragmentShaderSource); }
+
+ void pullLocations() override {
+ a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos"));
+ u_color = MBGL_CHECK_ERROR(glGetUniformLocation(program, "u_color"));
+ u_matrix = MBGL_CHECK_ERROR(glGetUniformLocation(program, "u_matrix"));
+ }
+ void bind() override {
+ SimpleShader::initialize();
+ Shader::bind();
+ }
+
+ GLuint a_pos = 0;
+ GLuint u_color = 0;
+ GLuint u_matrix = 0;
+ };
+
+ struct TexturedShader : public Shader {
+ const GLchar* vertexShaderSource = R"MBGL_SHADER(
+#ifdef GL_ES
+precision highp float;
+#endif
+
+attribute vec2 a_pos;
+attribute vec2 a_texCoord;
+uniform mat4 u_matrix;
+varying vec2 v_texCoord;
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+ v_texCoord = a_texCoord;
+}
+)MBGL_SHADER";
+
+ const GLchar* fragmentShaderSource = R"MBGL_SHADER(
+#ifdef GL_ES
+precision mediump float;
+#endif
+
+uniform sampler2D u_image;
+varying vec2 v_texCoord;
+void main() {
+ vec4 color = texture2D(u_image, v_texCoord);
+ gl_FragColor = color;
+}
+)MBGL_SHADER";
+
+ void initialize() { Shader::initialize(vertexShaderSource, fragmentShaderSource); }
+
+ void pullLocations() override {
+ a_pos = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_pos"));
+ a_texCoord = MBGL_CHECK_ERROR(glGetAttribLocation(program, "a_texCoord"));
+ u_image = MBGL_CHECK_ERROR(glGetUniformLocation(program, "u_image"));
+ u_matrix = MBGL_CHECK_ERROR(glGetUniformLocation(program, "u_matrix"));
+ }
+ void bind() override {
+ TexturedShader::initialize();
+ Shader::bind();
+ }
+
+ GLuint a_pos = 0;
+ GLuint a_texCoord = 0;
+ GLuint u_image = 0;
+ GLuint u_matrix = 0;
+ };
+
+ struct Buffer {
+ virtual ~Buffer() { release(); }
+ void release() {
+ if (!bufferId) return;
+ MBGL_CHECK_ERROR(glDeleteBuffers(1, &bufferId));
+ bufferId = 0;
+ }
+ void initialize() {
+ if (!bufferId) MBGL_CHECK_ERROR(glGenBuffers(1, &bufferId));
+ }
+ void bind(const GLenum target = GL_ARRAY_BUFFER) {
+ initialize();
+ MBGL_CHECK_ERROR(glBindBuffer(target, bufferId));
+ }
+ void detach(const GLenum target = GL_ARRAY_BUFFER) { MBGL_CHECK_ERROR(glBindBuffer(target, 0)); }
+ template <typename T, std::size_t N>
+ void upload(const std::array<T, N>& data) {
+ bind();
+ MBGL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW));
+ size = static_cast<unsigned int>(N * sizeof(T));
+ elements = N;
+ }
+ template <typename T>
+ void upload(const std::vector<T>& data) {
+ bind();
+ MBGL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, data.size() * sizeof(T), data.data(), GL_STATIC_DRAW));
+ size = data.size() * sizeof(T);
+ elements = data.size();
+ }
+
+ GLuint bufferId = 0;
+ unsigned int size = 0;
+ unsigned int elements = 0;
+ };
+
+public:
+ struct Texture {
+ ~Texture() { release(); }
+ void release() {
+ MBGL_CHECK_ERROR(glDeleteTextures(1, &texId));
+ texId = 0;
+ image = nullptr;
+ }
+ /*
+ Assign can be called any time. Conversely, upload must be called with a bound gl context.
+ */
+ void assign(const Immutable<style::Image::Impl>* img) {
+ if ((img && &img->get()->image == image) || (!img && !image)) return;
+ imageDirty = true;
+ image = (img) ? &img->get()->image : nullptr;
+ if (img)
+ sharedImage = *img; // keep reference until uploaded
+ else
+ sharedImage = nullopt;
+ }
+
+ void upload() {
+ if (!imageDirty) return;
+ imageDirty = false;
+ initialize();
+
+ MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texId));
+ if (!image || !image->valid()) {
+ MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
+ } else {
+ MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D,
+ 0,
+ GL_RGBA,
+ image->size.width,
+ image->size.height,
+ 0,
+ GL_RGBA,
+ GL_UNSIGNED_BYTE,
+ image->data.get()));
+ MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
+ MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
+ MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
+ MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
+ MBGL_CHECK_ERROR(glGenerateMipmap(GL_TEXTURE_2D));
+ if (RenderLocationIndicatorImpl::anisotropicFilteringAvailable)
+ MBGL_CHECK_ERROR(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16));
+ }
+ detach();
+ sharedImage = nullopt;
+ }
+ void initialize() {
+ if (texId != 0) return;
+ MBGL_CHECK_ERROR(glGenTextures(1, &texId));
+ }
+ void bind(int textureUnit = -1) {
+ initialize();
+ if (!image && !imageDirty) return;
+
+ upload();
+ if (textureUnit >= 0) MBGL_CHECK_ERROR(glActiveTexture(GL_TEXTURE0 + textureUnit));
+ MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texId));
+ }
+ void detach() { MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, 0)); }
+ bool isValid() { return imageDirty || image; }
+ GLuint texId = 0;
+ const mbgl::PremultipliedImage* image = nullptr;
+ optional<Immutable<style::Image::Impl>> sharedImage;
+ bool imageDirty = false;
+ };
+
+ RenderLocationIndicatorImpl() : ruler(0, mapbox::cheap_ruler::CheapRuler::Meters) {}
+
+ static bool hasExtension(const std::string& ext) {
+ if (const auto* extensions = reinterpret_cast<const char*>(MBGL_CHECK_ERROR(glGetString(GL_EXTENSIONS)))) {
+ if (strstr(extensions, ext.c_str()) != nullptr) return true;
+ }
+ return false;
+ }
+ void initialize() {
+ // Check if anisotropic filtering is available
+ if (initialized) return;
+ initialized = true;
+ if (hasExtension("GL_EXT_texture_filter_anisotropic")) anisotropicFilteringAvailable = true;
+ simpleShader.initialize();
+ texturedShader.initialize();
+ texCoords = {{{0.0f, 1.0f},
+ {0.0f, 0.0f},
+ {1.0f, 0.0f},
+ {1.0f, 1.0f}}}; // Quads will be drawn as triangle fans. so bl, tl, tr, br
+ texCoordsBuffer.upload(texCoords);
+ }
+
+ void render(const mbgl::LocationIndicatorRenderParameters& params) {
+ initialize();
+ drawRadius(params);
+ drawShadow();
+ drawPuck();
+ drawHat();
+ }
+
+ void release() {
+ if (!simpleShader.program) return;
+ textures.clear();
+ buffer.release();
+ circleBuffer.release();
+ puckBuffer.release();
+ hatBuffer.release();
+ texCoordsBuffer.release();
+ simpleShader.release();
+ texturedShader.release();
+ }
+
+ void updatePuckGeometry(const mbgl::LocationIndicatorRenderParameters& params) {
+ if (params.projectionMatrix != oldParams.projectionMatrix) positionChanged = true;
+ if (params.puckPosition != oldParams.puckPosition) {
+ positionChanged = true;
+ ruler = mapbox::cheap_ruler::CheapRuler(params.puckPosition.latitude(),
+ mapbox::cheap_ruler::CheapRuler::Meters);
+ } else if (params.puckBearing != oldParams.puckBearing ||
+ params.puckLayersDisplacement != oldParams.puckLayersDisplacement ||
+ params.perspectiveCompensation != oldParams.perspectiveCompensation ||
+ params.puckSizePx != oldParams.puckSizePx || params.puckHatSizePx != oldParams.puckHatSizePx ||
+ params.puckShadowSizePx != oldParams.puckShadowSizePx)
+ bearingChanged = true; // changes puck geometry but not necessarily the location
+ if (params.errorRadiusMeters != oldParams.errorRadiusMeters) radiusChanged = true;
+ if (params.puckImagePath != oldParams.puckImagePath)
+ setTextureFromImageID(params.puckImagePath, texPuck, params);
+ if (params.puckShadowImagePath != oldParams.puckShadowImagePath)
+ setTextureFromImageID(params.puckShadowImagePath, texShadow, params);
+ if (params.puckHatImagePath != oldParams.puckHatImagePath)
+ setTextureFromImageID(params.puckHatImagePath, texPuckHat, params);
+
+ projectionCircle = params.projectionMatrix;
+ const Point<double> positionMercator = project(params.puckPosition, *params.state);
+ mat4 translation;
+ matrix::identity(translation);
+ matrix::translate(translation, translation, positionMercator.x, positionMercator.y, 0.0);
+ matrix::multiply(projectionCircle, projectionCircle, translation);
+
+ if (positionChanged) {
+ updateRadius(params);
+ updatePuck(params);
+ positionChanged = false;
+ } else {
+ if (radiusChanged) {
+ updateRadius(params);
+ }
+ if (bearingChanged) {
+ updatePuck(params);
+ }
+ }
+ oldParams = params;
+ }
+
+protected:
+ static ScreenCoordinate latLngToScreenCoordinate(const LatLng& p, const TransformState& s) {
+ LatLng unwrappedLatLng = p.wrapped();
+ unwrappedLatLng.unwrapForShortestPath(s.getLatLng(LatLng::Wrapped));
+ ScreenCoordinate point = s.latLngToScreenCoordinate(unwrappedLatLng);
+ point.y = s.getSize().height - point.y;
+ return point;
+ }
+
+ static Point<double> project(const LatLng& c, const TransformState& s) {
+ LatLng unwrappedLatLng = c.wrapped();
+ unwrappedLatLng.unwrapForShortestPath(s.getLatLng(LatLng::Wrapped));
+ return Projection::project(unwrappedLatLng, s.getScale());
+ }
+
+ static Point<double> unproject(const LatLng& c, const TransformState& s) {
+ LatLng unwrappedLatLng = c.wrapped();
+ unwrappedLatLng.unwrapForShortestPath(s.getLatLng(LatLng::Wrapped));
+ return Projection::project(unwrappedLatLng, s.getScale());
+ }
+
+ void updateRadius(const mbgl::LocationIndicatorRenderParameters& params) {
+ const TransformState& s = *params.state;
+ const unsigned long numVtxCircumference = circle.size() - 1;
+ const float bearingStep = 360.0f / float(numVtxCircumference - 1); // first and last points are the same
+ const mapbox::cheap_ruler::point centerPoint(params.puckPosition.longitude(), params.puckPosition.latitude());
+ Point<double> center = project(params.puckPosition, s);
+ circle[0] = {0, 0};
+
+ double mapBearing = util::wrap(util::RAD2DEG * params.bearing, 0.0, util::DEGREES_MAX);
+ for (unsigned long i = 1; i <= numVtxCircumference; ++i) {
+ const float bearing_ = float(i - 1) * bearingStep - mapBearing;
+ Point<double> poc = ruler.destination(centerPoint, params.errorRadiusMeters, bearing_);
+ circle[i] = vec2(project(LatLng(poc.y, poc.x), s) - center);
+ }
+ radiusChanged = false;
+ }
+
+ // Size in "map pixels" for a screen pixel
+ static float pixelSizeToWorldSizeH(const LatLng& pos, const TransformState& s) {
+ ScreenCoordinate posScreen = latLngToScreenCoordinate(pos, s);
+ ScreenCoordinate posScreenLeftPx = posScreen;
+ posScreenLeftPx.x -= 1;
+ LatLng posLeftPx = screenCoordinateToLatLng(posScreenLeftPx, s);
+ Point<double> posMerc = project(pos, s);
+ Point<double> posLeftPxMerc = project(posLeftPx, s);
+ return vec2(posMerc - posLeftPxMerc).length();
+ }
+
+ static vec2 verticalDirectionMercator(const LatLng& pos, const TransformState& s) {
+ ScreenCoordinate posScreen = latLngToScreenCoordinate(pos, s);
+ Point<double> posMerc = project(pos, s);
+ return verticalDirectionMercator(posScreen, posMerc, s);
+ }
+
+ static vec2 verticalDirectionMercator(const ScreenCoordinate& pos, Point<double> posMerc, const TransformState& s) {
+ ScreenCoordinate screenDy = pos;
+ screenDy.y -= 1;
+ LatLng posDy = screenCoordinateToLatLng(screenDy, s);
+ Point<double> posMercDy = project(posDy, s);
+ return verticalDirectionMercator(posMerc, posMercDy);
+ }
+
+ static vec2 verticalDirectionMercator(const Point<double>& posMerc, const Point<double>& posMercDy) {
+ Point<double> verticalShiftMercator = posMercDy - posMerc;
+ vec2 res(verticalShiftMercator);
+ return res.normalized();
+ }
+
+ static Point<double> hatShadowShiftVector(const LatLng& position,
+ const mbgl::LocationIndicatorRenderParameters& params) {
+ const TransformState& s = *params.state;
+ ScreenCoordinate posScreen = latLngToScreenCoordinate(position, s);
+ posScreen.y = params.height - 1; // moving it to bottom
+ Point<double> posMerc = project(screenCoordinateToLatLng(posScreen, s), s);
+ vec2 verticalShiftAtPos = verticalDirectionMercator(posScreen, posMerc, s);
+ return {verticalShiftAtPos.x, verticalShiftAtPos.y};
+ }
+
+ static vec2 directionAtPositionScreen(const LatLng& position, float bearing, const TransformState& s) {
+ const double scale = s.getScale();
+ const vec2 rot = vec2(0.0, -1.0).rotated(-bearing);
+ Point<double> posMerc = project(position, s);
+ Point<double> posMercDelta = posMerc + rot.toPoint();
+ ScreenCoordinate posScreen = latLngToScreenCoordinate(position, s);
+ ScreenCoordinate posScreenDelta = latLngToScreenCoordinate(Projection::unproject(posMercDelta, scale), s);
+ return vec2(posScreenDelta - posScreen).normalized();
+ }
+
+ void updatePuck(const mbgl::LocationIndicatorRenderParameters& params) { return updatePuckPerspective(params); }
+
+ void updatePuckPerspective(const mbgl::LocationIndicatorRenderParameters& params) {
+ const TransformState& s = *params.state;
+ projectionPuck = projectionCircle; // Duplicated as it might change, depending on what puck style is chosen.
+ const mapbox::cheap_ruler::point centerPoint(params.puckPosition.longitude(), params.puckPosition.latitude());
+ static constexpr float bearings[]{
+ 225.0f, 315.0f, 45.0f, 135.0f}; // Quads will be drawn as triangle fans. so bl, tl, tr, br
+#ifndef M_SQRT2
+ static constexpr const float M_SQRT2 = std::sqrt(2.0f);
+#endif
+ // The puck has to stay square at all zoom levels. CheapRuler::destination does not guarantee this at low zoom
+ // levels, so the extent has to be produced in mercator space
+ const double tilt = s.getPitch();
+
+ // Point<double> verticalShiftAtCenter { float(std::sin(util::DEG2RAD * util::wrap<float>(-t.getBearing() *
+ // util::RAD2DEG, 0.0f, 360.0f) )),
+ // -float(std::cos(util::DEG2RAD * util::wrap<float>(-t.getBearing() *
+ // util::RAD2DEG, 0.0f, 360.0f))) };
+ // would be correct only in the vertical center of the map. As soon as position goes away from that line,
+ // the shift direction is skewed by the perspective projection.
+ // So the way to have a shift aligned to the screen vertical axis is to find this direction in screen space, and
+ // convert it back to map space. This would yield an always straight up shift. However, going further (= the
+ // opposite direction of where the lines are converging in the projection) might produce an even more realistic
+ // effect. But in this case, it empirically seems that the largest shift that look acceptable is what is
+ // obtained at the bottom of the window, avoiding the wider converging lines that pass by the edge of the screen
+ // going toward the top.
+
+ Point<double> verticalShift = hatShadowShiftVector(params.puckPosition, params);
+ const float horizontalScaleFactor =
+ (1.0f - params.perspectiveCompensation) +
+ util::clamp(pixelSizeToWorldSizeH(params.puckPosition, s), 0.8f, 100.1f) *
+ params.perspectiveCompensation; // Compensation factor for the perspective deformation
+ // ^ clamping this to 0.8 to avoid growing the puck too much close to the camera.
+ const double shadowRadius =
+ params.puckShadowSizePx * M_SQRT2 * 0.5 *
+ horizontalScaleFactor; // Technically it's not the radius, but the half diagonal of the quad.
+ const double puckRadius = params.puckSizePx * M_SQRT2 * 0.5 * horizontalScaleFactor;
+ const double hatRadius = params.puckHatSizePx * M_SQRT2 * 0.5 * horizontalScaleFactor;
+
+ for (unsigned long i = 0; i < 4; ++i) {
+ const auto b = util::wrap<float>(params.puckBearing + bearings[i], 0.0f, 360.0f);
+
+ const Point<double> cornerDirection{float(std::sin(util::DEG2RAD * b)),
+ -float(std::cos(util::DEG2RAD * b))};
+
+ Point<double> shadowOffset = cornerDirection * shadowRadius;
+ Point<double> puckOffset = cornerDirection * puckRadius;
+ Point<double> hatOffset = cornerDirection * hatRadius;
+
+ shadowGeometry[i] =
+ vec2(shadowOffset + (verticalShift * (tilt * -params.puckLayersDisplacement * horizontalScaleFactor)));
+ puckGeometry[i] = vec2(puckOffset);
+ hatGeometry[i] =
+ vec2(hatOffset + (verticalShift * (tilt * params.puckLayersDisplacement * horizontalScaleFactor)));
+ }
+
+ bearingChanged = false;
+ }
+
+ void drawRadius(const mbgl::LocationIndicatorRenderParameters& params) {
+ if (!(params.errorRadiusMeters > 0.0) ||
+ (params.errorRadiusColor.a == 0.0 && params.errorRadiusBorderColor.a == 0.0))
+ return;
+
+ simpleShader.bind();
+ mbgl::gl::bindUniform(simpleShader.u_color, params.errorRadiusColor);
+ mbgl::gl::bindUniform(simpleShader.u_matrix, projectionCircle);
+
+ circleBuffer.upload(circle);
+ MBGL_CHECK_ERROR(glEnableVertexAttribArray(simpleShader.a_pos));
+ MBGL_CHECK_ERROR(glVertexAttribPointer(simpleShader.a_pos, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
+
+ MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_FAN, 0, circle.size()));
+ if (params.errorRadiusBorderColor.a > 0.0f) {
+ mbgl::gl::bindUniform(simpleShader.u_color, params.errorRadiusBorderColor);
+ MBGL_CHECK_ERROR(glLineWidth(1.0f));
+ MBGL_CHECK_ERROR(glDrawArrays(GL_LINE_STRIP, 1, circle.size() - 1));
+ }
+ MBGL_CHECK_ERROR(glDisableVertexAttribArray(simpleShader.a_pos));
+ circleBuffer.detach();
+ simpleShader.detach();
+ }
+
+ void drawQuad(Buffer& buf, std::array<vec2, 4>& data, std::shared_ptr<Texture>& texture) {
+ if (!texture || !texture->isValid()) return;
+ texturedShader.bind();
+ texture->bind(0);
+ glUniform1i(texturedShader.u_image, 0);
+ mbgl::gl::bindUniform(texturedShader.u_matrix, projectionPuck);
+
+ buf.bind();
+ buf.upload(data);
+ MBGL_CHECK_ERROR(glEnableVertexAttribArray(texturedShader.a_pos));
+ MBGL_CHECK_ERROR(glVertexAttribPointer(texturedShader.a_pos, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
+
+ texCoordsBuffer.bind();
+ MBGL_CHECK_ERROR(glEnableVertexAttribArray(texturedShader.a_texCoord));
+ MBGL_CHECK_ERROR(glVertexAttribPointer(texturedShader.a_texCoord, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
+
+ MBGL_CHECK_ERROR(glDrawArrays(GL_TRIANGLE_FAN, 0, 4));
+ texture->detach();
+ texCoordsBuffer.detach();
+ texturedShader.detach();
+ }
+
+ void drawShadow() { drawQuad(shadowBuffer, shadowGeometry, texShadow); }
+
+ void drawPuck() { drawQuad(puckBuffer, puckGeometry, texPuck); }
+
+ void drawHat() { drawQuad(hatBuffer, hatGeometry, texPuckHat); }
+
+ static LatLng screenCoordinateToLatLng(const ScreenCoordinate& p,
+ const TransformState& s,
+ LatLng::WrapMode wrapMode = LatLng::Wrapped) {
+ ScreenCoordinate flippedPoint = p;
+ flippedPoint.y = s.getSize().height - flippedPoint.y;
+ return s.screenCoordinateToLatLng(flippedPoint, wrapMode);
+ }
+
+ void setTextureFromImageID(const std::string& imagePath,
+ std::shared_ptr<Texture>& tex,
+ const mbgl::LocationIndicatorRenderParameters& params) {
+ if (textures.find(imagePath) == textures.end()) {
+ std::shared_ptr<Texture> tx = std::make_shared<Texture>();
+ if (!imagePath.empty() && params.imageManager)
+ tx->assign(params.imageManager->getSharedImage(imagePath));
+ else
+ tx->assign(nullptr);
+ textures[imagePath] = tx;
+ }
+ tex = textures.at(imagePath);
+ }
+
+ std::map<std::string, std::shared_ptr<Texture>> textures;
+ mapbox::cheap_ruler::CheapRuler ruler;
+ SimpleShader simpleShader;
+ TexturedShader texturedShader;
+ Buffer buffer;
+ Buffer circleBuffer;
+ Buffer shadowBuffer;
+ Buffer puckBuffer;
+ Buffer hatBuffer;
+ Buffer texCoordsBuffer;
+ std::shared_ptr<Texture> texShadow;
+ std::shared_ptr<Texture> texPuck;
+ std::shared_ptr<Texture> texPuckHat;
+
+ std::array<vec2, 73> circle; // 72 points + position
+ std::array<vec2, 4> shadowGeometry;
+ std::array<vec2, 4> puckGeometry;
+ std::array<vec2, 4> hatGeometry;
+ std::array<vec2, 4> texCoords;
+ mbgl::mat4 projectionCircle;
+ mbgl::mat4 projectionPuck;
+
+ bool positionChanged = false;
+ bool radiusChanged = false;
+ bool bearingChanged = false;
+ mbgl::LocationIndicatorRenderParameters oldParams;
+ bool initialized = false;
+
+public:
+ mbgl::LocationIndicatorRenderParameters parameters;
+ static bool anisotropicFilteringAvailable;
+};
+
+bool RenderLocationIndicatorImpl::anisotropicFilteringAvailable = false;
+
+using namespace style;
+namespace {
+
+inline const LocationIndicatorLayer::Impl& impl(const Immutable<style::Layer::Impl>& impl) {
+ assert(impl->getTypeInfo() == LocationIndicatorLayer::Impl::staticTypeInfo());
+ return static_cast<const LocationIndicatorLayer::Impl&>(*impl);
+}
+} // namespace
+
+RenderLocationIndicatorLayer::RenderLocationIndicatorLayer(Immutable<style::LocationIndicatorLayer::Impl> _impl)
+ : RenderLayer(makeMutable<LocationIndicatorLayerProperties>(std::move(_impl))),
+ renderImpl(new RenderLocationIndicatorImpl()),
+ unevaluated(impl(baseImpl).paint.untransitioned()) {
+ assert(gfx::BackendScope::exists());
+}
+
+RenderLocationIndicatorLayer::~RenderLocationIndicatorLayer() {
+ assert(gfx::BackendScope::exists());
+ if (!contextDestroyed) MBGL_CHECK_ERROR(renderImpl->release());
+}
+
+void RenderLocationIndicatorLayer::transition(const TransitionParameters& parameters) {
+ unevaluated = impl(baseImpl).paint.transitioned(parameters, std::move(unevaluated));
+}
+
+void RenderLocationIndicatorLayer::evaluate(const PropertyEvaluationParameters& parameters) {
+ passes = RenderPass::Translucent;
+ auto properties = makeMutable<LocationIndicatorLayerProperties>(
+ staticImmutableCast<LocationIndicatorLayer::Impl>(baseImpl), unevaluated.evaluate(parameters));
+ const auto& evaluated = properties->evaluated;
+ auto& layout = impl(baseImpl).layout;
+
+ properties->renderPasses = mbgl::underlying_type(passes);
+
+ // paint
+ renderImpl->parameters.errorRadiusColor = evaluated.get<style::AccuracyRadiusColor>();
+ renderImpl->parameters.errorRadiusBorderColor = evaluated.get<style::AccuracyRadiusBorderColor>();
+ renderImpl->parameters.errorRadiusMeters = evaluated.get<style::AccuracyRadius>();
+ renderImpl->parameters.puckSizePx = evaluated.get<style::BearingImageSize>();
+ renderImpl->parameters.puckHatSizePx = evaluated.get<style::TopImageSize>();
+ renderImpl->parameters.puckShadowSizePx = evaluated.get<style::ShadowImageSize>();
+
+ const std::array<double, 3> pos = evaluated.get<style::Location>();
+ renderImpl->parameters.puckPosition = LatLng{pos[0], pos[1]};
+
+ // layout
+ if (!layout.get<style::Bearing>().isUndefined())
+ renderImpl->parameters.puckBearing = layout.get<style::Bearing>().asConstant();
+ if (!layout.get<style::BearingImage>().isUndefined())
+ renderImpl->parameters.puckImagePath = layout.get<style::BearingImage>().asConstant().id();
+ if (!layout.get<style::ShadowImage>().isUndefined())
+ renderImpl->parameters.puckShadowImagePath = layout.get<style::ShadowImage>().asConstant().id();
+ if (!layout.get<style::TopImage>().isUndefined())
+ renderImpl->parameters.puckHatImagePath = layout.get<style::TopImage>().asConstant().id();
+ if (!layout.get<style::ImageTiltDisplacement>().isUndefined())
+ renderImpl->parameters.puckLayersDisplacement = layout.get<style::ImageTiltDisplacement>().asConstant();
+ if (!layout.get<style::PerspectiveCompensation>().isUndefined())
+ renderImpl->parameters.perspectiveCompensation = layout.get<style::PerspectiveCompensation>().asConstant();
+
+ evaluatedProperties = std::move(properties);
+}
+
+bool RenderLocationIndicatorLayer::hasTransition() const {
+ return unevaluated.hasTransition();
+}
+bool RenderLocationIndicatorLayer::hasCrossfade() const {
+ return false;
+}
+
+void RenderLocationIndicatorLayer::markContextDestroyed() {
+ contextDestroyed = true;
+}
+
+void RenderLocationIndicatorLayer::prepare(const LayerPrepareParameters& p) {
+ renderImpl->parameters.imageManager = &p.imageManager;
+ const TransformState& state = p.state;
+ renderImpl->parameters.state = &state;
+
+ renderImpl->parameters.width = state.getSize().width;
+ renderImpl->parameters.height = state.getSize().height;
+ renderImpl->parameters.latitude = state.getLatLng().latitude();
+ renderImpl->parameters.longitude = state.getLatLng().longitude();
+ renderImpl->parameters.zoom = state.getZoom();
+ renderImpl->parameters.bearing = -state.getBearing() * util::RAD2DEG;
+ renderImpl->parameters.pitch = state.getPitch();
+ mat4 projMatrix;
+ state.getProjMatrix(projMatrix);
+ renderImpl->parameters.projectionMatrix = projMatrix;
+
+ renderImpl->updatePuckGeometry(renderImpl->parameters);
+}
+
+void RenderLocationIndicatorLayer::render(PaintParameters& paintParameters) {
+ auto& glContext = static_cast<gl::Context&>(paintParameters.context);
+
+ // Reset GL state to a known state so the CustomLayer always has a clean slate.
+ glContext.bindVertexArray = 0;
+ glContext.setDepthMode(paintParameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly));
+ glContext.setStencilMode(gfx::StencilMode::disabled());
+ glContext.setColorMode(paintParameters.colorModeForRenderPass()); // this is gfx::ColorMode::alphaBlended()
+ glContext.setCullFaceMode(gfx::CullFaceMode::disabled());
+
+ MBGL_CHECK_ERROR(renderImpl->render(renderImpl->parameters));
+
+ // Reset the view back to our original one, just in case the CustomLayer changed
+ // the viewport or Framebuffer.
+ paintParameters.backend.getDefaultRenderable().getResource<gl::RenderableResource>().bind();
+ glContext.setDirtyState();
+}
+
+} // namespace mbgl