Skip to content

Commit

Permalink
Improves picking implementation
Browse files Browse the repository at this point in the history
Details:
- Picking is now done on textures bound to existing contexts rather than
  on new contexts
- Picking is now pixel perfect, and respects z order of elements
- Replaces all existing enableEdge*Events by a unique enableEdgeEvents
  setting (that is boolean, and no more supports "debounce")
- Picking texture for edges is only drawn if the setting
  enableEdgeEvents is true
  • Loading branch information
jacomyal committed Nov 30, 2023
1 parent f09b94a commit 83941a8
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 94 deletions.
4 changes: 1 addition & 3 deletions examples/events/index.ts
Expand Up @@ -42,9 +42,7 @@ function logEvent(event: string, itemType: "node" | "edge" | "positions", item:

let hoveredEdge = null;
const renderer = new Sigma(graph, container, {
enableEdgeClickEvents: true,
enableEdgeWheelEvents: true,
enableEdgeHoverEvents: "debounce",
enableEdgeEvents: true,
edgeReducer(edge, data) {
const res = { ...data };
if (edge === hoveredEdge) res.color = "#cc0000";
Expand Down
6 changes: 3 additions & 3 deletions src/rendering/webgl/programs/common/edge.ts
Expand Up @@ -57,7 +57,7 @@ export abstract class EdgeProgram<Uniform extends string = string>
}

export interface EdgeProgramConstructor {
new (gl: WebGLRenderingContext, pickGl: WebGLRenderingContext | null, renderer: Sigma): AbstractEdgeProgram;
new (gl: WebGLRenderingContext, pickingBuffer: WebGLFramebuffer | null, renderer: Sigma): AbstractEdgeProgram;
}

/**
Expand All @@ -72,9 +72,9 @@ export function createEdgeCompoundProgram(programClasses: Array<EdgeProgramConst
return class EdgeCompoundProgram implements AbstractEdgeProgram {
programs: Array<AbstractEdgeProgram>;

constructor(gl: WebGLRenderingContext, pickGl: WebGLRenderingContext | null, renderer: Sigma) {
constructor(gl: WebGLRenderingContext, pickingBuffer: WebGLFramebuffer | null, renderer: Sigma) {
this.programs = programClasses.map((Program) => {
return new Program(gl, pickGl, renderer);
return new Program(gl, pickingBuffer, renderer);
});
}

Expand Down
6 changes: 3 additions & 3 deletions src/rendering/webgl/programs/common/node.ts
Expand Up @@ -40,7 +40,7 @@ export abstract class NodeProgram<Uniform extends string = string>
}

export interface NodeProgramConstructor {
new (gl: WebGLRenderingContext, pickGl: WebGLRenderingContext | null, renderer: Sigma): AbstractNodeProgram;
new (gl: WebGLRenderingContext, pickingBuffer: WebGLFramebuffer | null, renderer: Sigma): AbstractNodeProgram;
}

/**
Expand All @@ -57,9 +57,9 @@ export function createNodeCompoundProgram(
return class NodeCompoundProgram implements AbstractNodeProgram {
programs: NonEmptyArray<AbstractNodeProgram>;

constructor(gl: WebGLRenderingContext, pickGl: WebGLRenderingContext | null, renderer: Sigma) {
constructor(gl: WebGLRenderingContext, pickingBuffer: WebGLFramebuffer | null, renderer: Sigma) {
this.programs = programClasses.map((Program) => {
return new Program(gl, pickGl, renderer);
return new Program(gl, pickingBuffer, renderer);
}) as NonEmptyArray<AbstractNodeProgram>;
}

Expand Down
48 changes: 29 additions & 19 deletions src/rendering/webgl/programs/common/program.ts
Expand Up @@ -33,8 +33,10 @@ function getAttributesItemsCount(attrs: ProgramAttributeSpecification[]): number

export interface ProgramInfo<Uniform extends string = string> {
name: string;
enableAlphaBlending: boolean;
program: WebGLProgram;
gl: WebGLRenderingContext | WebGL2RenderingContext;
frameBuffer: WebGLFramebuffer | null;
buffer: WebGLBuffer;
constantBuffer: WebGLBuffer;
uniformLocations: Record<Uniform, WebGLUniformLocation>;
Expand Down Expand Up @@ -97,7 +99,7 @@ export abstract class Program<Uniform extends string = string> implements Abstra

constructor(
gl: WebGLRenderingContext | WebGL2RenderingContext,
pickGl: WebGLRenderingContext | WebGL2RenderingContext | null,
pickingBuffer: WebGLFramebuffer | null,
renderer: Sigma,
) {
// Reading and caching program definition
Expand All @@ -119,13 +121,14 @@ export abstract class Program<Uniform extends string = string> implements Abstra

// Members
this.renderer = renderer;
this.normalProgram = this.getProgramInfo("normal", gl, def.VERTEX_SHADER_SOURCE, def.FRAGMENT_SHADER_SOURCE);
this.pickProgram = pickGl
this.normalProgram = this.getProgramInfo("normal", gl, def.VERTEX_SHADER_SOURCE, def.FRAGMENT_SHADER_SOURCE, null);
this.pickProgram = pickingBuffer
? this.getProgramInfo(
"pick",
pickGl,
gl,
PICKING_PREFIX + def.VERTEX_SHADER_SOURCE,
PICKING_PREFIX + def.FRAGMENT_SHADER_SOURCE,
pickingBuffer,
)
: null;

Expand Down Expand Up @@ -155,10 +158,11 @@ export abstract class Program<Uniform extends string = string> implements Abstra
}

private getProgramInfo(
name: string,
name: "normal" | "pick",
gl: WebGLRenderingContext | WebGL2RenderingContext,
vertexShaderSource: string,
fragmentShaderSource: string,
frameBuffer: WebGLFramebuffer | null,
): ProgramInfo {
const def = this.getDefinition();

Expand Down Expand Up @@ -197,11 +201,13 @@ export abstract class Program<Uniform extends string = string> implements Abstra
return {
name,
program,
gl: gl,
gl,
frameBuffer,
buffer,
constantBuffer: constantBuffer || ({} as WebGLBuffer),
uniformLocations,
attributeLocations,
enableAlphaBlending: name !== "pick",
};
}

Expand Down Expand Up @@ -233,10 +239,6 @@ export abstract class Program<Uniform extends string = string> implements Abstra

gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
protected bind(): void {
this.bindProgram(this.normalProgram);
if (this.pickProgram) this.bindProgram(this.pickProgram);
}

private unbindProgram(program: ProgramInfo): void {
if (!this.isInstanced) {
Expand All @@ -246,10 +248,6 @@ export abstract class Program<Uniform extends string = string> implements Abstra
this.ATTRIBUTES.forEach((attr) => this.unbindAttribute(attr, program, true));
}
}
protected unbind(): void {
this.unbindProgram(this.normalProgram);
if (this.pickProgram) this.unbindProgram(this.pickProgram);
}

private bindAttribute(
attr: ProgramAttributeSpecification,
Expand Down Expand Up @@ -326,21 +324,33 @@ export abstract class Program<Uniform extends string = string> implements Abstra
abstract setUniforms(params: RenderParams, programInfo: ProgramInfo): void;

private renderProgram(params: RenderParams, programInfo: ProgramInfo): void {
const { gl, program } = programInfo;
const { gl, program, enableAlphaBlending } = programInfo;

if (enableAlphaBlending) gl.enable(gl.BLEND);
else gl.disable(gl.BLEND);

gl.useProgram(program);
this.setUniforms(params, programInfo);
this.drawWebGL(this.METHOD, programInfo);
}

render(params: RenderParams): void {
if (this.hasNothingToRender()) return;

this.bind();
this.bindProgram(this.normalProgram);
this.renderProgram(params, this.normalProgram);
if (this.pickProgram) this.renderProgram(params, this.pickProgram);
this.unbind();
this.unbindProgram(this.normalProgram);

if (this.pickProgram) {
this.bindProgram(this.pickProgram);
this.renderProgram(params, this.pickProgram);
this.unbindProgram(this.pickProgram);
}
}

drawWebGL(method: GLenum, { gl }: ProgramInfo): void {
drawWebGL(method: GLenum, { gl, frameBuffer }: ProgramInfo): void {
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);

if (!this.isInstanced) {
gl.drawArrays(method, 0, this.verticesCount);
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/rendering/webgl/programs/node.image.ts
Expand Up @@ -227,8 +227,8 @@ export default function getNodeImageProgram(): NodeProgramConstructor {
texture: WebGLTexture;
latestRenderParams?: RenderParams;

constructor(gl: WebGLRenderingContext, pickGl: WebGLRenderingContext | null, renderer: Sigma) {
super(gl, pickGl, renderer);
constructor(gl: WebGLRenderingContext, pickingBuffer: WebGLFramebuffer | null, renderer: Sigma) {
super(gl, pickingBuffer, renderer);

rebindTextureFns.push(() => {
if (this && this.rebindTexture) this.rebindTexture();
Expand Down
8 changes: 2 additions & 6 deletions src/settings.ts
Expand Up @@ -25,9 +25,7 @@ export interface Settings {
hideLabelsOnMove: boolean;
renderLabels: boolean;
renderEdgeLabels: boolean;
enableEdgeClickEvents: boolean;
enableEdgeWheelEvents: boolean;
enableEdgeHoverEvents: boolean | "debounce";
enableEdgeEvents: boolean;
// Component rendering
defaultNodeColor: string;
defaultNodeType: string;
Expand Down Expand Up @@ -70,9 +68,7 @@ export const DEFAULT_SETTINGS: Settings = {
hideLabelsOnMove: false,
renderLabels: true,
renderEdgeLabels: false,
enableEdgeClickEvents: false,
enableEdgeWheelEvents: false,
enableEdgeHoverEvents: false,
enableEdgeEvents: false,

// Component rendering
defaultNodeColor: "#999",
Expand Down

0 comments on commit 83941a8

Please sign in to comment.