Skip to content

Commit

Permalink
picking working for edges
Browse files Browse the repository at this point in the history
  • Loading branch information
mikekucera committed Aug 26, 2024
1 parent 968dfe2 commit e456278
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 64 deletions.
37 changes: 32 additions & 5 deletions src/extensions/renderer/canvas/webgl/drawing-edges-webgl.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { RENDER_TARGET } from './drawing-redraw-webgl';
import * as util from './webgl-util';
import { mat3 } from 'gl-matrix';

Expand Down Expand Up @@ -27,11 +28,13 @@ export class EdgeDrawing {
this.maxInstances = opts.webglBatchSize;
this.bgColor = opts.bgColor;

this.program = this.createShaderProgram();
this.program = this.createShaderProgram(RENDER_TARGET.SCREEN);
this.pickingProgram = this.createShaderProgram(RENDER_TARGET.PICKING);

this.vao = this.createVAO();
}

createShaderProgram() {
createShaderProgram(renderTarget) {
// see https://wwwtyro.net/2019/11/18/instanced-lines.html
const { gl } = this;

Expand All @@ -44,6 +47,8 @@ export class EdgeDrawing {
in vec2 aPosition;
in int aVertType;
in vec4 aIndex;
// lines
in vec4 aSourceTarget;
in float aLineWidth;
Expand All @@ -57,6 +62,7 @@ export class EdgeDrawing {
in mat3 aTargetArrowTransform;
out vec4 vColor;
flat out vec4 vIndex;
flat out int vVertType;
void main(void) {
Expand All @@ -81,6 +87,8 @@ export class EdgeDrawing {
gl_Position = vec4(2.0, 0.0, 0.0, 1.0); // discard vertex by putting it outside webgl clip space
vColor = vec4(0.0, 0.0, 0.0, 0.0);
}
vIndex = aIndex;
vVertType = aVertType;
}
`;
Expand All @@ -91,6 +99,7 @@ export class EdgeDrawing {
uniform vec4 uBGColor;
in vec4 vColor;
flat in vec4 vIndex;
flat in int vVertType;
out vec4 outColor;
Expand All @@ -103,6 +112,15 @@ export class EdgeDrawing {
} else {
outColor = vColor;
}
${ renderTarget.picking ?
` if(outColor.a == 0.0)
outColor = vec4(0.0, 0.0, 0.0, 0.0);
else
outColor = vIndex;
`
: ''
}
}
`;

Expand All @@ -114,6 +132,7 @@ export class EdgeDrawing {
program.aPosition = gl.getAttribLocation(program, 'aPosition');
program.aVertType = gl.getAttribLocation(program, 'aVertType');

program.aIndex = gl.getAttribLocation(program, 'aIndex');
program.aSourceTarget = gl.getAttribLocation(program, 'aSourceTarget');
program.aLineWidth = gl.getAttribLocation(program, 'aLineWidth');
program.aLineColor = gl.getAttribLocation(program, 'aLineColor');
Expand Down Expand Up @@ -164,6 +183,7 @@ export class EdgeDrawing {
util.createBufferStaticDraw(gl, 'int', program.aVertType, vertTypes);

const n = this.maxInstances;
this.indexBuffer = util.createBufferDynamicDraw(gl, n, 'vec4', program.aIndex);
this.sourceTargetBuffer = util.createBufferDynamicDraw(gl, n, 'vec4', program.aSourceTarget);
this.lineWidthBuffer = util.createBufferDynamicDraw(gl, n, 'float', program.aLineWidth);
this.lineColorBuffer = util.createBufferDynamicDraw(gl, n, 'vec4' , program.aLineColor);
Expand Down Expand Up @@ -218,16 +238,17 @@ export class EdgeDrawing {
}
}

startFrame(panZoomMatrix, debugInfo) {
startFrame(panZoomMatrix, debugInfo, renderTarget = RENDER_TARGET.SCREEN) {
this.panZoomMatrix = panZoomMatrix
this.debugInfo = debugInfo;
this.renderTarget = renderTarget;
}

startBatch() {
this.instanceCount = 0;
}

draw(edge) {
draw(edge, index) {
// edge points and arrow angles etc are calculated by the base renderer and cached in the rscratch object
const rs = edge._private.rscratch;
const i = this.instanceCount;
Expand All @@ -247,6 +268,7 @@ export class EdgeDrawing {

const lineColor = util.toWebGLColor(color, opacity);

this.indexBuffer.setData(util.indexToVec4(index), i);
this.sourceTargetBuffer.setData([sx, sy, tx, ty], i);
this.lineWidthBuffer.setData([width], i);
this.lineColorBuffer.setData(lineColor, i);
Expand Down Expand Up @@ -279,14 +301,19 @@ export class EdgeDrawing {
}

endBatch() {
const { gl, program, vao, vertexCount, instanceCount: count } = this;
const { gl, vao, vertexCount, instanceCount: count } = this;
if(count === 0)
return;

const program = this.renderTarget.picking
? this.pickingProgram
: this.program;

gl.useProgram(program);
gl.bindVertexArray(vao);

// buffer the attribute data
this.indexBuffer.bufferSubData(count);
this.sourceTargetBuffer.bufferSubData(count);
this.lineWidthBuffer.bufferSubData(count);
this.lineColorBuffer.bufferSubData(count);
Expand Down
27 changes: 13 additions & 14 deletions src/extensions/renderer/canvas/webgl/drawing-nodes-webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,20 +218,19 @@ export class NodeDrawing {
vec4 texColor;
${idxs.map(i => `if(vAtlasId == ${i}) texColor = texture(uTexture${i}, vTexCoord);`).join('\n\telse ')}
${(() => {
if(renderTarget.screen) {
return `
if(vLayColor.a == 0.0)
outColor = texColor;
else
outColor = texColor * vLayColor;
`;
} else if(renderTarget.picking) {
return `
if(vLayColor.a == 0.0)
outColor = texColor;
else
outColor = texColor * vLayColor;
${ renderTarget.picking ?
` if(outColor.a == 0.0)
outColor = vec4(0.0, 0.0, 0.0, 0.0);
else
outColor = vIndex;
`;
}
})()}
`
: ''
}
}
`;

Expand Down Expand Up @@ -282,7 +281,7 @@ export class NodeDrawing {
this.matrixBuffer1 = util.create3x3MatrixBufferDynamicDraw(gl, n, program.aNodeMatrix1);
this.matrixBuffer2 = util.create3x3MatrixBufferDynamicDraw(gl, n, program.aNodeMatrix2);

this.indexBuffer = util.createBufferDynamicDraw(gl, n, 'vec4', program.aIndex);
this.indexBuffer = util.createBufferDynamicDraw(gl, n, 'vec4', program.aIndex);
this.atlasIdBuffer = util.createBufferDynamicDraw(gl, n, 'int', program.aAtlasId);
this.layColorBuffer = util.createBufferDynamicDraw(gl, n, 'vec4', program.aLayColor);
this.tex1Buffer = util.createBufferDynamicDraw(gl, n, 'vec4', program.aTex1);
Expand Down
153 changes: 108 additions & 45 deletions src/extensions/renderer/canvas/webgl/drawing-redraw-webgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,17 @@ function overrideCanvasRendererFunctions(r) {
}
}

// Override the findNearestElements function to call the webgl version
const canvasFindNearestElements = r.findNearestElements;
// Override function to call the webgl version
// const canvasFindNearestElements = r.findNearestElements;
r.findNearestElements = function(x, y, interactiveElementsOnly, isTouch) {
// don't call the canvas version of this function, its very slow
const canvasEles = canvasFindNearestElements.call(r, x, y, interactiveElementsOnly, isTouch);
const webglEles = findNearestElementsWebgl(r, x, y, interactiveElementsOnly, isTouch);

console.log('canvas eles', canvasEles.map(ele => ele.id()));
console.log('webgl eles', webglEles.map(ele => ele.id()));
// the canvas version of this function is very slow on large graphs
return findNearestElementsWebgl(r, x, y, interactiveElementsOnly, isTouch);
}

return webglEles;
// Override function to call the webgl version
// const canvasGetAllInBox = r.getAllInBox;
r.getAllInBox = function(x1, y1, x2, y2) {
return getAllInBoxWebgl(r, x1, y1, x2, y2);
}
}

Expand Down Expand Up @@ -225,47 +225,111 @@ function drawAtlases(r) {


/**
* This reverses what BRp.projectIntoViewport does, and converts to webgl coordinates.
* TODO: what if the coordinates are off the edge of the canvas?
* (x1, y1) is top left corner
* (x2, y2) is bottom right corner
*/
function modelCoordsToWebgl(r, x, y) {
const [ offsetLeft, offsetTop, , , scale ] = r.findContainerClientCoords();
const { x:panx, y:pany, zoom } = util.getEffectivePanZoom(r);

var clientX = Math.round((x * zoom + panx) * scale + offsetLeft);
var clientY = Math.round((y * zoom + pany) * scale + offsetTop);
clientY = r.canvasHeight - clientY; // normal canvas has y=0 at top, webgl has y=0 at bottom

return [ clientX, clientY ];
};
function getPickingIndexesInBox(r, x1, y1, x2, y2) {
const [ cX1, cY1, cX2, cY2 ] = util.modelCoordsToWebgl(r, x1, y1, x2, y2);
const w = Math.abs(cX2 - cX1);
const h = Math.abs(cY2 - cY1);


function findNearestElementsWebgl(r, x, y, interactiveElementsOnly, isTouch) {
const gl = r.data.contexts[r.WEBGL];

const [ clientX, clientY ] = modelCoordsToWebgl(r, x, y);

gl.bindFramebuffer(gl.FRAMEBUFFER, r.pickingFrameBuffer);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);


// Draw element z-indexes to the framebuffer
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
renderWebgl(r, null, RENDER_TARGET.PICKING);

const data = new Uint8Array(4);
gl.readPixels(clientX, clientY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, data);
const data = new Uint8Array(w * h * 4);
// (cX1, cY2) is the bottom left corner of the box
gl.readPixels(cX1, cY2, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

// TODO, make the target larger than one pixel, so we can get one node and one edge
const indexes = new Array(w * h);
for(let i = 0; i < indexes.length; i++) {
const pixel = data.slice(i*4, i*4 + 4);
const index = util.vec4ToIndex(pixel) - 1; // The framebuffer is cleared with 0s, so z-indexes are offset by 1
indexes[i] = index;
}
return indexes;
}


function getAllInBoxWebgl(r, x1, y1, x2, y2) { // model coordinates
var x1c = Math.min( x1, x2 );
var x2c = Math.max( x1, x2 );
var y1c = Math.min( y1, y2 );
var y2c = Math.max( y1, y2 );

x1 = x1c;
x2 = x2c;
y1 = y1c;
y2 = y2c;

const indexes = getPickingIndexesInBox(r, x1, y1, x2, y2);

// The framebuffer is cleared with 0s, so z-indexes are offset by 1
const index = util.vec4ToIndex(data) - 1;

const eles = r.getCachedZSortedEles();
if(index >= 0) {
const ele = eles[index];
console.log(ele.id());
return [ele];

const box = new Set();
for(const index of indexes) {
if(index >= 0) {
box.add(eles[index]);
}
}
return Array.from(box);
}


function findNearestElementsWebgl(r, x, y) { // model coordinates
const targetSize = 6; // This defines a square around the target point in model coordinates

x -= targetSize / 2;
y -= targetSize / 2;
const w = targetSize;
const h = targetSize;

const indexes = getPickingIndexesInBox(r, x, y, x + w, y + h);

const dim = Math.sqrt(indexes.length);
const rows = dim;
const cols = dim;
const center = dim / 2;

let nearestNode;
let nearestNodeSquareDist = Infinity;
let nearestEdge;
let nearestEdgeSquareDist = Infinity;

const eles = r.getCachedZSortedEles();

for(let row = 0; row < rows; row++) {
for(let col = 0; col < cols; col++) {
const i = row * cols + col;
const index = indexes[i];
if(index >= 0) {
const ele = eles[index];
const dist = Math.pow(row - center, 2) + Math.pow(col - center, 2);
if(ele.isNode() && dist < nearestNodeSquareDist) {
nearestNode = ele;
nearestNodeSquareDist = dist;
} else if(ele.isEdge() && dist < nearestEdgeSquareDist) {
nearestEdge = ele;
nearestEdgeSquareDist = dist;
}
}
}
}

if(nearestNode && nearestEdge) {
return [ nearestNode, nearestEdge ]; // TODO do I have to sort by nearest?
} else if(nearestNode) {
return [ nearestNode ];
} else if(nearestEdge) {
return [ nearestEdge ];
} else {
return [];
}
return [];
}


Expand Down Expand Up @@ -307,7 +371,7 @@ function renderWebgl(r, options, renderTarget) {
index += 1; // 0 is used to clear the background, need to offset all z-indexes by one
if(ele.isNode()) {
if(prevEle && prevEle.isEdge()) {
// edgeDrawing.endBatch();
edgeDrawing.endBatch();
}
nodeDrawing.draw(ele, index, 'node-underlay');
nodeDrawing.draw(ele, index, 'node-body');
Expand All @@ -317,7 +381,7 @@ function renderWebgl(r, options, renderTarget) {
if(prevEle && prevEle.isNode()) {
nodeDrawing.endBatch();
}
// edgeDrawing.draw(ele);
edgeDrawing.draw(ele, index);
}
prevEle = ele;
}
Expand All @@ -326,10 +390,10 @@ function renderWebgl(r, options, renderTarget) {
const eles = r.getCachedZSortedEles();

nodeDrawing.startFrame(panZoomMatrix, debugInfo, renderTarget);
// edgeDrawing.startFrame(panZoomMatrix, debugInfo, renderTarget);
edgeDrawing.startFrame(panZoomMatrix, debugInfo, renderTarget);

nodeDrawing.startBatch();
// edgeDrawing.startBatch();
edgeDrawing.startBatch();

if(renderTarget.screen) {
for(let i = 0; i < eles.nondrag.length; i++) {
Expand All @@ -338,15 +402,14 @@ function renderWebgl(r, options, renderTarget) {
for(let i = 0; i < eles.drag.length; i++) {
draw(eles.drag[i], -1);
}
} else if (renderTarget.picking) {
} else if(renderTarget.picking) {
for(let i = 0; i < eles.length; i++) {
draw(eles[i], i);
}
}


nodeDrawing.endBatch();
// edgeDrawing.endBatch();
edgeDrawing.endBatch();


if(r.data.gc) {
Expand Down
Loading

0 comments on commit e456278

Please sign in to comment.