Last active
December 11, 2020 13:18
-
-
Save tafsiri/e3ecf9c459174038cb06 to your computer and use it in GitHub Desktop.
2D Picking with canvas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>2D Picking with canvas</title> | |
<meta name="description" content=""> | |
<meta name="author" content="Yannick Assogba"> | |
<script src="//rawgit.com/mrdoob/stats.js/master/build/stats.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.js"></script> | |
</head> | |
<body> | |
<div id='container'> | |
</div> | |
<script src="index.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This example uses a hidden canvas to demonstrate a technique for | |
* simulating DOM click events while rendering to a canvas. | |
* | |
* The basic technique is to render your visual markers twice, the second | |
* time on a hidden canvas where each marker gets a unique color. We can then | |
* look up that color to get back to the data in question. | |
* | |
* Open your web console and click on the squares to see their original | |
* indices in the data array. | |
*/ | |
window.addEventListener('load', function(){ | |
var stats = new Stats(); | |
stats.setMode( 0 ); // 0: fps, 1: ms, 2: mb | |
// align top-left | |
stats.domElement.style.position = 'absolute'; | |
stats.domElement.style.left = '0px'; | |
stats.domElement.style.top = '0px'; | |
document.body.appendChild( stats.domElement ); | |
var width = 960; | |
var height = 500; | |
var mainCanvas = document.createElement("canvas"); | |
var hiddenCanvas = document.createElement("canvas"); | |
mainCanvas.setAttribute('width', width); | |
mainCanvas.setAttribute('height', height); | |
hiddenCanvas.setAttribute('width', width); | |
hiddenCanvas.setAttribute('height', height); | |
var container = document.querySelector("#container"); | |
container.appendChild(mainCanvas); | |
// container.appendChild(hiddenCanvas); // Include this to see the hidden canvas. | |
// A map to lookup nodes by color used in the hidden canvas. | |
var colToNode = {}; | |
/* | |
Generate the data. | |
*/ | |
var data = []; | |
function makeData(count) { | |
data = []; | |
for(var i = 0; i < count; i++) { | |
var obj = { | |
x: Math.random() * (width - 20), | |
y: Math.random() * (height - 20), | |
xVel: (Math.random() * 0.5) * (Math.random() < 0.5 ? -1 : 1), | |
yVel: (Math.random() * 0.5) * (Math.random() < 0.5 ? -1 : 1), | |
width: 15, | |
height: 15, | |
index: i | |
}; | |
data.push(obj); | |
} | |
return data; | |
} | |
var controls = {count:100}; | |
var gui = new dat.GUI(); | |
var controller = gui.add(controls, 'count', 0, 20000).step(100); | |
controller.onChange(function(value) { | |
data = makeData(value); | |
}); | |
/* | |
Updates the nodes on each frame to make them bounce around the screen. | |
*/ | |
function update(data) { | |
var numElements = data.length; | |
for(var i = 0; i < numElements; i++) { | |
var node = data[i]; | |
node.x += node.xVel; | |
node.y += node.yVel; | |
if(node.x > width || node.x < 0) { | |
node.xVel *= -1; | |
} | |
if(node.y > height || node.y < 0) { | |
node.yVel *= -1; | |
} | |
} | |
} | |
/* | |
Generates the next color in the sequence, going from 0,0,0 to 255,255,255. | |
*/ | |
var nextCol = 1; | |
function genColor(){ | |
var ret = []; | |
// via http://stackoverflow.com/a/15804183 | |
if(nextCol < 16777215){ | |
ret.push(nextCol & 0xff); // R | |
ret.push((nextCol & 0xff00) >> 8); // G | |
ret.push((nextCol & 0xff0000) >> 16); // B | |
nextCol += 100; // This is exagerated for this example and would ordinarily be 1. | |
} | |
var col = "rgb(" + ret.join(',') + ")"; | |
return col; | |
} | |
function draw(data, canvas, hidden) { | |
var ctx = canvas.getContext('2d'); | |
ctx.clearRect(0, 0, width, height); | |
var numElements = data.length; | |
for(var i = 0; i < numElements; i++) { | |
var node = data[i]; | |
if(node.renderCol) { | |
// Render clicked nodes in the color of their corresponding node | |
// on the hidden canvas. | |
ctx.fillStyle = node.renderCol; | |
} else { | |
ctx.fillStyle = 'DimGray'; | |
} | |
if(hidden) { | |
if(node.__pickColor === undefined) { | |
// If we have never drawn the node to the hidden canvas get a new | |
// color for it and put it in the map. | |
node.__pickColor = genColor(); | |
colToNode[node.__pickColor] = node; | |
} | |
// On the hidden canvas each rectangle gets a unique color. | |
ctx.fillStyle = node.__pickColor; | |
} | |
// Draw the actual rectangle | |
ctx.fillRect(node.x, node.y, node.width, node.height); | |
} | |
} | |
// Listen for clicks on the main canvas | |
mainCanvas.addEventListener("click", function(e){ | |
draw(data, hiddenCanvas, true); | |
var mouseX = e.layerX; | |
var mouseY = e.layerY; | |
// Get the corresponding pixel color on the hidden canvas | |
// and look up the node in our map. | |
var ctx = hiddenCanvas.getContext("2d"); | |
var col = ctx.getImageData(mouseX, mouseY, 1, 1).data; | |
var colString = "rgb(" + col[0] + "," + col[1] + ","+ col[2] + ")"; | |
var node = colToNode[colString]; | |
if(node) { | |
node.renderCol = node.__pickColor; | |
console.log("Clicked on node with index:", node.index, node); | |
} | |
}); | |
// Generate the data and start the draw loop. | |
data = makeData(100); // Increase this number to get more boxes | |
function animate() { | |
stats.begin(); | |
draw(data, mainCanvas); | |
// draw(data, hiddenCanvas, true); | |
update(data); | |
stats.end(); | |
window.requestAnimationFrame(animate); | |
} | |
window.requestAnimationFrame(animate); | |
}, false); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment