Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ node_modules/

# IDEs and editors
.idea/
.vscode/

# Optional npm cache directory
.npm
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,4 @@
"main"
]
}
}
}
14 changes: 14 additions & 0 deletions src/glsl.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
declare module '*.glsl' {
const value: string;
export default value;
}

declare module '*.vert' {
const value: string;
export default value;
}

declare module '*.frag' {
const value: string;
export default value;
}
151 changes: 151 additions & 0 deletions src/renderer/webgl/shaders/edge/edge.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#version 300 es

precision highp float;

in vec2 vWorldPos;
in vec2 vStart;
in vec2 vEnd;
in vec2 vControl;
in float vHalfWidth;
in float vLoopbackRadius;
in float vArrowSize;
in vec2 vArrowTip;
in vec2 vArrowDir;
in vec4 vColor;
in vec4 vShadowColor;
in float vShadowSize;
in vec2 vShadowOffset;
flat in int vEdgeType;

uniform bool uSimpleMode;

out vec4 fragColor;

float sdSegment(vec2 p, vec2 a, vec2 b) {
vec2 pa = p - a;
vec2 ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h);
}

float sdBezier(vec2 pos, vec2 A, vec2 B, vec2 C) {
vec2 a = B - A;
vec2 b = A - 2.0 * B + C;
vec2 c = a * 2.0;
vec2 d = A - pos;

float kk = 1.0 / max(dot(b, b), 0.0001);
float kx = kk * dot(a, b);
float ky = kk * (2.0 * dot(a, a) + dot(d, b)) / 3.0;
float kz = kk * dot(d, a);

float p = ky - kx * kx;
float q = kx * (2.0 * kx * kx - 3.0 * ky) + kz;
float p3 = p * p * p;
float q2 = q * q;
float h = q2 + 4.0 * p3;

float res;
if (h >= 0.0) {
h = sqrt(h);
vec2 x = (vec2(h, -h) - q) / 2.0;
vec2 uv = sign(x) * pow(abs(x), vec2(1.0 / 3.0));
float t = clamp(uv.x + uv.y - kx, 0.0, 1.0);
vec2 qo = d + (c + b * t) * t;
res = dot(qo, qo);
} else {
float z = sqrt(-p);
float v = acos(q / (p * z * 2.0)) / 3.0;
float m = cos(v);
float n = sin(v) * 1.732050808;
vec3 t = clamp(vec3(m + m, -n - m, n - m) * z - kx, 0.0, 1.0);
vec2 qx = d + (c + b * t.x) * t.x;
float dx = dot(qx, qx);
vec2 qy = d + (c + b * t.y) * t.y;
float dy = dot(qy, qy);
res = min(dx, dy);
}

return sqrt(res);
}

float sdArrow(vec2 p, vec2 tip, vec2 dir, float size) {
if (size <= 0.0) return 1e6;

vec2 perp = vec2(-dir.y, dir.x);
vec2 rel = p - tip;
float along = dot(rel, -dir);
float across = dot(rel, perp);

if (along < 0.0) return length(rel);
if (along > size) {
float hw = size * 0.4;
float closest = clamp(across, -hw, hw);
vec2 pt = tip - dir * size + perp * closest;
return length(p - pt);
}

float halfW = (along / size) * size * 0.4;
float d = abs(across) - halfW;
return d;
}

void main() {
if (uSimpleMode && vEdgeType == 0) {
fragColor = vColor;
return;
}

// Edge SDF: type-based dispatch (always needed).
float dist;
if (vEdgeType == 0) {
dist = sdSegment(vWorldPos, vStart, vEnd);
} else if (vEdgeType == 1) {
dist = sdBezier(vWorldPos, vStart, vControl, vEnd);
} else {
dist = abs(length(vWorldPos - vControl) - vLoopbackRadius);
}

float edgeSdf = dist - vHalfWidth;
float combinedSdf = edgeSdf;

if (vArrowSize > 0.0) {
float arrowDist = sdArrow(vWorldPos, vArrowTip, vArrowDir, vArrowSize);
combinedSdf = min(edgeSdf, arrowDist);
}

float shadowAlpha = 0.0;
if (vShadowSize > 0.0) {
vec2 shadowPos = vWorldPos - vShadowOffset;
float shadowDist;
if (vEdgeType == 0) {
shadowDist = sdSegment(shadowPos, vStart, vEnd);
} else if (vEdgeType == 1) {
shadowDist = sdBezier(shadowPos, vStart, vControl, vEnd);
} else {
shadowDist = abs(length(shadowPos - vControl) - vLoopbackRadius);
}
float shadowArrowDist = vArrowSize > 0.0
? sdArrow(shadowPos, vArrowTip, vArrowDir, vArrowSize)
: 1.0e6;
float shadowCombined = min(shadowDist - vHalfWidth, shadowArrowDist);
float t = max(shadowCombined, 0.0) / vShadowSize;
shadowAlpha = exp(-t * t * 1.5) * 0.5 * vShadowColor.a;
}

float aa = fwidth(combinedSdf);
float edgeAlpha = 1.0 - smoothstep(-aa, aa, combinedSdf);
vec4 edgeColor = vColor;
edgeColor.a *= edgeAlpha;

float finalAlpha = edgeColor.a + shadowAlpha * (1.0 - edgeColor.a);

if (finalAlpha < 0.001) discard;

if (shadowAlpha > 0.0) {
vec3 finalRGB = (edgeColor.rgb * edgeColor.a + vShadowColor.rgb * shadowAlpha * (1.0 - edgeColor.a)) / finalAlpha;
fragColor = vec4(finalRGB, finalAlpha);
} else {
fragColor = edgeColor;
}
}
94 changes: 94 additions & 0 deletions src/renderer/webgl/shaders/edge/edge.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#version 300 es

precision highp float;

in vec2 aQuadPosition;

in vec2 aStart;
in vec2 aEnd;
in vec2 aControl;
in float aWidth;
in float aEdgeType;
in float aLoopbackRadius;
in float aArrowSize;
in vec2 aArrowTip;
in vec2 aArrowDir;
in vec4 aColor;
in vec4 aShadowColor;
in float aShadowSize;
in float aShadowOffsetX;
in float aShadowOffsetY;

uniform vec2 uResolution;
uniform vec2 uTranslation;
uniform float uScale;
uniform vec2 uOriginOffset;

out vec2 vWorldPos;
out vec2 vStart;
out vec2 vEnd;
out vec2 vControl;
out float vHalfWidth;
out float vLoopbackRadius;
out float vArrowSize;
out vec2 vArrowTip;
out vec2 vArrowDir;
out vec4 vColor;
out vec4 vShadowColor;
out float vShadowSize;
out vec2 vShadowOffset;
flat out int vEdgeType;

void main() {
vEdgeType = int(aEdgeType + 0.5);
vStart = aStart;
vEnd = aEnd;
vControl = aControl;
float effectiveWidth = max(aWidth, 1.0 / uScale);
vHalfWidth = effectiveWidth * 0.5;
vLoopbackRadius = aLoopbackRadius;
vArrowSize = aArrowSize;
vArrowTip = aArrowTip;
vArrowDir = aArrowDir;
vColor = aColor;
vShadowColor = aShadowColor;
vShadowSize = aShadowSize;
vShadowOffset = vec2(aShadowOffsetX, aShadowOffsetY);

float pad = vHalfWidth + aShadowSize + abs(aShadowOffsetX) + abs(aShadowOffsetY);

vec2 worldPos;

if (vEdgeType == 0) {
vec2 dir = aEnd - aStart;
float len = length(dir);
vec2 unitDir = dir / max(len, 0.0001);
vec2 perp = vec2(-unitDir.y, unitDir.x);
float totalHalf = pad + aArrowSize;
vec2 midpoint = (aStart + aEnd) * 0.5;
worldPos = midpoint
+ unitDir * (len * 0.5 + totalHalf) * aQuadPosition.x
+ perp * totalHalf * aQuadPosition.y;
} else if (vEdgeType == 1) {
float margin = pad + aArrowSize;
vec2 bboxMin = min(min(aStart, aEnd), aControl) - margin;
vec2 bboxMax = max(max(aStart, aEnd), aControl) + margin;
vec2 center = (bboxMin + bboxMax) * 0.5;
vec2 halfSize = (bboxMax - bboxMin) * 0.5;
worldPos = center + aQuadPosition * halfSize;
} else {
float margin = pad + aArrowSize;
vec2 ctr = aControl;
float r = aLoopbackRadius;
vec2 bboxMin = min(ctr - (r + margin), aStart - margin);
vec2 bboxMax = max(ctr + (r + margin), aStart + margin);
vec2 center = (bboxMin + bboxMax) * 0.5;
vec2 halfSize = (bboxMax - bboxMin) * 0.5;
worldPos = center + aQuadPosition * halfSize;
}

vWorldPos = worldPos;
vec2 screenPos = (worldPos + uOriginOffset) * uScale + uTranslation;
vec2 clip = (screenPos / uResolution) * 2.0 - 1.0;
gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
}
15 changes: 15 additions & 0 deletions src/renderer/webgl/shaders/label/label.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#version 300 es

precision highp float;

uniform sampler2D uAtlas;

in vec2 vAtlasUV;

out vec4 fragColor;

void main() {
vec4 texel = texture(uAtlas, vAtlasUV);
if (texel.a < 0.01) discard;
fragColor = texel;
}
25 changes: 25 additions & 0 deletions src/renderer/webgl/shaders/label/label.vert
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#version 300 es

in vec2 aQuadPosition;

in vec2 aLabelCenter;
in vec2 aLabelSize;
in vec2 aLabelUV0;
in vec2 aLabelUV1;

uniform vec2 uResolution;
uniform vec2 uTranslation;
uniform float uScale;
uniform vec2 uOriginOffset;

out vec2 vAtlasUV;

void main() {
vec2 worldPos = aLabelCenter + aQuadPosition * aLabelSize;
vec2 screenPos = (worldPos + uOriginOffset) * uScale + uTranslation;
vec2 clip = (screenPos / uResolution) * 2.0 - 1.0;
gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);

vec2 uv01 = aQuadPosition * 0.5 + 0.5;
vAtlasUV = mix(aLabelUV0, aLabelUV1, uv01);
}
Loading
Loading