<NME> circles.ts
<BEF> /**
 * @license
 * Copyright 2017 Google Inc.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file Facilities for drawing circles in WebGL as quads (triangle fan).
 */

import {drawQuads, glsl_getQuadVertexPosition, VERTICES_PER_QUAD} from 'neuroglancer/webgl/quad';
import {ShaderBuilder, ShaderProgram} from 'neuroglancer/webgl/shader';

export const VERTICES_PER_CIRCLE = VERTICES_PER_QUAD;

export function defineCircleShader(builder: ShaderBuilder, crossSectionFade: boolean) {
  builder.addVertexCode(glsl_getQuadVertexPosition);
  // x and y components: The x and y radii of the point in normalized device coordinates.
  // z component: Starting point of border from [0, 1]..
  // w component: Fraction of total radius that is feathered.
  builder.addUniform('highp vec3', 'uCircleParams');

  // 2-D position within circle quad, ranging from [-1, -1] to [1, 1].
  builder.addVarying('highp vec4', 'vCircleCoord');
  builder.addVertexCode(`
void emitCircle(vec4 position, float diameter, float borderWidth) {
  gl_Position = position;
  float totalDiameter = diameter + 2.0 * (borderWidth + uCircleParams.z);
  if (diameter == 0.0) totalDiameter = 0.0;
  vec2 circleCornerOffset = getQuadVertexPosition(vec2(-1.0, -1.0), vec2(1.0, 1.0));
  gl_Position.xy += circleCornerOffset * uCircleParams.xy * gl_Position.w * totalDiameter;
  vCircleCoord.xy = circleCornerOffset;
  vCircleCoord.z = diameter / totalDiameter;
  vCircleCoord.w = uCircleParams.z / totalDiameter;
}
`);
  if (crossSectionFade) {
    builder.addFragmentCode(`
float getCircleAlphaMultiplier() {
  return 1.0 - 2.0 * abs(0.5 - gl_FragCoord.z);
}
`);
  } else {
    builder.addFragmentCode(`
float getCircleAlphaMultiplier() {
  return 1.0;
}
`);
  }
  builder.addFragmentCode(`
vec4 getCircleColor(vec4 interiorColor, vec4 borderColor) {
  float radius = length(vCircleCoord.xy);
  if (radius > 1.0) {
    discard;
  }

  float borderColorFraction = clamp((radius - vCircleCoord.z) / vCircleCoord.w, 0.0, 1.0);
  float feather = clamp((1.0 - radius) / vCircleCoord.w, 0.0, 1.0);
  vec4 color = mix(interiorColor, borderColor, borderColorFraction);

  return vec4(color.rgb, color.a * feather * getCircleAlphaMultiplier());
}
`);
}

export function initializeCircleShader(
    shader: ShaderProgram, projectionParameters: {width: number, height: number},
    options: {featherWidthInPixels: number}) {
  const {gl} = shader;
  gl.uniform3f(
      shader.uniform('uCircleParams'), 1 / projectionParameters.width,
      1 / projectionParameters.height, Math.max(1e-6, options.featherWidthInPixels));
}

export function drawCircles(
    gl: WebGL2RenderingContext, circlesPerInstance: number, numInstances: number) {
  drawQuads(gl, circlesPerInstance, numInstances);
}

<MSG> fix(annotations): fix display of circles with borderWidth=0

Previously, due to feathering, even with borderWidth=0, the border
color would be shown in the feather region, which is particularly
noticable if there are many circles displayed on top of each other.
This change disables feathering if borderWidth=0 in order to prevent
that artifact.

<DFF> @@ -40,8 +40,13 @@ void emitCircle(vec4 position, float diameter, float borderWidth) {
   vec2 circleCornerOffset = getQuadVertexPosition(vec2(-1.0, -1.0), vec2(1.0, 1.0));
   gl_Position.xy += circleCornerOffset * uCircleParams.xy * gl_Position.w * totalDiameter;
   vCircleCoord.xy = circleCornerOffset;
-  vCircleCoord.z = diameter / totalDiameter;
-  vCircleCoord.w = uCircleParams.z / totalDiameter;
+  if (borderWidth == 0.0) {
+    vCircleCoord.z = totalDiameter;
+    vCircleCoord.w = 1e-6;
+  } else {
+    vCircleCoord.z = diameter / totalDiameter;
+    vCircleCoord.w = uCircleParams.z / totalDiameter;
+  }
 }
 `);
   if (crossSectionFade) {

{'commit': 'diff --git a/src/neuroglancer/webgl/circles.ts b/src/neuroglancer/webgl/circles.ts\nindex 9ade21f..6a12588 100644\n--- a/src/neuroglancer/webgl/circles.ts\n+++ b/src/neuroglancer/webgl/circles.ts\n@@ -40,8 +40,13 @@ void emitCircle(vec4 position, float diameter, float borderWidth) {\n   vec2 circleCornerOffset = getQuadVertexPosition(vec2(-1.0, -1.0), vec2(1.0, 1.0));\n   gl_Position.xy += circleCornerOffset * uCircleParams.xy * gl_Position.w * totalDiameter;\n   vCircleCoord.xy = circleCornerOffset;\n-  vCircleCoord.z = diameter / totalDiameter;\n-  vCircleCoord.w = uCircleParams.z / totalDiameter;\n+  if (borderWidth == 0.0) {\n+    vCircleCoord.z = totalDiameter;\n+    vCircleCoord.w = 1e-6;\n+  } else {\n+    vCircleCoord.z = diameter / totalDiameter;\n+    vCircleCoord.w = uCircleParams.z / totalDiameter;\n+  }\n }\n `);\n   if (crossSectionFade) {\n', 'commit_subject': 'fix(annotations): fix display of circles with borderWidth=0', 'repo_name': 'google/neuroglancer', 'committer': {'name': 'Jeremy Maitin-Shepard', 'email': 'e349c33eb9031b46b70f51528427a078cdb06294@google.com', 'time_sec': '1620251283', 'tz_offset': '-420', 'date': {'seconds': '1620251283'}}, 'commit_tree': '95909e86d966e362770a93fb03a34759292daac8', 'commit_message': 'fix(annotations): fix display of circles with borderWidth=0\n\nPreviously, due to feathering, even with borderWidth=0, the border\ncolor would be shown in the feather region, which is particularly\nnoticable if there are many circles displayed on top of each other.\nThis change disables feathering if borderWidth=0 in order to prevent\nthat artifact.\n', 'language_name': 'Go', 'license': 'apache-2.0', 'hunks': '@@ -40,8 +40,13 @@ void emitCircle(vec4 position, float diameter, float borderWidth) {\n   vec2 circleCornerOffset = getQuadVertexPosition(vec2(-1.0, -1.0), vec2(1.0, 1.0));\n   gl_Position.xy += circleCornerOffset * uCircleParams.xy * gl_Position.w * totalDiameter;\n   vCircleCoord.xy = circleCornerOffset;\n-  vCircleCoord.z = diameter / totalDiameter;\n-  vCircleCoord.w = uCircleParams.z / totalDiameter;\n+  if (borderWidth == 0.0) {\n+    vCircleCoord.z = totalDiameter;\n+    vCircleCoord.w = 1e-6;\n+  } else {\n+    vCircleCoord.z = diameter / totalDiameter;\n+    vCircleCoord.w = uCircleParams.z / totalDiameter;\n+  }\n }\n `);\n   if (crossSectionFade) {\n', 'addition_count': 7, 'deletion_count': 2, 'src_file': 'a/src/neuroglancer/webgl/circles.ts', 'tgt_file': 'b/src/neuroglancer/webgl/circles.ts', 'file_extension': '.ts', 'before_file': '/**\n * @license\n * Copyright 2017 Google Inc.\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @file Facilities for drawing circles in WebGL as quads (triangle fan).\n */\n\nimport {drawQuads, glsl_getQuadVertexPosition, VERTICES_PER_QUAD} from \'neuroglancer/webgl/quad\';\nimport {ShaderBuilder, ShaderProgram} from \'neuroglancer/webgl/shader\';\n\nexport const VERTICES_PER_CIRCLE = VERTICES_PER_QUAD;\n\nexport function defineCircleShader(builder: ShaderBuilder, crossSectionFade: boolean) {\n  builder.addVertexCode(glsl_getQuadVertexPosition);\n  // x and y components: The x and y radii of the point in normalized device coordinates.\n  // z component: Starting point of border from [0, 1]..\n  // w component: Fraction of total radius that is feathered.\n  builder.addUniform(\'highp vec3\', \'uCircleParams\');\n\n  // 2-D position within circle quad, ranging from [-1, -1] to [1, 1].\n  builder.addVarying(\'highp vec4\', \'vCircleCoord\');\n  builder.addVertexCode(`\nvoid emitCircle(vec4 position, float diameter, float borderWidth) {\n  gl_Position = position;\n  float totalDiameter = diameter + 2.0 * (borderWidth + uCircleParams.z);\n  if (diameter == 0.0) totalDiameter = 0.0;\n  vec2 circleCornerOffset = getQuadVertexPosition(vec2(-1.0, -1.0), vec2(1.0, 1.0));\n  gl_Position.xy += circleCornerOffset * uCircleParams.xy * gl_Position.w * totalDiameter;\n  vCircleCoord.xy = circleCornerOffset;\n  vCircleCoord.z = diameter / totalDiameter;\n  vCircleCoord.w = uCircleParams.z / totalDiameter;\n}\n`);\n  if (crossSectionFade) {\n    builder.addFragmentCode(`\nfloat getCircleAlphaMultiplier() {\n  return 1.0 - 2.0 * abs(0.5 - gl_FragCoord.z);\n}\n`);\n  } else {\n    builder.addFragmentCode(`\nfloat getCircleAlphaMultiplier() {\n  return 1.0;\n}\n`);\n  }\n  builder.addFragmentCode(`\nvec4 getCircleColor(vec4 interiorColor, vec4 borderColor) {\n  float radius = length(vCircleCoord.xy);\n  if (radius > 1.0) {\n    discard;\n  }\n\n  float borderColorFraction = clamp((radius - vCircleCoord.z) / vCircleCoord.w, 0.0, 1.0);\n  float feather = clamp((1.0 - radius) / vCircleCoord.w, 0.0, 1.0);\n  vec4 color = mix(interiorColor, borderColor, borderColorFraction);\n\n  return vec4(color.rgb, color.a * feather * getCircleAlphaMultiplier());\n}\n`);\n}\n\nexport function initializeCircleShader(\n    shader: ShaderProgram, projectionParameters: {width: number, height: number},\n    options: {featherWidthInPixels: number}) {\n  const {gl} = shader;\n  gl.uniform3f(\n      shader.uniform(\'uCircleParams\'), 1 / projectionParameters.width,\n      1 / projectionParameters.height, Math.max(1e-6, options.featherWidthInPixels));\n}\n\nexport function drawCircles(\n    gl: WebGL2RenderingContext, circlesPerInstance: number, numInstances: number) {\n  drawQuads(gl, circlesPerInstance, numInstances);\n}\n', 'message': 'fix(annotations): fix display of circles with borderWidth=0\n\nPreviously, due to feathering, even with borderWidth=0, the border\ncolor would be shown in the feather region, which is particularly\nnoticable if there are many circles displayed on top of each other.\nThis change disables feathering if borderWidth=0 in order to prevent\nthat artifact.\n', 'author_name': 'Jeremy Maitin-Shepard', 'lang': 'ts'}