TileParseResult TileWorker::parsePendingLayers(const PlacementConfig config) {
    // Try parsing the remaining layers that we couldn't parse in the first step due to missing
    // dependencies.
    for (auto it = pending.begin(); it != pending.end();) {
        const SymbolLayer& symbolLayer = *it->first;
        SymbolBucket* symbolBucket = dynamic_cast<SymbolBucket*>(it->second.get());

        if (!symbolBucket->needsDependencies(glyphStore, spriteStore)) {
            symbolBucket->addFeatures(reinterpret_cast<uintptr_t>(this),
                                      *symbolLayer.impl->spriteAtlas,
                                      glyphAtlas,
                                      glyphStore);
            placementPending.emplace(symbolLayer.impl->bucketName(), std::move(it->second));
            pending.erase(it++);
            continue;
        }

        // Advance the iterator here; we're skipping this when erasing an element from this list.
        ++it;
    }

    return prepareResult(config);
}
void Painter::renderSymbol(SymbolBucket &bucket, std::shared_ptr<StyleLayer> layer_desc, const Tile::ID &id, const mat4 &matrix) {
    // Abort early.
    if (pass == RenderPass::Opaque) {
        return;
    }

    const SymbolProperties &properties = layer_desc->getProperties<SymbolProperties>();

    glDisable(GL_STENCIL_TEST);

    if (bucket.hasTextData()) {
        GlyphAtlas &glyphAtlas = *map.getGlyphAtlas();
        glyphAtlas.bind();

        renderSDF(bucket,
                  id,
                  matrix,
                  bucket.properties.text,
                  properties.text,
                  24.0f,
        {{ float(glyphAtlas.width) / 4, float(glyphAtlas.height) / 4 }},
        *sdfGlyphShader,
        &SymbolBucket::drawGlyphs);
    }

    if (bucket.hasIconData()) {
        bool sdf = bucket.sdfIcons;

        const float angleOffset =
            bucket.properties.icon.rotation_alignment == RotationAlignmentType::Map
            ? map.getState().getAngle()
            : 0;

        // If layerStyle.size > bucket.info.fontSize then labels may collide
        const float fontSize = properties.icon.size != 0 ? properties.icon.size : bucket.properties.icon.max_size;
        const float fontScale = fontSize / 1.0f;

        SpriteAtlas &spriteAtlas = *map.getSpriteAtlas();
        spriteAtlas.bind(map.getState().isChanging() || bucket.properties.placement == PlacementType::Line || angleOffset != 0 || fontScale != 1 || sdf);

        std::array<float, 2> texsize = {{
                float(spriteAtlas.getWidth()),
                float(spriteAtlas.getHeight())
            }
        };

        if (sdf) {
            renderSDF(bucket,
                      id,
                      matrix,
                      bucket.properties.icon,
                      properties.icon,
                      1.0f,
                      texsize,
                      *sdfIconShader,
                      &SymbolBucket::drawIcons);
        } else {
            mat4 vtxMatrix = translatedMatrix(matrix, properties.icon.translate, id, properties.icon.translate_anchor);

            mat4 exMatrix;
            matrix::copy(exMatrix, projMatrix);

            if (angleOffset) {
                matrix::rotate_z(exMatrix, exMatrix, angleOffset);
            }

            matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f);

            useProgram(iconShader->program);
            iconShader->u_matrix = vtxMatrix;
            iconShader->u_exmatrix = exMatrix;
            iconShader->u_texsize = texsize;

            // Convert the -pi..pi to an int8 range.
            const float angle = std::round(map.getState().getAngle() / M_PI * 128);

            // adjust min/max zooms for variable font sies
            float zoomAdjust = std::log(fontSize / bucket.properties.icon.max_size) / std::log(2);

            iconShader->u_angle = (int32_t)(angle + 256) % 256;
            iconShader->u_flip = bucket.properties.placement == PlacementType::Line ? 1 : 0;
            iconShader->u_zoom = (map.getState().getNormalizedZoom() - zoomAdjust) * 10; // current zoom level

            iconShader->u_fadedist = 0 * 10;
            iconShader->u_minfadezoom = map.getState().getNormalizedZoom() * 10;
            iconShader->u_maxfadezoom = map.getState().getNormalizedZoom() * 10;
            iconShader->u_fadezoom = map.getState().getNormalizedZoom() * 10;
            iconShader->u_opacity = properties.icon.opacity;

            depthRange(strata, 1.0f);
            bucket.drawIcons(*iconShader);
        }
    }

    glEnable(GL_STENCIL_TEST);
}
void Painter::renderSymbol(SymbolBucket &bucket, const StyleLayer &layer_desc, const TileID &id, const mat4 &matrix) {
    // Abort early.
    if (pass == RenderPass::Opaque) {
        return;
    }

    const auto &properties = layer_desc.getProperties<SymbolProperties>();
    const auto &layout = bucket.layout;

    config.depthTest = true;
    config.depthMask = GL_FALSE;

    if (bucket.hasCollisionBoxData() && (
                (bucket.hasIconData() && properties.icon.opacity) ||
                (bucket.hasTextData() && properties.text.opacity))) {
        config.stencilTest = true;

        useProgram(collisionBoxShader->program);
        collisionBoxShader->u_matrix = matrix;
        collisionBoxShader->u_scale = std::pow(2, state.getNormalizedZoom() - id.z);
        collisionBoxShader->u_zoom = state.getNormalizedZoom() * 10;
        collisionBoxShader->u_maxzoom = (id.z + 1) * 10;
        lineWidth(3.0f);

        config.depthRange = { strata, 1.0f };
        bucket.drawCollisionBoxes(*collisionBoxShader);

    }

    // TODO remove the `|| true` when #1673 is implemented
    const bool drawAcrossEdges = !(layout.text.allow_overlap || layout.icon.allow_overlap ||
          layout.text.ignore_placement || layout.icon.ignore_placement) || true;

    // Disable the stencil test so that labels aren't clipped to tile boundaries.
    //
    // Layers with features that may be drawn overlapping aren't clipped. These
    // layers are sorted in the y direction, and to draw the correct ordering near
    // tile edges the icons are included in both tiles and clipped when drawing.
    config.stencilTest = drawAcrossEdges ? false : true;

    if (bucket.hasIconData()) {
        bool sdf = bucket.sdfIcons;

        const float angleOffset =
            layout.icon.rotation_alignment == RotationAlignmentType::Map
                ? state.getAngle()
                : 0;

        // If layerStyle.size > bucket.info.fontSize then labels may collide
        const float fontSize = properties.icon.size != 0 ? properties.icon.size : layout.icon.max_size;
        const float fontScale = fontSize / 1.0f;

        spriteAtlas->bind(state.isChanging() || layout.placement == PlacementType::Line || angleOffset != 0 || fontScale != 1 || sdf);

        if (sdf) {
            renderSDF(bucket,
                      id,
                      matrix,
                      layout.icon,
                      properties.icon,
                      1.0f,
                      {{ float(spriteAtlas->getWidth()) / 4.0f, float(spriteAtlas->getHeight()) / 4.0f }},
                      *sdfIconShader,
                      &SymbolBucket::drawIcons);
        } else {
            mat4 vtxMatrix = translatedMatrix(matrix, properties.icon.translate, id, properties.icon.translate_anchor);

            mat4 exMatrix;
            matrix::copy(exMatrix, projMatrix);

            if (angleOffset) {
                matrix::rotate_z(exMatrix, exMatrix, angleOffset);
            }

            matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f);

            useProgram(iconShader->program);
            iconShader->u_matrix = vtxMatrix;
            iconShader->u_exmatrix = exMatrix;
            iconShader->u_texsize = {{ float(spriteAtlas->getWidth()) / 4.0f, float(spriteAtlas->getHeight()) / 4.0f }};

            // adjust min/max zooms for variable font sies
            float zoomAdjust = std::log(fontSize / layout.icon.max_size) / std::log(2);

            iconShader->u_zoom = (state.getNormalizedZoom() - zoomAdjust) * 10; // current zoom level
            iconShader->u_fadedist = 0 * 10;
            iconShader->u_minfadezoom = state.getNormalizedZoom() * 10;
            iconShader->u_maxfadezoom = state.getNormalizedZoom() * 10;
            iconShader->u_fadezoom = state.getNormalizedZoom() * 10;
            iconShader->u_opacity = properties.icon.opacity;

            config.depthRange = { strata, 1.0f };
            bucket.drawIcons(*iconShader);
        }
    }

    if (bucket.hasTextData()) {
        glyphAtlas->bind();

        renderSDF(bucket,
                  id,
                  matrix,
                  layout.text,
                  properties.text,
                  24.0f,
                  {{ float(glyphAtlas->width) / 4, float(glyphAtlas->height) / 4 }},
                  *sdfGlyphShader,
                  &SymbolBucket::drawGlyphs);
    }

}
void Painter::renderSymbol(SymbolBucket& bucket, const SymbolLayer& layer, const TileID& id, const mat4& matrix) {
    // Abort early.
    if (pass == RenderPass::Opaque) {
        return;
    }

    const auto& properties = layer.paint;
    const auto& layout = bucket.layout;

    config.depthMask = GL_FALSE;

    if (bucket.hasCollisionBoxData()) {
        config.stencilOp.reset();
        config.stencilTest = GL_TRUE;

        config.program = collisionBoxShader->program;
        collisionBoxShader->u_matrix = matrix;
        collisionBoxShader->u_scale = std::pow(2, state.getNormalizedZoom() - id.z);
        collisionBoxShader->u_zoom = state.getNormalizedZoom() * 10;
        collisionBoxShader->u_maxzoom = (id.z + 1) * 10;
        config.lineWidth = 1.0f;

        setDepthSublayer(0);
        bucket.drawCollisionBoxes(*collisionBoxShader);

    }

    // TODO remove the `true ||` when #1673 is implemented
    const bool drawAcrossEdges = true || !(layout.text.allowOverlap || layout.icon.allowOverlap ||
          layout.text.ignorePlacement || layout.icon.ignorePlacement);

    // Disable the stencil test so that labels aren't clipped to tile boundaries.
    //
    // Layers with features that may be drawn overlapping aren't clipped. These
    // layers are sorted in the y direction, and to draw the correct ordering near
    // tile edges the icons are included in both tiles and clipped when drawing.
    if (drawAcrossEdges) {
        config.stencilTest = GL_FALSE;
    } else {
        config.stencilOp.reset();
        config.stencilTest = GL_TRUE;
    }

    if (bucket.hasIconData()) {
        if (layout.icon.rotationAlignment == RotationAlignmentType::Map) {
            config.depthFunc.reset();
            config.depthTest = GL_TRUE;
        } else {
            config.depthTest = GL_FALSE;
        }

        bool sdf = bucket.sdfIcons;

        const float angleOffset =
            layout.icon.rotationAlignment == RotationAlignmentType::Map
                ? state.getAngle()
                : 0;

        const float fontSize = properties.icon.size;
        const float fontScale = fontSize / 1.0f;

        SpriteAtlas* activeSpriteAtlas = layer.spriteAtlas;
        activeSpriteAtlas->bind(state.isChanging() || layout.placement == PlacementType::Line
                || angleOffset != 0 || fontScale != 1 || sdf || state.getPitch() != 0);

        if (sdf) {
            renderSDF(bucket,
                      id,
                      matrix,
                      layout.icon,
                      properties.icon,
                      1.0f,
                      {{ float(activeSpriteAtlas->getWidth()) / 4.0f, float(activeSpriteAtlas->getHeight()) / 4.0f }},
                      *sdfIconShader,
                      &SymbolBucket::drawIcons);
        } else {
            mat4 vtxMatrix = translatedMatrix(matrix, properties.icon.translate, id, properties.icon.translateAnchor);

            bool skewed = layout.icon.rotationAlignment == RotationAlignmentType::Map;
            mat4 exMatrix;
            float s;

            if (skewed) {
                matrix::identity(exMatrix);
                s = 4096.0f / util::tileSize / id.overscaling / std::pow(2, state.getZoom() - id.z);
            } else {
                exMatrix = extrudeMatrix;
                s = state.getAltitude();
            }
            matrix::scale(exMatrix, exMatrix, s, s, 1);

            matrix::scale(exMatrix, exMatrix, fontScale, fontScale, 1.0f);

            // calculate how much longer the real world distance is at the top of the screen
            // than at the middle of the screen.
            float topedgelength = std::sqrt(std::pow(state.getHeight(), 2) / 4.0f * (1.0f + std::pow(state.getAltitude(), 2)));
            float x = state.getHeight() / 2.0f * std::tan(state.getPitch());
            float extra = (topedgelength + x) / topedgelength - 1;

            config.program = iconShader->program;
            iconShader->u_matrix = vtxMatrix;
            iconShader->u_exmatrix = exMatrix;
            iconShader->u_texsize = {{ float(spriteAtlas->getWidth()) / 4.0f, float(spriteAtlas->getHeight()) / 4.0f }};
            iconShader->u_skewed = skewed;
            iconShader->u_extra = extra;

            // adjust min/max zooms for variable font sies
            float zoomAdjust = std::log(fontSize / layout.icon.size) / std::log(2);

            iconShader->u_zoom = (state.getNormalizedZoom() - zoomAdjust) * 10; // current zoom level
            iconShader->u_fadedist = 0 * 10;
            iconShader->u_minfadezoom = state.getNormalizedZoom() * 10;
            iconShader->u_maxfadezoom = state.getNormalizedZoom() * 10;
            iconShader->u_fadezoom = state.getNormalizedZoom() * 10;
            iconShader->u_opacity = properties.icon.opacity;

            setDepthSublayer(0);
            bucket.drawIcons(*iconShader);
        }
    }

    if (bucket.hasTextData()) {
        if (layout.text.rotationAlignment == RotationAlignmentType::Map) {
            config.depthFunc.reset();
            config.depthTest = GL_TRUE;
        } else {
            config.depthTest = GL_FALSE;
        }

        glyphAtlas->bind();

        renderSDF(bucket,
                  id,
                  matrix,
                  layout.text,
                  properties.text,
                  24.0f,
                  {{ float(glyphAtlas->width) / 4, float(glyphAtlas->height) / 4 }},
                  *sdfGlyphShader,
                  &SymbolBucket::drawGlyphs);
    }

}
void Painter::renderSymbol(PaintParameters& parameters,
                           SymbolBucket& bucket,
                           const SymbolLayer& layer,
                           const RenderTile& tile) {
    // Abort early.
    if (pass == RenderPass::Opaque) {
        return;
    }

    const auto& paint = layer.impl->paint;
    const auto& layout = bucket.layout;

    config.depthMask = GL_FALSE;

    // TODO remove the `true ||` when #1673 is implemented
    const bool drawAcrossEdges = (frame.mapMode == MapMode::Continuous) && (true || !(layout.textAllowOverlap || layout.iconAllowOverlap ||
          layout.textIgnorePlacement || layout.iconIgnorePlacement));

    // Disable the stencil test so that labels aren't clipped to tile boundaries.
    //
    // Layers with features that may be drawn overlapping aren't clipped. These
    // layers are sorted in the y direction, and to draw the correct ordering near
    // tile edges the icons are included in both tiles and clipped when drawing.
    if (drawAcrossEdges) {
        config.stencilTest = GL_FALSE;
    } else {
        config.stencilOp.reset();
        config.stencilTest = GL_TRUE;
    }

    if (bucket.hasIconData()) {
        if (layout.iconRotationAlignment == AlignmentType::Map) {
            config.depthFunc.reset();
            config.depthTest = GL_TRUE;
        } else {
            config.depthTest = GL_FALSE;
        }

        bool sdf = bucket.sdfIcons;

        const float angleOffset =
            layout.iconRotationAlignment == AlignmentType::Map
                ? state.getAngle()
                : 0;

        const float fontSize = layer.impl->iconSize;
        const float fontScale = fontSize / 1.0f;

        SpriteAtlas* activeSpriteAtlas = layer.impl->spriteAtlas;
        const bool iconScaled = fontScale != 1 || frame.pixelRatio != activeSpriteAtlas->getPixelRatio() || bucket.iconsNeedLinear;
        const bool iconTransformed = layout.iconRotationAlignment == AlignmentType::Map || angleOffset != 0 || state.getPitch() != 0;
        activeSpriteAtlas->bind(sdf || state.isChanging() || iconScaled || iconTransformed, store, config, 0);

        if (sdf) {
            renderSDF(bucket,
                      tile,
                      1.0f,
                      {{ float(activeSpriteAtlas->getWidth()) / 4.0f, float(activeSpriteAtlas->getHeight()) / 4.0f }},
                      parameters.shaders.sdfIcon,
                      &SymbolBucket::drawIcons,
                      layout.iconRotationAlignment,
                      // icon-pitch-alignment is not yet implemented
                      // and we simply inherit the rotation alignment
                      layout.iconRotationAlignment,
                      layout.iconSize,
                      paint.iconOpacity,
                      paint.iconColor,
                      paint.iconHaloColor,
                      paint.iconHaloWidth,
                      paint.iconHaloBlur,
                      paint.iconTranslate,
                      paint.iconTranslateAnchor,
                      layer.impl->iconSize);
        } else {
            mat4 vtxMatrix = tile.translatedMatrix(paint.iconTranslate,
                                                   paint.iconTranslateAnchor,
                                                   state);

            std::array<float, 2> extrudeScale;

            const bool alignedWithMap = layout.iconRotationAlignment == AlignmentType::Map;
            if (alignedWithMap) {
                extrudeScale.fill(tile.id.pixelsToTileUnits(1, state.getZoom()) * fontScale);
            } else {
                extrudeScale = {{
                    pixelsToGLUnits[0] * fontScale * state.getAltitude(),
                    pixelsToGLUnits[1] * fontScale * state.getAltitude()
                }};
            }

            auto& iconShader = parameters.shaders.icon;

            config.program = iconShader.getID();
            iconShader.u_matrix = vtxMatrix;
            iconShader.u_extrude_scale = extrudeScale;
            iconShader.u_texsize = {{ float(activeSpriteAtlas->getWidth()) / 4.0f, float(activeSpriteAtlas->getHeight()) / 4.0f }};
            iconShader.u_rotate_with_map = alignedWithMap;
            iconShader.u_texture = 0;

            // adjust min/max zooms for variable font sies
            float zoomAdjust = std::log(fontSize / layout.iconSize) / std::log(2);
            iconShader.u_zoom = (state.getZoom() - zoomAdjust) * 10; // current zoom level
            iconShader.u_opacity = paint.iconOpacity;

            frameHistory.bind(store, config, 1);
            iconShader.u_fadetexture = 1;

            setDepthSublayer(0);
            bucket.drawIcons(iconShader, store, isOverdraw());
        }
    }

    if (bucket.hasTextData()) {
        if (layout.textRotationAlignment == AlignmentType::Map) {
            config.depthFunc.reset();
            config.depthTest = GL_TRUE;
        } else {
            config.depthTest = GL_FALSE;
        }

        glyphAtlas->bind(store, config, 0);

        renderSDF(bucket,
                  tile,
                  24.0f,
                  {{ float(glyphAtlas->width) / 4, float(glyphAtlas->height) / 4 }},
                  parameters.shaders.sdfGlyph,
                  &SymbolBucket::drawGlyphs,
                  layout.textRotationAlignment,
                  layout.textPitchAlignment,
                  layout.textSize,
                  paint.textOpacity,
                  paint.textColor,
                  paint.textHaloColor,
                  paint.textHaloWidth,
                  paint.textHaloBlur,
                  paint.textTranslate,
                  paint.textTranslateAnchor,
                  layer.impl->textSize);
    }

    if (bucket.hasCollisionBoxData()) {
        config.stencilOp.reset();
        config.stencilTest = GL_TRUE;

        auto& collisionBoxShader = shaders->collisionBox;
        config.program = collisionBoxShader.getID();
        collisionBoxShader.u_matrix = tile.matrix;
        // TODO: This was the overscaled z instead of the canonical z.
        collisionBoxShader.u_scale = std::pow(2, state.getZoom() - tile.id.canonical.z);
        collisionBoxShader.u_zoom = state.getZoom() * 10;
        collisionBoxShader.u_maxzoom = (tile.id.canonical.z + 1) * 10;
        config.lineWidth = 1.0f;

        setDepthSublayer(0);
        bucket.drawCollisionBoxes(collisionBoxShader, store);

    }
}
Esempio n. 6
0
TEST(Buckets, SymbolBucket) {
    HeadlessBackend backend({ 512, 256 });
    BackendScope scope { backend };

    style::SymbolLayoutProperties::PossiblyEvaluated layout;
    bool sdfIcons = false;
    bool iconsNeedLinear = false;
    bool sortFeaturesByY = false;
    std::vector<SymbolInstance> symbolInstances;

    gl::Context context;
    SymbolBucket bucket { layout, {}, 16.0f, 1.0f, 0, sdfIcons, iconsNeedLinear, sortFeaturesByY, std::move(symbolInstances) };
    ASSERT_FALSE(bucket.hasIconData());
    ASSERT_FALSE(bucket.hasTextData());
    ASSERT_FALSE(bucket.hasCollisionBoxData());
    ASSERT_FALSE(bucket.hasData());
    ASSERT_FALSE(bucket.needsUpload());

    // SymbolBucket::addFeature() is a no-op.
    GeometryCollection point { { { 0, 0 } } };
    bucket.addFeature(StubGeometryTileFeature { {}, FeatureType::Point, point, properties }, point);
    ASSERT_FALSE(bucket.hasData());
    ASSERT_FALSE(bucket.needsUpload());

    bucket.text.segments.emplace_back(0, 0);
    ASSERT_TRUE(bucket.hasTextData());
    ASSERT_TRUE(bucket.hasData());
    ASSERT_TRUE(bucket.needsUpload());

    bucket.upload(context);
    ASSERT_FALSE(bucket.needsUpload());
}
Esempio n. 7
0
void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& seenCrossTileIDs) {
    if (bucket.hasTextData()) bucket.text.opacityVertices.clear();
    if (bucket.hasIconData()) bucket.icon.opacityVertices.clear();
    if (bucket.hasCollisionBoxData()) bucket.collisionBox.dynamicVertices.clear();
    if (bucket.hasCollisionCircleData()) bucket.collisionCircle.dynamicVertices.clear();

    JointOpacityState duplicateOpacityState(false, false, true);

    const bool textAllowOverlap = bucket.layout.get<style::TextAllowOverlap>();
    const bool iconAllowOverlap = bucket.layout.get<style::IconAllowOverlap>();
    
    // If allow-overlap is true, we can show symbols before placement runs on them
    // But we have to wait for placement if we potentially depend on a paired icon/text
    // with allow-overlap: false.
    // See https://github.com/mapbox/mapbox-gl-native/issues/12483
    JointOpacityState defaultOpacityState(
            textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || bucket.layout.get<style::IconOptional>()),
            iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || bucket.layout.get<style::TextOptional>()),
            true);

    for (SymbolInstance& symbolInstance : bucket.symbolInstances) {
        bool isDuplicate = seenCrossTileIDs.count(symbolInstance.crossTileID) > 0;

        auto it = opacities.find(symbolInstance.crossTileID);
        auto opacityState = defaultOpacityState;
        if (isDuplicate) {
            opacityState = duplicateOpacityState;
        } else if (it != opacities.end()) {
            opacityState = it->second;
        }

        if (it == opacities.end()) {
            opacities.emplace(symbolInstance.crossTileID, defaultOpacityState);
        }

        seenCrossTileIDs.insert(symbolInstance.crossTileID);

        if (symbolInstance.hasText) {
            auto opacityVertex = SymbolOpacityAttributes::vertex(opacityState.text.placed, opacityState.text.opacity);
            for (size_t i = 0; i < symbolInstance.horizontalGlyphQuads.size() * 4; i++) {
                bucket.text.opacityVertices.emplace_back(opacityVertex);
            }
            for (size_t i = 0; i < symbolInstance.verticalGlyphQuads.size() * 4; i++) {
                bucket.text.opacityVertices.emplace_back(opacityVertex);
            }
            if (symbolInstance.placedTextIndex) {
                bucket.text.placedSymbols[*symbolInstance.placedTextIndex].hidden = opacityState.isHidden();
            }
            if (symbolInstance.placedVerticalTextIndex) {
                bucket.text.placedSymbols[*symbolInstance.placedVerticalTextIndex].hidden = opacityState.isHidden();
            }
        }
        if (symbolInstance.hasIcon) {
            auto opacityVertex = SymbolOpacityAttributes::vertex(opacityState.icon.placed, opacityState.icon.opacity);
            if (symbolInstance.iconQuad) {
                bucket.icon.opacityVertices.emplace_back(opacityVertex);
                bucket.icon.opacityVertices.emplace_back(opacityVertex);
                bucket.icon.opacityVertices.emplace_back(opacityVertex);
                bucket.icon.opacityVertices.emplace_back(opacityVertex);
            }
            if (symbolInstance.placedIconIndex) {
                bucket.icon.placedSymbols[*symbolInstance.placedIconIndex].hidden = opacityState.isHidden();
            }
        }
        
        auto updateCollisionBox = [&](const auto& feature, const bool placed) {
            if (feature.alongLine) {
                return;
            }
            auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(placed, false);
            for (size_t i = 0; i < feature.boxes.size() * 4; i++) {
                bucket.collisionBox.dynamicVertices.emplace_back(dynamicVertex);
            }
        };
        
        auto updateCollisionCircles = [&](const auto& feature, const bool placed) {
            if (!feature.alongLine) {
                return;
            }
            for (const CollisionBox& box : feature.boxes) {
                auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(placed, !box.used);
                bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex);
                bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex);
                bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex);
                bucket.collisionCircle.dynamicVertices.emplace_back(dynamicVertex);
            }
        };
        
        if (bucket.hasCollisionBoxData()) {
            updateCollisionBox(symbolInstance.textCollisionFeature, opacityState.text.placed);
            updateCollisionBox(symbolInstance.iconCollisionFeature, opacityState.icon.placed);
        }
        if (bucket.hasCollisionCircleData()) {
            updateCollisionCircles(symbolInstance.textCollisionFeature, opacityState.text.placed);
            updateCollisionCircles(symbolInstance.iconCollisionFeature, opacityState.icon.placed);
        }
    }

    bucket.updateOpacity();
    bucket.sortFeatures(state.getAngle());
    auto retainedData = retainedQueryData.find(bucket.bucketInstanceId);
    if (retainedData != retainedQueryData.end()) {
        retainedData->second.featureSortOrder = bucket.featureSortOrder;
    }
}
Esempio n. 8
0
void Placement::placeLayerBucket(
        SymbolBucket& bucket,
        const mat4& posMatrix,
        const mat4& textLabelPlaneMatrix,
        const mat4& iconLabelPlaneMatrix,
        const float scale,
        const float textPixelRatio,
        const bool showCollisionBoxes,
        std::unordered_set<uint32_t>& seenCrossTileIDs,
        const bool holdingForFade,
        const CollisionGroups::CollisionGroup& collisionGroup) {

    auto partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(state.getZoom());
    auto partiallyEvaluatedIconSize = bucket.iconSizeBinder->evaluateForZoom(state.getZoom());

    optional<CollisionTileBoundaries> avoidEdges;
    if (mapMode == MapMode::Tile &&
        (bucket.layout.get<style::SymbolAvoidEdges>() ||
         bucket.layout.get<style::SymbolPlacement>() == style::SymbolPlacementType::Line)) {
        avoidEdges = collisionIndex.projectTileBoundaries(posMatrix);
    }
    
    const bool textAllowOverlap = bucket.layout.get<style::TextAllowOverlap>();
    const bool iconAllowOverlap = bucket.layout.get<style::IconAllowOverlap>();
    // This logic is similar to the "defaultOpacityState" logic below in updateBucketOpacities
    // If we know a symbol is always supposed to show, force it to be marked visible even if
    // it wasn't placed into the collision index (because some or all of it was outside the range
    // of the collision grid).
    // There is a subtle edge case here we're accepting:
    //  Symbol A has text-allow-overlap: true, icon-allow-overlap: true, icon-optional: false
    //  A's icon is outside the grid, so doesn't get placed
    //  A's text would be inside grid, but doesn't get placed because of icon-optional: false
    //  We still show A because of the allow-overlap settings.
    //  Symbol B has allow-overlap: false, and gets placed where A's text would be
    //  On panning in, there is a short period when Symbol B and Symbol A will overlap
    //  This is the reverse of our normal policy of "fade in on pan", but should look like any other
    //  collision and hopefully not be too noticeable.
    // See https://github.com/mapbox/mapbox-gl-native/issues/12683
    const bool alwaysShowText = textAllowOverlap && (iconAllowOverlap || !bucket.hasIconData() || bucket.layout.get<style::IconOptional>());
    const bool alwaysShowIcon = iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || bucket.layout.get<style::TextOptional>());
    
    for (auto& symbolInstance : bucket.symbolInstances) {

        if (seenCrossTileIDs.count(symbolInstance.crossTileID) == 0) {
            if (holdingForFade) {
                // Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't
                // know yet if we have a duplicate in a parent tile that _should_ be placed.
                placements.emplace(symbolInstance.crossTileID, JointPlacement(false, false, false));
                continue;
            }

            bool placeText = false;
            bool placeIcon = false;
            bool offscreen = true;

            if (symbolInstance.placedTextIndex) {
                PlacedSymbol& placedSymbol = bucket.text.placedSymbols.at(*symbolInstance.placedTextIndex);
                const float fontSize = evaluateSizeForFeature(partiallyEvaluatedTextSize, placedSymbol);

                auto placed = collisionIndex.placeFeature(symbolInstance.textCollisionFeature,
                        posMatrix, textLabelPlaneMatrix, textPixelRatio,
                        placedSymbol, scale, fontSize,
                        bucket.layout.get<style::TextAllowOverlap>(),
                        bucket.layout.get<style::TextPitchAlignment>() == style::AlignmentType::Map,
                        showCollisionBoxes, avoidEdges, collisionGroup.second);
                placeText = placed.first;
                offscreen &= placed.second;
            }

            if (symbolInstance.placedIconIndex) {
                PlacedSymbol& placedSymbol = bucket.icon.placedSymbols.at(*symbolInstance.placedIconIndex);
                const float fontSize = evaluateSizeForFeature(partiallyEvaluatedIconSize, placedSymbol);

                auto placed = collisionIndex.placeFeature(symbolInstance.iconCollisionFeature,
                        posMatrix, iconLabelPlaneMatrix, textPixelRatio,
                        placedSymbol, scale, fontSize,
                        bucket.layout.get<style::IconAllowOverlap>(),
                        bucket.layout.get<style::IconPitchAlignment>() == style::AlignmentType::Map,
                        showCollisionBoxes, avoidEdges, collisionGroup.second);
                placeIcon = placed.first;
                offscreen &= placed.second;
            }

            const bool iconWithoutText = !symbolInstance.hasText || bucket.layout.get<style::TextOptional>();
            const bool textWithoutIcon = !symbolInstance.hasIcon || bucket.layout.get<style::IconOptional>();

            // combine placements for icon and text
            if (!iconWithoutText && !textWithoutIcon) {
                placeText = placeIcon = placeText && placeIcon;
            } else if (!textWithoutIcon) {
                placeText = placeText && placeIcon;
            } else if (!iconWithoutText) {
                placeIcon = placeText && placeIcon;
            }

            if (placeText) {
                collisionIndex.insertFeature(symbolInstance.textCollisionFeature, bucket.layout.get<style::TextIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
            }

            if (placeIcon) {
                collisionIndex.insertFeature(symbolInstance.iconCollisionFeature, bucket.layout.get<style::IconIgnorePlacement>(), bucket.bucketInstanceId, collisionGroup.first);
            }

            assert(symbolInstance.crossTileID != 0);

            if (placements.find(symbolInstance.crossTileID) != placements.end()) {
                // If there's a previous placement with this ID, it comes from a tile that's fading out
                // Erase it so that the placement result from the non-fading tile supersedes it
                placements.erase(symbolInstance.crossTileID);
            }
            
            placements.emplace(symbolInstance.crossTileID, JointPlacement(placeText || alwaysShowText, placeIcon || alwaysShowIcon, offscreen || bucket.justReloaded));
            seenCrossTileIDs.insert(symbolInstance.crossTileID);
        }
    } 

    bucket.justReloaded = false;
}