Skip to content

Commit

Permalink
[edge-curve] Drafts @sigma/edge-curve package
Browse files Browse the repository at this point in the history
This commit drafts the new edge-curve renderer, based on previous work
by @Yomguithereal, in his sigma-experiments.
It compiles, but it does not work as intended yet.

TODO:
- Fix missing part of the curve
- Add custom canvas label renderer that follows the curvature
  • Loading branch information
jacomyal committed Mar 12, 2024
1 parent 3caade1 commit be1ae79
Show file tree
Hide file tree
Showing 16 changed files with 3,172 additions and 6 deletions.
13 changes: 12 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"preconstruct": {
"packages": [
"packages/sigma",
"packages/node-image"
"packages/node-image",
"packages/edge-curve"
]
}
}
2 changes: 2 additions & 0 deletions packages/edge-curve/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
4 changes: 4 additions & 0 deletions packages/edge-curve/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.gitignore
node_modules
src
tsconfig.json
49 changes: 49 additions & 0 deletions packages/edge-curve/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@sigma/edge-curve",
"version": "3.0.0-beta.1",
"description": "An edge program that renders edges as curves for sigma.js",
"main": "dist/sigma-edge-curve.cjs.js",
"module": "dist/sigma-edge-curve.esm.js",
"types": "dist/sigma-edge-curve.cjs.d.ts",
"files": [
"/dist"
],
"sideEffects": false,
"homepage": "https://www.sigmajs.org",
"bugs": "http://github.com/jacomyal/sigma.js/issues",
"repository": {
"type": "git",
"url": "http://github.com/jacomyal/sigma.js.git"
},
"keywords": [
"graph",
"graphology",
"sigma"
],
"contributors": [
{
"name": "Guillaume Plique",
"url": "http://github.com/Yomguithereal"
},
{
"name": "Alexis Jacomy",
"url": "http://github.com/jacomyal"
}
],
"license": "MIT",
"preconstruct": {
"entrypoints": [
"index.ts"
]
},
"peerDependencies": {
"sigma": ">=3.0.0-beta.10"
},
"exports": {
".": {
"module": "./dist/sigma-edge-curve.esm.js",
"import": "./dist/sigma-edge-curve.cjs.mjs",
"default": "./dist/sigma-edge-curve.cjs.js"
}
}
}
84 changes: 84 additions & 0 deletions packages/edge-curve/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Attributes } from "graphology-types";
import { EdgeProgram, ProgramInfo } from "sigma/rendering";
import { EdgeDisplayData, NodeDisplayData, RenderParams } from "sigma/types";
import { floatColor } from "sigma/utils";

import FRAGMENT_SHADER_SOURCE from "./shader-frag";
import VERTEX_SHADER_SOURCE from "./shader-vert";

const { UNSIGNED_BYTE, FLOAT } = WebGLRenderingContext;

const UNIFORMS = ["u_matrix", "u_sizeRatio", "u_dimensions", "u_pixelRatio"] as const;

const DEFAULT_EDGE_CURVATURE = 0.25;

export default class EdgeCurveProgram<
N extends Attributes = Attributes,
E extends Attributes = Attributes,
G extends Attributes = Attributes,
> extends EdgeProgram<(typeof UNIFORMS)[number], N, E, G> {
getDefinition() {
return {
VERTICES: 4,
VERTEX_SHADER_SOURCE,
FRAGMENT_SHADER_SOURCE,
METHOD: WebGLRenderingContext.TRIANGLES,
UNIFORMS,
ATTRIBUTES: [
{ name: "a_source", size: 2, type: FLOAT },
{ name: "a_target", size: 2, type: FLOAT },
{ name: "a_thickness", size: 1, type: FLOAT },
{ name: "a_curvature", size: 1, type: FLOAT },
{ name: "a_color", size: 4, type: UNSIGNED_BYTE, normalized: true },
{ name: "a_id", size: 4, type: UNSIGNED_BYTE, normalized: true },
],
CONSTANT_ATTRIBUTES: [
{ name: "a_current", size: 1, type: FLOAT }, // TODO: could optimize to bool
{ name: "a_direction", size: 1, type: FLOAT }, // TODO: could optimize to byte
],
CONSTANT_DATA: [
[0, 1],
[0, -1],
[1, 1],
[1, -1],
],
};
}

processVisibleItem(
edgeIndex: number,
startIndex: number,
sourceData: NodeDisplayData,
targetData: NodeDisplayData,
data: EdgeDisplayData & { curvature?: number },
) {
const thickness = data.size || 1;
const x1 = sourceData.x;
const y1 = sourceData.y;
const x2 = targetData.x;
const y2 = targetData.y;
const color = floatColor(data.color);
const curvature = typeof data.curvature === "number" ? data.curvature : DEFAULT_EDGE_CURVATURE;

const array = this.array;

// First point
array[startIndex++] = x1;
array[startIndex++] = y1;
array[startIndex++] = x2;
array[startIndex++] = y2;
array[startIndex++] = thickness;
array[startIndex++] = curvature;
array[startIndex++] = color;
array[startIndex++] = edgeIndex;
}

setUniforms(params: RenderParams, { gl, uniformLocations }: ProgramInfo): void {
const { u_matrix, u_pixelRatio, u_sizeRatio, u_dimensions } = uniformLocations;

gl.uniformMatrix3fv(u_matrix, false, params.matrix);
gl.uniform1f(u_pixelRatio, params.pixelRatio);
gl.uniform1f(u_sizeRatio, params.sizeRatio);
gl.uniform2f(u_dimensions, params.width * params.pixelRatio, params.height * params.pixelRatio);
}
}
47 changes: 47 additions & 0 deletions packages/edge-curve/src/shader-frag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// language=GLSL
const SHADER_SOURCE = /*glsl*/ `
precision mediump float;
varying vec4 v_color;
varying float v_thickness;
varying vec2 v_cpA;
varying vec2 v_cpB;
varying vec2 v_cpC;
float det(vec2 a, vec2 b) {
return a.x * b.y - b.x * a.y;
}
vec2 get_distance_vector(vec2 b0, vec2 b1, vec2 b2) {
float a = det(b0, b2), b = 2.0 * det(b1, b0), d = 2.0 * det(b2, b1);
float f = b * d - a * a;
vec2 d21 = b2 - b1, d10 = b1 - b0, d20 = b2 - b0;
vec2 gf = 2.0 * (b * d21 + d * d10 + a * d20);
gf = vec2(gf.y, -gf.x);
vec2 pp = -f * gf / dot(gf, gf);
vec2 d0p = b0 - pp;
float ap = det(d0p, d20), bp = 2.0 * det(d10, d0p);
float t = clamp((ap + bp) / (2.0 * a + b + d), 0.0, 1.0);
return mix(mix(b0, b1, t), mix(b1, b2, t), t);
}
float distToQuadraticBezierCurve(vec2 p, vec2 b0, vec2 b1, vec2 b2) {
return length(get_distance_vector(b0 - p, b1 - p, b2 - p));
}
const float epsilon = 0.7;
const vec4 transparent = vec4(0.0, 0.0, 0.0, 0.0);
void main(void) {
float dist = distToQuadraticBezierCurve(gl_FragCoord.xy, v_cpA, v_cpB, v_cpC);
if (dist < v_thickness + epsilon) {
float inCurve = 1.0 - smoothstep(v_thickness - epsilon, v_thickness + epsilon, dist);
gl_FragColor = inCurve * vec4(v_color.rgb * v_color.a, v_color.a);
} else {
gl_FragColor = transparent;
}
}
`;

export default SHADER_SOURCE;
87 changes: 87 additions & 0 deletions packages/edge-curve/src/shader-vert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// language=GLSL
const VERTEX_SHADER_SOURCE = /*glsl*/ `
attribute vec4 a_color;
attribute float a_direction;
attribute float a_thickness;
attribute vec2 a_source;
attribute vec2 a_target;
attribute float a_current;
attribute float a_curvature;
uniform mat3 u_matrix;
uniform float u_sizeRatio;
uniform float u_pixelRatio;
uniform vec2 u_dimensions;
varying vec4 v_color;
varying float v_thickness;
varying vec2 v_cpA;
varying vec2 v_cpB;
varying vec2 v_cpC;
const float bias = 255.0 / 254.0;
const float epsilon = 0.7;
const float minThickness = 0.3;
vec2 clipspaceToViewport(vec2 pos, vec2 dimensions) {
return vec2(
(pos.x + 1.0) * dimensions.x / 2.0,
(pos.y + 1.0) * dimensions.y / 2.0
);
}
vec2 viewportToClipspace(vec2 pos, vec2 dimensions) {
return vec2(
pos.x / dimensions.x * 2.0 - 1.0,
pos.y / dimensions.y * 2.0 - 1.0
);
}
void main() {
// Selecting the correct position
// Branchless "position = a_source if a_current == 1.0 else a_target"
vec2 position = a_source * max(0.0, a_current) + a_target * max(0.0, 1.0 - a_current);
position = (u_matrix * vec3(position, 1)).xy;
vec2 source = (u_matrix * vec3(a_source, 1)).xy;
vec2 target = (u_matrix * vec3(a_target, 1)).xy;
vec2 viewportPosition = clipspaceToViewport(position, u_dimensions);
vec2 viewportSource = clipspaceToViewport(source, u_dimensions);
vec2 viewportTarget = clipspaceToViewport(target, u_dimensions);
vec2 delta = viewportTarget.xy - viewportSource.xy;
float len = length(delta);
vec2 normal = vec2(-delta.y, delta.x) * a_direction;
vec2 unitNormal = normal / len;
float boundingBoxThickness = len * a_curvature;
float curveThickness = max(minThickness, a_thickness / 2.0 / u_sizeRatio * u_pixelRatio);
v_thickness = curveThickness;
v_cpA = viewportSource;
v_cpB = 0.5 * (viewportSource + viewportTarget) + unitNormal * a_direction * boundingBoxThickness;
v_cpC = viewportTarget;
vec2 viewportOffsetPosition = (
viewportPosition +
unitNormal * (boundingBoxThickness / 2.0 + curveThickness + epsilon) *
max(0.0, a_direction) // NOTE: cutting the bounding box in half to avoid overdraw
);
position = viewportToClipspace(viewportOffsetPosition, u_dimensions);
gl_Position = vec4(position, 0, 1);
#ifdef PICKING_MODE
// For picking mode, we use the ID as the color:
v_color = a_id;
#else
// For normal mode, we use the color:
v_color = a_color;
#endif
v_color.a *= bias;
}
`;

export default VERTEX_SHADER_SOURCE;
40 changes: 40 additions & 0 deletions packages/edge-curve/src/stories/AllFeatures.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Meta, StoryObj } from "@storybook/html";
import Graph from "graphology";
import Sigma from "sigma";

import EdgeCurveProgram from "../index.ts";
import data from "./data.json";
import "./stage.css";

const createPictogramsStage = () => {
const stage = document.createElement("div");
stage.classList.add("stage");

const graph = new Graph();
graph.import(data);

new Sigma(graph, stage, {
allowInvalidContainer: true,
defaultEdgeType: "curve",
edgeProgramClasses: {
curve: EdgeCurveProgram,
},
});

return stage;
};

const meta: Meta<typeof createPictogramsStage> = {
title: "edge-curve",
render: () => createPictogramsStage(),
parameters: {
layout: "fullscreen",
},
};

export default meta;
type Story = StoryObj<typeof createPictogramsStage>;

export const ComparisonExample: Story = {
name: "All features at once",
};
Loading

0 comments on commit be1ae79

Please sign in to comment.