Skip to content

Instantly share code, notes, and snippets.

@tafsiri

tafsiri/index.html

Last active Dec 11, 2020
Embed
What would you like to do?
2D Picking with canvas
<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 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