#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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}; float puckScale = 0; float puckHatScale = 0; float puckShadowScale = 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& 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(M_PI_2 - std::atan2(-norm.y, norm.x), 0, M_PI * 2.0) * util::RAD2DEG; } Point 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 void upload(const std::array& data) { bind(); MBGL_CHECK_ERROR(glBufferData(GL_ARRAY_BUFFER, N * sizeof(T), data.data(), GL_STATIC_DRAW)); size = static_cast(N * sizeof(T)); elements = N; } template void upload(const std::vector& 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* img) { imageDirty = true; image = (img) ? &(img->get()->image) : nullptr; width = height = 0; pixelRatio = 1.0f; if (image) { sharedImage = *img; // keep reference until uploaded width = image->size.width; height = image->size.height; pixelRatio = img->get()->pixelRatio; } else { sharedImage = nullopt; } } void upload() { if (!imageDirty) return; imageDirty = false; initialize(); MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texId)); if (!sharedImage || !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> sharedImage; bool imageDirty = false; size_t width = 0; size_t height = 0; float pixelRatio = 1.0f; }; RenderLocationIndicatorImpl(std::string sourceLayer) : ruler(0, mapbox::cheap_ruler::CheapRuler::Meters), feature(std::make_shared()), featureEnvelope(std::make_shared>()) { feature->sourceLayer = std::move(sourceLayer); } static bool hasAnisotropicFiltering() { const auto* extensions = reinterpret_cast(glGetString(GL_EXTENSIONS)); GLenum error = glGetError(); if (error != GL_NO_ERROR) { // glGetString(GL_EXTENSIONS) is deprecated in OpenGL Desktop 3.0+. But OpenGL 3.0+ // has anisotropic filtering. return true; } else { if (strstr(extensions, "GL_EXT_texture_filter_anisotropic") != nullptr) return true; } return false; } void initialize() { // Check if anisotropic filtering is available if (initialized) return; initialized = true; if (hasAnisotropicFiltering()) 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; for (const auto& t : textures) t.second->release(); 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.puckScale != oldParams.puckScale || params.puckHatScale != oldParams.puckHatScale || params.puckShadowScale != oldParams.puckShadowScale) bearingChanged = true; // changes puck geometry but not necessarily the location if (params.errorRadiusMeters != oldParams.errorRadiusMeters) radiusChanged = true; bearingChanged |= setTextureFromImageID(params.puckImagePath, texPuck, params); bearingChanged |= setTextureFromImageID(params.puckShadowImagePath, texShadow, params); bearingChanged |= setTextureFromImageID(params.puckHatImagePath, texPuckHat, params); projectionCircle = params.projectionMatrix; const Point positionMercator = project(params.puckPosition, *params.state); 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; dirtyFeature = true; } void updateFeature() { if (!dirtyFeature) return; dirtyFeature = false; featureEnvelope->clear(); if (!texPuck || !texPuck->isValid()) return; feature->geometry = mapbox::geometry::point{oldParams.puckPosition.latitude(), oldParams.puckPosition.longitude()}; mapbox::geometry::linear_ring border; for (const auto& v : puckGeometry) { vec4 p{{v.x, v.y, 0, 1}}; matrix::transformMat4(p, p, translation); border.push_back(Point{int64_t(p[0]), int64_t(p[1])}); } border.push_back(border.front()); featureEnvelope->push_back(border); } 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 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 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 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 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 posMerc = project(pos, s); Point posLeftPxMerc = project(posLeftPx, s); return vec2(posMerc - posLeftPxMerc).length(); } static vec2 verticalDirectionMercator(const LatLng& pos, const TransformState& s) { ScreenCoordinate posScreen = latLngToScreenCoordinate(pos, s); Point posMerc = project(pos, s); return verticalDirectionMercator(posScreen, posMerc, s); } static vec2 verticalDirectionMercator(const ScreenCoordinate& pos, Point posMerc, const TransformState& s) { ScreenCoordinate screenDy = pos; screenDy.y -= 1; LatLng posDy = screenCoordinateToLatLng(screenDy, s); Point posMercDy = project(posDy, s); return verticalDirectionMercator(posMerc, posMercDy); } static vec2 verticalDirectionMercator(const Point& posMerc, const Point& posMercDy) { Point verticalShiftMercator = posMercDy - posMerc; vec2 res(verticalShiftMercator); return res.normalized(); } static Point 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 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 posMerc = project(position, s); Point 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) { updatePuckPerspective(params); bearingChanged = false; } 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 verticalShiftAtCenter { float(std::sin(util::DEG2RAD * util::wrap(-t.getBearing() * // util::RAD2DEG, 0.0f, 360.0f) )), // -float(std::cos(util::DEG2RAD * util::wrap(-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 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 = ((texShadow) ? texShadow->width / texShadow->pixelRatio : 0.0) * params.puckShadowScale * M_SQRT2 * 0.5 * horizontalScaleFactor; // Technically it's not the radius, but the half diagonal of the quad. const double puckRadius = ((texPuck) ? texPuck->width / texPuck->pixelRatio : 0.0) * params.puckScale * M_SQRT2 * 0.5 * horizontalScaleFactor; const double hatRadius = ((texPuckHat) ? texPuckHat->width / texPuckHat->pixelRatio : 0.0) * params.puckHatScale * M_SQRT2 * 0.5 * horizontalScaleFactor; for (unsigned long i = 0; i < 4; ++i) { const auto b = util::wrap(params.puckBearing + bearings[i], 0.0f, 360.0f); const Point cornerDirection{float(std::sin(util::DEG2RAD * b)), -float(std::cos(util::DEG2RAD * b))}; Point shadowOffset = cornerDirection * shadowRadius; Point puckOffset = cornerDirection * puckRadius; Point 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))); } } 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, GLsizei(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, GLsizei(circle.size() - 1))); } MBGL_CHECK_ERROR(glDisableVertexAttribArray(simpleShader.a_pos)); circleBuffer.detach(); simpleShader.detach(); } void drawQuad(Buffer& buf, std::array& data, std::shared_ptr& 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); } bool setTextureFromImageID(const std::string& imagePath, std::shared_ptr& texture, const mbgl::LocationIndicatorRenderParameters& params) { bool updated = false; if (textures.find(imagePath) == textures.end()) { std::shared_ptr tx = std::make_shared(); if (!imagePath.empty() && params.imageManager) { tx->assign(params.imageManager->getSharedImage(imagePath)); updated = true; } else { tx->assign(nullptr); } textures[imagePath] = tx; texture = tx; } else { const Immutable* sharedImage = params.imageManager->getSharedImage(imagePath); const mbgl::PremultipliedImage* img = (sharedImage) ? &sharedImage->get()->image : nullptr; std::shared_ptr& tex = textures.at(imagePath); if (tex->image != img) { // image for the ID might have changed. tex->assign(sharedImage); updated = true; } texture = tex; } return updated; } std::map> 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 texShadow; std::shared_ptr texPuck; std::shared_ptr texPuckHat; std::array circle; // 72 points + position std::array shadowGeometry; std::array puckGeometry; std::array hatGeometry; std::array texCoords; mbgl::mat4 translation; mbgl::mat4 projectionCircle; mbgl::mat4 projectionPuck; bool positionChanged = false; bool radiusChanged = false; bool bearingChanged = false; mbgl::LocationIndicatorRenderParameters oldParams; bool initialized = false; bool dirtyFeature = true; public: mbgl::LocationIndicatorRenderParameters parameters; std::shared_ptr feature; std::shared_ptr> featureEnvelope; static bool anisotropicFilteringAvailable; }; bool RenderLocationIndicatorImpl::anisotropicFilteringAvailable = false; using namespace style; namespace { inline const LocationIndicatorLayer::Impl& impl(const Immutable& impl) { assert(impl->getTypeInfo() == LocationIndicatorLayer::Impl::staticTypeInfo()); return static_cast(*impl); } } // namespace RenderLocationIndicatorLayer::RenderLocationIndicatorLayer(Immutable _impl) : RenderLayer(makeMutable(std::move(_impl))), renderImpl(std::make_unique(impl(baseImpl).id)), unevaluated(impl(baseImpl).paint.untransitioned()) { assert(gfx::BackendScope::exists()); } RenderLocationIndicatorLayer::~RenderLocationIndicatorLayer() { 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( staticImmutableCast(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(); renderImpl->parameters.errorRadiusBorderColor = evaluated.get(); renderImpl->parameters.errorRadiusMeters = evaluated.get(); renderImpl->parameters.puckScale = evaluated.get(); renderImpl->parameters.puckHatScale = evaluated.get(); renderImpl->parameters.puckShadowScale = evaluated.get(); renderImpl->parameters.puckBearing = evaluated.get().getAngle(); renderImpl->parameters.puckLayersDisplacement = evaluated.get(); renderImpl->parameters.perspectiveCompensation = evaluated.get(); const std::array pos = evaluated.get(); renderImpl->parameters.puckPosition = LatLng{pos[0], pos[1]}; // layout if (!layout.get().isUndefined()) renderImpl->parameters.puckImagePath = layout.get().asConstant().id(); if (!layout.get().isUndefined()) renderImpl->parameters.puckShadowImagePath = layout.get().asConstant().id(); if (!layout.get().isUndefined()) renderImpl->parameters.puckHatImagePath = layout.get().asConstant().id(); 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(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().bind(); glContext.setDirtyState(); } void RenderLocationIndicatorLayer::populateDynamicRenderFeatureIndex(DynamicFeatureIndex& index) const { renderImpl->updateFeature(); if (!renderImpl->featureEnvelope->empty()) index.insert(renderImpl->feature, renderImpl->featureEnvelope); } } // namespace mbgl