See https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#trailing-whitespace
586 lines
23 KiB
C++
586 lines
23 KiB
C++
#pragma once
|
|
|
|
#include <mbgl/shaders/widevector_ubo.hpp>
|
|
#include <mbgl/shaders/shader_source.hpp>
|
|
#include <mbgl/shaders/mtl/shader_program.hpp>
|
|
|
|
namespace mbgl {
|
|
namespace shaders {
|
|
|
|
constexpr auto wideVectorShaderPrelude = R"(
|
|
|
|
enum {
|
|
idWideVectorUniformsUBO = idDrawableReservedVertexOnlyUBO,
|
|
idWideVectorUniformWideVecUBO = drawableReservedUBOCount,
|
|
wideVectorUBOCount
|
|
};
|
|
|
|
)";
|
|
|
|
template <>
|
|
struct ShaderSource<BuiltIn::WideVectorShader, gfx::Backend::Type::Metal> {
|
|
static constexpr auto name = "WideVectorShader";
|
|
static constexpr auto vertexMainFunction = "vertexTri_wideVecPerf";
|
|
static constexpr auto fragmentMainFunction = "fragmentTri_wideVecPerf";
|
|
|
|
static const std::array<AttributeInfo, 3> attributes;
|
|
static const std::array<AttributeInfo, 4> instanceAttributes;
|
|
static const std::array<TextureInfo, 0> textures;
|
|
|
|
static constexpr auto prelude = wideVectorShaderPrelude;
|
|
static constexpr auto source = R"(
|
|
|
|
namespace WhirlyKitShader
|
|
{
|
|
|
|
/** Expressions are used to change values like width and opacity over zoom levels. **/
|
|
#define WKSExpStops 8
|
|
|
|
// Line Joins
|
|
// These are assumed to match WideVectorLineJoinType
|
|
typedef enum {
|
|
WKSVertexLineJoinMiter = 0,
|
|
WKSVertexLineJoinBevel = 1,
|
|
WKSVertexLineJoinRound = 2,
|
|
WKSVertexLineJoinMiterClip = 3,
|
|
WKSVertexLineJoinMiterSimple = 4,
|
|
WKSVertexLineJoinNone = 5,
|
|
} WKSVertexLineJoinType;
|
|
|
|
// Line Caps
|
|
// These are assumed to match WideVectorLineCapType
|
|
typedef enum {
|
|
WKSVertexLineCapRound = 0,
|
|
WKSVertexLineCapButt = 1,
|
|
WKSVertexLineCapSquare = 2,
|
|
} WKSVertexLineCapType;
|
|
|
|
// Uniforms for the basic case.
|
|
struct Uniforms
|
|
{
|
|
simd::float4x4 mvpMatrix;
|
|
simd::float4x4 mvpMatrixDiff;
|
|
simd::float4x4 mvMatrix;
|
|
simd::float4x4 mvMatrixDiff;
|
|
simd::float4x4 pMatrix;
|
|
simd::float4x4 pMatrixDiff;
|
|
simd::float2 frameSize;
|
|
};
|
|
|
|
// Instructions to the wide vector shaders, usually per-drawable
|
|
struct UniformWideVec {
|
|
simd::float4 color; // Color
|
|
float w2; // Width / 2.0 in screen space
|
|
float offset; // Offset from center in screen space
|
|
float edge; // Edge falloff control
|
|
float texRepeat; // Texture scaling specific to wide vectors
|
|
simd::float2 texOffset; // Texture offset.
|
|
float miterLimit; // Miter join limit, multiples of width
|
|
WKSVertexLineJoinType join; // Line joins
|
|
WKSVertexLineCapType cap; // Line endcaps
|
|
bool hasExp; // Look for a UniformWideVecExp structure for color, opacity, and width
|
|
float interClipLimit; // Allow clipping of out-of-bounds intersection points
|
|
// Value is the multiple of distance-squared that is allowed.
|
|
};
|
|
|
|
// Instance info for the wide vector (new) vertex shader
|
|
typedef struct
|
|
{
|
|
// Center of the point on the line
|
|
simd::float3 center;
|
|
// Color
|
|
simd::float4 color;
|
|
// Used to track loops and such
|
|
int prev,next; // set to -1 for non-loops
|
|
} VertexTriWideVecInstance;
|
|
|
|
}
|
|
|
|
// These have syntax that only the shader language can grok
|
|
// We put them here so we can include them in other Metal libraries
|
|
#ifdef __METAL_VERSION__
|
|
|
|
/**
|
|
Wide Vector Shaders
|
|
These work to build/render objects in 2D space, but based
|
|
on 3D locations.
|
|
*/
|
|
|
|
// Vertex definition for wide vector (new version)
|
|
struct VertexTriWideVecB
|
|
{
|
|
// x, y offset around the center
|
|
float3 screenPos [[attribute(wideVectorUBOCount + 0)]];
|
|
float4 color [[attribute(wideVectorUBOCount + 1)]];
|
|
int index [[attribute(wideVectorUBOCount + 2)]];
|
|
};
|
|
|
|
// Wide vector vertex passed to fragment shader (new version)
|
|
struct ProjVertexTriWideVecPerf {
|
|
float4 position [[invariant]] [[position]]; // transformed to NDC
|
|
float2 screenPos; // un-transformed vertex position
|
|
float2 centerPos; // un-transformed circle center
|
|
float2 midDir; // Turn direction
|
|
float4 color;
|
|
float2 texCoord;
|
|
float w2;
|
|
float edge;
|
|
uint2 maskIDs;
|
|
bool roundJoin;
|
|
|
|
//uint whichVert; // helpful for debugging
|
|
};
|
|
|
|
#else
|
|
|
|
#endif
|
|
|
|
using namespace metal;
|
|
using namespace WhirlyKitShader;
|
|
|
|
struct IntersectInfo {
|
|
bool valid;
|
|
float2 interPt;
|
|
float c;
|
|
float ta,tb;
|
|
};
|
|
|
|
// wedge product (2D cross product)
|
|
// if A ^ B > 0, A is to the right of B
|
|
float wedge(float2 a, float2 b) {
|
|
return a.x * b.y - a.y * b.x;
|
|
}
|
|
|
|
// Intersect two lines
|
|
IntersectInfo intersectLines(float2 a0, float2 a1, float2 b0, float2 b1)
|
|
{
|
|
const float2 dA = a0 - a1;
|
|
const float2 dB = b0 - b1;
|
|
|
|
// Solve the system of equations formed by equating the lines to get their common point.
|
|
const float denom = wedge(dA, dB);
|
|
if (denom == 0.0) {
|
|
// If the denominator comes out zero, the lines are parallel and do not intersect
|
|
return { .valid = false };
|
|
}
|
|
|
|
const float tA = wedge(a0, a1);
|
|
const float tB = wedge(b0, b1);
|
|
const float2 inter = float2((tA * dB.x - dA.x * tB), (tA * dB.y - dA.y * tB)) / denom;
|
|
|
|
return {
|
|
.valid = true,
|
|
.interPt = inter,
|
|
.ta = 0.0,
|
|
.tb = 0.0,
|
|
};
|
|
}
|
|
|
|
// Intersect two offset lines
|
|
IntersectInfo intersectWideLines(float2 p0,float2 p1,float2 p2,
|
|
float2 n0,float2 n1)
|
|
{
|
|
return intersectLines(p0 + n0, p1 + n0, p1 + n1, p2 + n1);
|
|
}
|
|
|
|
// Used to track what info we have about a center point
|
|
struct CenterInfo {
|
|
/// Screen coordinates of the line segment endpoint
|
|
float2 screenPos;
|
|
/// Length of the segment (in screen coordinates)
|
|
float len;
|
|
/// Normalized direction of the segment
|
|
float2 nDir;
|
|
/// Normalized plane normal, perpendicular to the segment
|
|
float2 norm;
|
|
};
|
|
|
|
float3 viewPos(constant simd::float4x4 &mat, float3 vec) {
|
|
const float4 p = mat * float4(vec,1.0);
|
|
return p.xyz; // / p.w; ?
|
|
}
|
|
|
|
float2 screenPos_MVP(constant Uniforms &u, float3 viewPos) {
|
|
const float4 p4 = float4(viewPos, 1.0);
|
|
|
|
// Use the MVP matrix
|
|
const float4 s = u.mvpMatrix * p4;
|
|
|
|
return s.xy/s.w;
|
|
}
|
|
|
|
constant constexpr float wideVecMinTurnThreshold = 1e-5;
|
|
constant constexpr float wideVecMaxTurnThreshold = 0.99999998476; // sin(89.99 deg)
|
|
constant constexpr int WideVecPolyStartGeom = 0;
|
|
constant constexpr int WideVecPolyBodyGeom = 1;
|
|
constant constexpr int WideVecPolyEndGeom = 2;
|
|
constant constexpr float4 discardPt(0,0,-1e6,NAN);
|
|
|
|
// Performance version of wide vector shader
|
|
vertex ProjVertexTriWideVecPerf vertexTri_wideVecPerf(
|
|
thread const VertexTriWideVecB vert [[ stage_in ]],
|
|
constant Uniforms &uniforms [[ buffer(idWideVectorUniformsUBO) ]],
|
|
constant UniformWideVec &wideVec [[ buffer(idWideVectorUniformWideVecUBO) ]],
|
|
uint instanceID [[ instance_id ]],
|
|
constant VertexTriWideVecInstance *wideVecInsts [[ buffer(wideVectorUBOCount + 3) ]])
|
|
{
|
|
ProjVertexTriWideVecPerf outVert = {
|
|
.position = discardPt,
|
|
.roundJoin = false,
|
|
};
|
|
|
|
// Vertex index within the instance, 0-11
|
|
// Odd indexes are on the left, evens are on the right.
|
|
const int whichVert = (vert.index >> 16) & 0xffff;
|
|
// Polygon index within the segment. 0=Start cap, 1=body, 2=end cap
|
|
const int whichPoly = vert.index & 0xffff;
|
|
// Are we on the left edge, or the right?
|
|
const bool isLeft = (whichVert & 1);
|
|
// Are we on the starting end of the segment or the end?
|
|
const bool isEnd = (whichVert > 5);
|
|
|
|
// Track vertex for debugging
|
|
//outVert.whichVert = whichVert;
|
|
|
|
// Pull out the width and possibly calculate one
|
|
float w2 = wideVec.w2;
|
|
|
|
// w2 includes edge-blend, strokeWidth does not
|
|
const float strokeWidth = 2 * w2;
|
|
if (w2 > 0.0) {
|
|
w2 = w2 + wideVec.edge;
|
|
}
|
|
|
|
// Disable joins for narrow lines
|
|
auto joinType = (w2 >= 1) ? wideVec.join : WKSVertexLineJoinNone;
|
|
|
|
// Find the various instances representing center points.
|
|
// We need one behind and one ahead of us.
|
|
// previous segment
|
|
// | this segment
|
|
// | | next segment
|
|
// | | |
|
|
bool instValid[4] = { false, true, false, false };
|
|
VertexTriWideVecInstance inst[4] = { {}, wideVecInsts[instanceID], {}, {} };
|
|
|
|
if (inst[1].prev != -1 && joinType != WKSVertexLineJoinNone) {
|
|
inst[0] = wideVecInsts[inst[1].prev];
|
|
instValid[0] = true;
|
|
}
|
|
if (inst[1].next != -1) {
|
|
inst[2] = wideVecInsts[inst[1].next];
|
|
instValid[2] = true;
|
|
|
|
if (inst[2].next != -1 && joinType != WKSVertexLineJoinNone) {
|
|
inst[3] = wideVecInsts[inst[2].next];
|
|
instValid[3] = true;
|
|
}
|
|
} else {
|
|
// We need at least this and next
|
|
return outVert;
|
|
}
|
|
|
|
const auto capType = wideVec.cap;
|
|
const auto isStartCap = (whichPoly == WideVecPolyStartGeom && !instValid[0]);
|
|
const auto isEndCap = (whichPoly == WideVecPolyEndGeom && !instValid[3]);
|
|
|
|
// Butt is the default cap style, the line ends at the point.
|
|
if ((isStartCap || isEndCap) && capType == WhirlyKitShader::WKSVertexLineCapButt) {
|
|
return outVert;
|
|
}
|
|
|
|
// Figure out position on the screen for each center point.
|
|
// centers[1] represents the segment leading to the current point.
|
|
// centers[2] represents the segment leading from the current point.
|
|
// centers[X] = vector from centers[X-1] to centers[X]
|
|
CenterInfo centers[4];
|
|
for (unsigned int ii=0;ii<4;ii++) {
|
|
if (!instValid[ii]) {
|
|
continue;
|
|
}
|
|
centers[ii].screenPos = screenPos_MVP(uniforms, inst[ii].center);
|
|
}
|
|
|
|
const float2 screenScale(2.0/uniforms.frameSize.x,2.0/uniforms.frameSize.y); // ~(0.001,0.001)
|
|
|
|
// Calculate directions and normals. Done in isotropic coords to
|
|
// avoid skewing everything when later multiplying by `screenScale`.
|
|
for (unsigned int ii=1;ii<4;ii++) {
|
|
if (instValid[ii-1]) {
|
|
const float2 scaledDir = (centers[ii].screenPos - centers[ii-1].screenPos) / screenScale;
|
|
centers[ii].len = length(scaledDir);
|
|
centers[ii].nDir = normalize(scaledDir);
|
|
centers[ii].norm = float2(-centers[ii].nDir.y, centers[ii].nDir.x);
|
|
}
|
|
}
|
|
|
|
// Pull out the center line offset, or calculate one
|
|
float centerLine = wideVec.offset;
|
|
|
|
// Intersect on the left or right depending
|
|
const float interSgn = (whichVert & 1) ? 1 : -1;
|
|
|
|
// Do the offset intersection
|
|
bool intersectValid = false;
|
|
bool turningLeft = false;
|
|
const int interIdx = isEnd ? 1 : 0;
|
|
float2 offsetCenter = centers[interIdx + 1].screenPos + centers[2].norm * centerLine * screenScale;
|
|
float2 interPt(0, 0), realInterPt(0, 0);
|
|
float dotProd = 0, theta = 0, miterLength = 0;
|
|
|
|
// If we're on the far end of the body segment, we need this and the next two segments.
|
|
// Otherwise we need the previous, this, and the next segment.
|
|
if (instValid[interIdx] && instValid[interIdx+1] && instValid[interIdx+2]) {
|
|
|
|
// Don't even bother computing intersections for very acute angles or very small turns
|
|
dotProd = dot(centers[interIdx+1].nDir, centers[interIdx+2].nDir);
|
|
if (-wideVecMaxTurnThreshold < dotProd &&
|
|
dotProd < wideVecMaxTurnThreshold &&
|
|
abs(abs(dotProd) - 1) >= wideVecMinTurnThreshold) {
|
|
|
|
// Interior turn angle, both vectors are normalized.
|
|
theta = M_PI_F - acos(dotProd);
|
|
|
|
// "If the miter length divided by the stroke width exceeds the miterlimit then:
|
|
// miter: the join is converted to a bevel
|
|
// miter-clip: the miter is clipped at half the miter length from the intersection"
|
|
if (joinType == WKSVertexLineJoinMiter || joinType == WKSVertexLineJoinMiterClip) {
|
|
miterLength = abs(1 / sin(theta / 2));
|
|
if (miterLength > wideVec.miterLimit) {
|
|
if (joinType == WKSVertexLineJoinMiter) {
|
|
joinType = WKSVertexLineJoinBevel;
|
|
} else if (joinType == WKSVertexLineJoinMiterClip) {
|
|
miterLength = wideVec.miterLimit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Intersect the left or right sides of prev-this and this-next, plus offset
|
|
thread const CenterInfo &prev = centers[interIdx+0];
|
|
thread const CenterInfo &cur = centers[interIdx+1];
|
|
thread const CenterInfo &next = centers[interIdx+2];
|
|
const float2 edgeDist = screenScale * (interSgn * w2 + centerLine);
|
|
const float2 n0 = edgeDist * cur.norm;
|
|
const float2 n1 = edgeDist * next.norm;
|
|
const IntersectInfo interInfo = intersectLines(prev.screenPos + n0, cur.screenPos + n0,
|
|
cur.screenPos + n1, next.screenPos + n1);
|
|
if (interInfo.valid) {
|
|
const float c = wedge(next.screenPos - cur.screenPos, cur.screenPos - prev.screenPos);
|
|
turningLeft = (c < 0);
|
|
realInterPt = interPt = interInfo.interPt;
|
|
|
|
// Limit the distance to the smaller of half way back along the previous segment
|
|
// or half way forward along the next one to keep consecutive segments from colliding.
|
|
const float maxAdjDist = min(cur.len, next.len) / 2;
|
|
|
|
// If we're using offsets, we also need to know the point where the offset lines
|
|
// intersect, as the distance to the original intersection point isn't helpful.
|
|
// todo: this doesn't make sense for small angles, but what's the actual threshold?
|
|
if (centerLine != 0) {
|
|
const float2 cn0 = cur.norm * centerLine * screenScale;
|
|
const float2 cn1 = next.norm * centerLine * screenScale;
|
|
const IntersectInfo i2 = intersectLines(prev.screenPos + cn0, cur.screenPos + cn0,
|
|
cur.screenPos + cn1, next.screenPos + cn1);
|
|
if (i2.valid && length_squared((i2.interPt - offsetCenter)/screenScale) < maxAdjDist*maxAdjDist) {
|
|
offsetCenter = i2.interPt;
|
|
}
|
|
}
|
|
|
|
const float2 interVec = (interPt - offsetCenter) / screenScale;
|
|
const float interDist2 = length_squared(interVec);
|
|
const float maxClipDist2 = (maxAdjDist * wideVec.interClipLimit) *
|
|
(maxAdjDist * wideVec.interClipLimit);
|
|
|
|
// Limit intersection distance.
|
|
// For up to a multiple of that, adjust the intersection point back along its
|
|
// length to that limit, effectively narrowing the line instead of just giving up.
|
|
if (interDist2 <= maxAdjDist*maxAdjDist) {
|
|
intersectValid = true;
|
|
} else if (wideVec.interClipLimit > 0 && interDist2 <= maxClipDist2) {
|
|
interPt = offsetCenter + normalize(interVec) * sqrt(interDist2) * screenScale;
|
|
intersectValid = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Endcaps not used for miter case, discard them.
|
|
if ((joinType == WKSVertexLineJoinMiter || joinType == WKSVertexLineJoinMiterSimple) &&
|
|
whichPoly != WideVecPolyBodyGeom && !isStartCap && !isEndCap) {
|
|
return outVert;
|
|
}
|
|
|
|
// Get the color from the uniform:
|
|
float4 color = wideVec.color;
|
|
|
|
outVert.color = color;
|
|
outVert.w2 = w2;
|
|
outVert.edge = wideVec.edge;
|
|
|
|
// Work out the corner positions by extending the normals
|
|
const float2 realEdge = interSgn * w2 * screenScale;
|
|
const float2 corner = offsetCenter + centers[2].norm * realEdge;
|
|
const float turnSgn = turningLeft ? -1 : 1;
|
|
const bool isInsideEdge = (isLeft == turningLeft);
|
|
|
|
// Current corner is the default result for all points.
|
|
float2 pos = corner;
|
|
|
|
bool discardTri = false;
|
|
|
|
if (isStartCap || isEndCap) {
|
|
// Square extends beyond the point by half a width.
|
|
// Round uses the same geometry but rounds it off with the fragment shader.
|
|
if (capType == WhirlyKitShader::WKSVertexLineCapSquare ||
|
|
capType == WhirlyKitShader::WKSVertexLineCapRound) {
|
|
switch (whichVert) {
|
|
case 0: case 1: case 10: case 11:
|
|
pos = corner + centers[2].nDir * w2 * screenScale * (isEnd ? 1 : -1);
|
|
break;
|
|
}
|
|
}
|
|
if (capType == WhirlyKitShader::WKSVertexLineCapRound) {
|
|
outVert.roundJoin = true;
|
|
outVert.centerPos = offsetCenter / screenScale;
|
|
outVert.screenPos = pos / screenScale;
|
|
outVert.midDir = centers[2].nDir * (isEnd ? 1 : -1);
|
|
}
|
|
} else {
|
|
if (joinType == WKSVertexLineJoinNone || !intersectValid) {
|
|
// Trivial case, just use the corner
|
|
outVert.position = float4(pos, 0, 1);
|
|
return outVert;
|
|
}
|
|
|
|
// Since there is one, use the intersect point by default
|
|
pos = interPt;
|
|
|
|
// We'll need the corner on the opposite side for several things.
|
|
const float2 realOtherEdge = -interSgn * w2 * screenScale;
|
|
const float2 otherCorner = offsetCenter + centers[2].norm * realOtherEdge;
|
|
|
|
// For a bevel, use the intersect point for the inside of the turn, but not the outside.
|
|
// Round piggypacks on bevel.
|
|
if (joinType == WKSVertexLineJoinBevel || joinType == WKSVertexLineJoinRound) {
|
|
switch (whichVert) {
|
|
// Start cap, 0-3-1, 0-2-3
|
|
case 2: case 10:
|
|
discardTri = true; // Not using triangle #2
|
|
break;
|
|
// Merge inside corners to avoid overlap, use default outside corner.
|
|
case 0: case 3: case 8: case 9:
|
|
case 4: case 5: case 6: case 7:
|
|
pos = isInsideEdge ? interPt : corner;
|
|
break;
|
|
case 1: case 11: {
|
|
// Use the point halfway between the outside corner and the one on the opposite side.
|
|
const float2 norm = centers[(whichVert == 1) ? 1 : 3].norm;
|
|
const float2 c = offsetCenter + centers[2].norm * realEdge * turnSgn;
|
|
const float2 e = turnSgn * interSgn * w2 * screenScale;
|
|
pos = (offsetCenter + c + norm * e) / 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// For the round case, extend the center of the bevel out into a "tip," which will be
|
|
// turned into a round extension by the fragment shader. This isn't exactly right, but
|
|
// I think we need more geometry to do better.
|
|
// todo: fix texture Y-coords
|
|
if (joinType == WKSVertexLineJoinRound && whichPoly != WideVecPolyBodyGeom && !discardTri) {
|
|
outVert.roundJoin = true;
|
|
outVert.centerPos = offsetCenter / screenScale;
|
|
outVert.screenPos = pos / screenScale;
|
|
|
|
// Direction bisecting the turn toward the outside (right for a left turn)
|
|
const float2 mid = isEnd ? (centers[2].nDir - centers[3].nDir) :
|
|
(centers[1].nDir - centers[2].nDir);
|
|
outVert.midDir = normalize(mid / screenScale);
|
|
|
|
if (whichVert == 1 || whichVert == 11) {
|
|
// Extend the corner far enough to cover the necessary round-ness.
|
|
// This should probably be related to the turn angle, we're just fudging it.
|
|
const float extend = 2 * w2;
|
|
pos += outVert.midDir * extend * screenScale;
|
|
outVert.screenPos = pos / screenScale;
|
|
}
|
|
}
|
|
} else if (joinType == WKSVertexLineJoinMiterClip) {
|
|
// Direction of intersect point (bisecting the segment directions)
|
|
const float2 interVec = (realInterPt - offsetCenter) / screenScale;
|
|
const float2 interDir = normalize(interVec) * turnSgn * interSgn;
|
|
// And the perpendicular
|
|
const float2 interNorm = float2(-interDir.y, interDir.x);
|
|
// Distance from centerline intersect and edge intersect
|
|
const float interDist = length(interVec);
|
|
// "the miter is clipped by a line perpendicular to the line bisecting
|
|
// the angle between the two path segments at a distance of half the
|
|
// value of miter length from the intersection of the two path segments."
|
|
const float midExt = miterLength / 2 * strokeWidth + wideVec.edge;
|
|
|
|
switch (whichVert) {
|
|
// Start cap, 0-3-1, 0-2-3
|
|
case 0: case 8: {
|
|
// Out to the miter cap, then perpendicular to the line edge.
|
|
// The half-width of the clipped miter cap is the tangent of half the interior
|
|
// turn angle times the amount the original intersection extends beyond the cap.
|
|
const float miterEdgeW2 = (interDist - midExt) * tan(theta / 2);
|
|
pos = offsetCenter + screenScale * (interDir * midExt -
|
|
interNorm * miterEdgeW2 * turnSgn * (whichVert ? -1 : 1));
|
|
break;
|
|
}
|
|
case 1: case 10:
|
|
// Extend the turn bisector to the miter clip edge
|
|
pos = turningLeft ? (offsetCenter + interDir * midExt * screenScale) :
|
|
((whichVert == 1) ? corner : otherCorner);
|
|
break;
|
|
case 2: case 9:
|
|
// Extend segment endpoint outward along the intersection angle by the miter length
|
|
pos = turningLeft ? ((whichVert == 2) ? corner : otherCorner) :
|
|
(offsetCenter + interDir * midExt * screenScale);
|
|
break;
|
|
case 3: case 11:
|
|
// The edge intersect, or the one on the other side
|
|
pos = turningLeft ? interPt : (offsetCenter + (offsetCenter - interPt));
|
|
break;
|
|
// Body segment, 4-7-5, 4-6-7
|
|
// Merge inside corners to avoid overlap, use default outside corner.
|
|
case 4: case 5: case 6: case 7:
|
|
pos = isInsideEdge ? interPt : corner;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!discardTri) {
|
|
outVert.position = float4(pos, 0, 1);
|
|
}
|
|
|
|
return outVert;
|
|
}
|
|
|
|
// Fragment shader that takes the back of the globe into account
|
|
fragment float4 fragmentTri_wideVecPerf(
|
|
ProjVertexTriWideVecPerf vert [[stage_in]],
|
|
constant Uniforms &uniforms [[ buffer(idWideVectorUniformsUBO) ]])
|
|
{
|
|
float patternAlpha = 1.0;
|
|
|
|
// Reduce alpha of the "tip" for round joins to get a round look.
|
|
float roundAlpha = 1.0;
|
|
if (vert.roundJoin && dot(vert.screenPos - vert.centerPos, vert.midDir) > 0) {
|
|
const float r = length_squared(vert.screenPos - vert.centerPos) / vert.w2 / vert.w2;
|
|
roundAlpha = clamp((r > 0.95) ? (1 - r) * 20 : 1.0, 0.0, 1.0);
|
|
}
|
|
|
|
// Reduce alpha along the edges to get smooth blending.
|
|
const float edgeAlpha = (vert.edge > 0) ? clamp((1 - abs(vert.texCoord.x)) * vert.w2 / vert.edge, 0.0, 1.0) : 1.0;
|
|
|
|
return vert.color * float4(1,1,1,edgeAlpha * patternAlpha * roundAlpha);
|
|
}
|
|
|
|
)";
|
|
};
|
|
|
|
} // namespace shaders
|
|
} // namespace mbgl
|