#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { using namespace style; namespace { inline const LineLayer::Impl& impl_cast(const Immutable& impl) { assert(impl->getTypeInfo() == LineLayer::Impl::staticTypeInfo()); return static_cast(*impl); } } // namespace RenderLineLayer::RenderLineLayer(Immutable _impl) : RenderLayer(makeMutable(std::move(_impl))), unevaluated(impl_cast(baseImpl).paint.untransitioned()), colorRamp({256, 1}) {} RenderLineLayer::~RenderLineLayer() = default; void RenderLineLayer::transition(const TransitionParameters& parameters) { unevaluated = impl_cast(baseImpl).paint.transitioned(parameters, std::move(unevaluated)); updateColorRamp(); } void RenderLineLayer::evaluate(const PropertyEvaluationParameters& parameters) { auto properties = makeMutable( staticImmutableCast(baseImpl), parameters.getCrossfadeParameters(), unevaluated.evaluate(parameters)); auto& evaluated = properties->evaluated; passes = (evaluated.get().constantOr(1.0) > 0 && evaluated.get().constantOr(Color::black()).a > 0 && evaluated.get().constantOr(1.0) > 0) ? RenderPass::Translucent : RenderPass::None; properties->renderPasses = mbgl::underlying_type(passes); evaluatedProperties = std::move(properties); } bool RenderLineLayer::hasTransition() const { return unevaluated.hasTransition(); } bool RenderLineLayer::hasCrossfade() const { return getCrossfade(evaluatedProperties).t != 1; } void RenderLineLayer::prepare(const LayerPrepareParameters& params) { RenderLayer::prepare(params); for (const RenderTile& tile : *renderTiles) { const LayerRenderData* renderData = tile.getLayerRenderData(*baseImpl); if (!renderData) continue; const auto& evaluated = getEvaluated(renderData->layerProperties); if (evaluated.get().from.empty()) continue; auto& bucket = static_cast(*renderData->bucket); const LinePatternCap cap = bucket.layout.get() == LineCapType::Round ? LinePatternCap::Round : LinePatternCap::Square; // Ensures that the dash data gets added to the atlas. params.lineAtlas.getDashPatternTexture( evaluated.get().from, evaluated.get().to, cap); } } void RenderLineLayer::upload(gfx::UploadPass& uploadPass) { if (!unevaluated.get().getValue().isUndefined() && !colorRampTexture) { colorRampTexture = uploadPass.createTexture(colorRamp); } } void RenderLineLayer::render(PaintParameters& parameters) { assert(renderTiles); if (parameters.pass == RenderPass::Opaque) { return; } parameters.renderTileClippingMasks(renderTiles); for (const RenderTile& tile : *renderTiles) { const LayerRenderData* renderData = getRenderDataForPass(tile, parameters.pass); if (!renderData) { continue; } auto& bucket = static_cast(*renderData->bucket); const auto& evaluated = getEvaluated(renderData->layerProperties); const auto& crossfade = getCrossfade(renderData->layerProperties); auto draw = [&](auto& programInstance, auto&& uniformValues, const optional& patternPositionA, const optional& patternPositionB, auto&& textureBindings) { const auto& paintPropertyBinders = bucket.paintPropertyBinders.at(getID()); paintPropertyBinders.setPatternParameters(patternPositionA, patternPositionB, crossfade); const auto allUniformValues = programInstance.computeAllUniformValues(std::forward(uniformValues), paintPropertyBinders, evaluated, parameters.state.getZoom()); const auto allAttributeBindings = programInstance.computeAllAttributeBindings( *bucket.vertexBuffer, paintPropertyBinders, evaluated ); checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); programInstance.draw(parameters.context, *parameters.renderPass, gfx::Triangles(), parameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly), parameters.stencilModeForClipping(tile.id), parameters.colorModeForRenderPass(), gfx::CullFaceMode::disabled(), *bucket.indexBuffer, bucket.segments, allUniformValues, allAttributeBindings, std::forward(textureBindings), getID()); }; if (!evaluated.get().from.empty()) { const LinePatternCap cap = bucket.layout.get() == LineCapType::Round ? LinePatternCap::Round : LinePatternCap::Square; const auto& dashPatternTexture = parameters.lineAtlas.getDashPatternTexture( evaluated.get().from, evaluated.get().to, cap); draw(parameters.programs.getLineLayerPrograms().lineSDF, LineSDFProgram::layoutUniformValues(evaluated, parameters.pixelRatio, tile, parameters.state, parameters.pixelsToGLUnits, dashPatternTexture.getFrom(), dashPatternTexture.getTo(), crossfade, dashPatternTexture.getSize().width), {}, {}, LineSDFProgram::TextureBindings{ dashPatternTexture.textureBinding(), }); } else if (!unevaluated.get().isUndefined()) { const auto& linePatternValue = evaluated.get().constantOr(Faded{"", ""}); const Size& texsize = tile.getIconAtlasTexture().size; optional posA = tile.getPattern(linePatternValue.from.id()); optional posB = tile.getPattern(linePatternValue.to.id()); draw(parameters.programs.getLineLayerPrograms().linePattern, LinePatternProgram::layoutUniformValues( evaluated, tile, parameters.state, parameters.pixelsToGLUnits, parameters.pixelRatio, texsize, crossfade), posA, posB, LinePatternProgram::TextureBindings{ textures::image::Value{ tile.getIconAtlasTexture().getResource(), gfx::TextureFilterType::Linear }, }); } else if (!unevaluated.get().getValue().isUndefined()) { assert(colorRampTexture); draw(parameters.programs.getLineLayerPrograms().lineGradient, LineGradientProgram::layoutUniformValues( evaluated, tile, parameters.state, parameters.pixelsToGLUnits, parameters.pixelRatio), {}, {}, LineGradientProgram::TextureBindings{ textures::image::Value{ colorRampTexture->getResource(), gfx::TextureFilterType::Linear }, }); } else { draw(parameters.programs.getLineLayerPrograms().line, LineProgram::layoutUniformValues( evaluated, tile, parameters.state, parameters.pixelsToGLUnits, parameters.pixelRatio), {}, {}, LineProgram::TextureBindings{}); } } } namespace { GeometryCollection offsetLine(const GeometryCollection& rings, double offset) { assert(offset != 0.0f); assert(!rings.empty()); GeometryCollection newRings; newRings.reserve(rings.size()); const Point zero(0, 0); for (const auto& ring : rings) { newRings.emplace_back(); auto& newRing = newRings.back(); newRing.reserve(ring.size()); for (auto i = ring.begin(); i != ring.end(); ++i) { auto& p = *i; Point aToB = i == ring.begin() ? zero : util::perp(util::unit(convertPoint(p - *(i - 1)))); Point bToC = i + 1 == ring.end() ? zero : util::perp(util::unit(convertPoint(*(i + 1) - p))); Point extrude = util::unit(aToB + bToC); const double cosHalfAngle = extrude.x * bToC.x + extrude.y * bToC.y; extrude *= (cosHalfAngle != 0) ? (1.0 / cosHalfAngle) : 0; newRing.emplace_back(convertPoint(extrude * offset) + p); } } return newRings; } } // namespace bool RenderLineLayer::queryIntersectsFeature(const GeometryCoordinates& queryGeometry, const GeometryTileFeature& feature, const float zoom, const TransformState& transformState, const float pixelsToTileUnits, const mat4&, const FeatureState& featureState) const { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; // Translate query geometry auto translatedQueryGeometry = FeatureIndex::translateQueryGeometry( queryGeometry, evaluated.get(), evaluated.get(), transformState.getBearing(), pixelsToTileUnits); // Evaluate function auto offset = evaluated.get().evaluate(feature, zoom, featureState, style::LineOffset::defaultValue()) * pixelsToTileUnits; // Test intersection const auto halfWidth = static_cast(getLineWidth(feature, zoom, featureState) / 2.0 * pixelsToTileUnits); // Apply offset to geometry if (offset != 0.0f && !feature.getGeometries().empty()) { return util::polygonIntersectsBufferedMultiLine( translatedQueryGeometry.value_or(queryGeometry), offsetLine(feature.getGeometries(), offset), halfWidth); } return util::polygonIntersectsBufferedMultiLine( translatedQueryGeometry.value_or(queryGeometry), feature.getGeometries(), halfWidth); } void RenderLineLayer::updateColorRamp() { auto colorValue = unevaluated.get().getValue(); if (colorValue.isUndefined()) { return; } const auto length = colorRamp.bytes(); for (uint32_t i = 0; i < length; i += 4) { const auto color = colorValue.evaluate(static_cast(i) / length); colorRamp.data[i] = std::floor(color.r * 255); colorRamp.data[i + 1] = std::floor(color.g * 255); colorRamp.data[i + 2] = std::floor(color.b * 255); colorRamp.data[i + 3] = std::floor(color.a * 255); } if (colorRampTexture) { colorRampTexture = nullopt; } } float RenderLineLayer::getLineWidth(const GeometryTileFeature& feature, const float zoom, const FeatureState& featureState) const { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; float lineWidth = evaluated.get().evaluate(feature, zoom, featureState, style::LineWidth::defaultValue()); float gapWidth = evaluated.get().evaluate(feature, zoom, featureState, style::LineGapWidth::defaultValue()); if (gapWidth) { return gapWidth + 2 * lineWidth; } else { return lineWidth; } } } // namespace mbgl