Skip to content

Commit 656a784

Browse files
authored
Merge pull request #209 from MetaCell/feat/skeleton-node-border
feat: add node outline
2 parents 184ead3 + 7af0d27 commit 656a784

2 files changed

Lines changed: 42 additions & 7 deletions

File tree

src/skeleton/frontend.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,12 @@ const SELECTED_NODE_OUTLINE_MAX_WIDTH_3D = "7.0";
208208
// the min for typical nodes and scales up the ring for larger nodes.
209209
const SELECTED_NODE_OUTLINE_DIAMETER_FRACTION = "0.5";
210210

211+
const NODE_BORDER_OUTLINE_DIAMETER_FRACTION = "0.15";
212+
const NODE_BORDER_OUTLINE_MIN_WIDTH_2D = "1.0";
213+
const NODE_BORDER_OUTLINE_MAX_WIDTH_2D = "2.5";
214+
const NODE_BORDER_OUTLINE_MIN_WIDTH_3D = "1.0";
215+
const NODE_BORDER_OUTLINE_MAX_WIDTH_3D = "2.0";
216+
211217
interface VertexAttributeRenderInfo extends VertexAttributeInfo {
212218
name: string;
213219
webglDataType: number;
@@ -767,6 +773,7 @@ void emitDefault() {
767773
);
768774
builder.addUniform("highp float", "uNodeDiameter");
769775
let selectedOutlineWidthExpression = "0.0";
776+
let borderOutlineWidthExpression = "0.0";
770777
if (this.nodeIdAttributeIndex !== undefined) {
771778
builder.addUniform("highp vec3", "uSelectedNodeOutlineColor");
772779
builder.addUniform("highp int", "uSelectedNodeId");
@@ -781,6 +788,13 @@ void emitDefault() {
781788
? SELECTED_NODE_OUTLINE_MAX_WIDTH_2D
782789
: SELECTED_NODE_OUTLINE_MAX_WIDTH_3D;
783790
selectedOutlineWidthExpression = `(max(vSelectedNode, vHighlightedNode) * clamp(${SELECTED_NODE_OUTLINE_DIAMETER_FRACTION} * uNodeDiameter, ${selectedOutlineMinWidth}, ${selectedOutlineMaxWidth}))`;
791+
const borderOutlineMinWidth = this.targetIsSliceView
792+
? NODE_BORDER_OUTLINE_MIN_WIDTH_2D
793+
: NODE_BORDER_OUTLINE_MIN_WIDTH_3D;
794+
const borderOutlineMaxWidth = this.targetIsSliceView
795+
? NODE_BORDER_OUTLINE_MAX_WIDTH_2D
796+
: NODE_BORDER_OUTLINE_MAX_WIDTH_3D;
797+
borderOutlineWidthExpression = `(max(vSelectedNode, vHighlightedNode) * clamp(${NODE_BORDER_OUTLINE_DIAMETER_FRACTION} * uNodeDiameter, ${borderOutlineMinWidth}, ${borderOutlineMaxWidth}))`;
784798
}
785799
let vertexMain = `
786800
highp uint vertexIndex = uint(gl_InstanceID);
@@ -805,7 +819,8 @@ highp vec3 vertexPosition = readAttribute0(vertexIndex);
805819
emitCircle(
806820
uProjection * vec4(vertexPosition, 1.0),
807821
uNodeDiameter,
808-
${selectedOutlineWidthExpression}
822+
${selectedOutlineWidthExpression},
823+
${borderOutlineWidthExpression}
809824
);
810825
`;
811826
const segmentColorExpression = this.getSegmentColorExpression();
@@ -823,6 +838,9 @@ emitCircle(
823838
const borderColorExpression = hasNodeIdSelection
824839
? `mix(mix(renderColor, vec4(uSelectedNodeOutlineColor, renderColor.a), vSelectedNode), vec4(uHighlightedNodeOutlineColor, renderColor.a), vHighlightedNode)`
825840
: "renderColor";
841+
const borderOutlineColorExpression = hasNodeIdSelection
842+
? `mix(mix(renderColor, vec4(1.0, 1.0, 1.0, renderColor.a), vSelectedNode), vec4(0.0, 0.0, 0.0, renderColor.a), vHighlightedNode)`
843+
: "renderColor";
826844
builder.addFragmentCode(`
827845
vec4 segmentColor() {
828846
return getSegmentAppearance(${segmentExpression});
@@ -833,7 +851,8 @@ void emitRGBA(vec4 color) {
833851
if (alpha <= 0.0) discard;
834852
vec4 renderColor = vec4(color.rgb, alpha);
835853
vec4 borderColor = ${borderColorExpression};
836-
vec4 circleColor = getCircleColor(renderColor, borderColor);
854+
vec4 borderOutlineColor = ${borderOutlineColorExpression};
855+
vec4 circleColor = getCircleColor(renderColor, borderColor, borderOutlineColor);
837856
emit(vec4(circleColor.rgb * circleColor.a, circleColor.a), vPickID);
838857
}
839858
void emitRGB(vec3 color) {
@@ -871,14 +890,18 @@ void emitDefault() {
871890
const borderColorExpression = hasNodeIdSelection
872891
? `mix(mix(renderColor, vec4(uSelectedNodeOutlineColor, renderColor.a), vSelectedNode), vec4(uHighlightedNodeOutlineColor, renderColor.a), vHighlightedNode)`
873892
: "renderColor";
893+
const borderOutlineColorExpression = hasNodeIdSelection
894+
? `mix(mix(renderColor, vec4(1.0, 1.0, 1.0, renderColor.a), vSelectedNode), vec4(0.0, 0.0, 0.0, renderColor.a), vHighlightedNode)`
895+
: "renderColor";
874896
builder.addFragmentCode(`
875897
vec4 segmentColor() {
876898
return ${segmentColorExpression};
877899
}
878900
void emitRGBA(vec4 color) {
879901
vec4 renderColor = color;
880902
vec4 borderColor = ${borderColorExpression};
881-
vec4 circleColor = getCircleColor(renderColor, borderColor);
903+
vec4 borderOutlineColor = ${borderOutlineColorExpression};
904+
vec4 circleColor = getCircleColor(renderColor, borderColor, borderOutlineColor);
882905
emit(vec4(circleColor.rgb * circleColor.a, circleColor.a), vPickID);
883906
}
884907
void emitRGB(vec3 color) {

src/webgl/circles.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,22 +39,29 @@ export function defineCircleShader(
3939

4040
// 2-D position within circle quad, ranging from [-1, -1] to [1, 1].
4141
builder.addVarying("highp vec4", "vCircleCoord");
42+
// Normalized radius where the first border ends and the border outline begins.
43+
builder.addVarying("highp float", "vCircleBorderFraction");
4244
builder.addVertexCode(`
43-
void emitCircle(vec4 position, float diameter, float borderWidth) {
45+
void emitCircle(vec4 position, float diameter, float borderWidth, float borderOutlineWidth) {
4446
gl_Position = position;
45-
float totalDiameter = diameter + 2.0 * (borderWidth + uCircleParams.z);
47+
float totalDiameter = diameter + 2.0 * (borderWidth + borderOutlineWidth + uCircleParams.z);
4648
if (diameter == 0.0) totalDiameter = 0.0;
4749
vec2 circleCornerOffset = getQuadVertexPosition(vec2(-1.0, -1.0), vec2(1.0, 1.0));
4850
gl_Position.xy += circleCornerOffset * uCircleParams.xy * gl_Position.w * totalDiameter;
4951
vCircleCoord.xy = circleCornerOffset;
50-
if (borderWidth == 0.0) {
52+
if (borderWidth == 0.0 && borderOutlineWidth == 0.0) {
5153
vCircleCoord.z = totalDiameter;
5254
vCircleCoord.w = 1e-6;
55+
vCircleBorderFraction = totalDiameter;
5356
} else {
5457
vCircleCoord.z = diameter / totalDiameter;
58+
vCircleBorderFraction = (diameter + 2.0 * borderWidth) / totalDiameter;
5559
vCircleCoord.w = uCircleParams.z / totalDiameter;
5660
}
5761
}
62+
void emitCircle(vec4 position, float diameter, float borderWidth) {
63+
emitCircle(position, diameter, borderWidth, 0.0);
64+
}
5865
`);
5966
if (crossSectionFade) {
6067
builder.addFragmentCode(`
@@ -70,18 +77,23 @@ float getCircleAlphaMultiplier() {
7077
`);
7178
}
7279
builder.addFragmentCode(`
73-
vec4 getCircleColor(vec4 interiorColor, vec4 borderColor) {
80+
vec4 getCircleColor(vec4 interiorColor, vec4 borderColor, vec4 borderOutlineColor) {
7481
float radius = length(vCircleCoord.xy);
7582
if (radius > 1.0) {
7683
discard;
7784
}
7885
7986
float borderColorFraction = clamp((radius - vCircleCoord.z) / vCircleCoord.w, 0.0, 1.0);
87+
float outlineColorFraction = clamp((radius - vCircleBorderFraction) / vCircleCoord.w, 0.0, 1.0);
8088
float feather = clamp((1.0 - radius) / vCircleCoord.w, 0.0, 1.0);
8189
vec4 color = mix(interiorColor, borderColor, borderColorFraction);
90+
color = mix(color, borderOutlineColor, outlineColorFraction);
8291
8392
return vec4(color.rgb, color.a * feather * getCircleAlphaMultiplier());
8493
}
94+
vec4 getCircleColor(vec4 interiorColor, vec4 borderColor) {
95+
return getCircleColor(interiorColor, borderColor, borderColor);
96+
}
8597
`);
8698
}
8799

0 commit comments

Comments
 (0)