From bfbd42b232206ad93f60402db824760133a048f7 Mon Sep 17 00:00:00 2001 From: Eric Rowell Date: Thu, 11 Apr 2013 23:51:21 -0700 Subject: [PATCH] extended getIntersection method for Layer to improve flexibility. Replaced instances of Math.round to bitwise round via | 0 for a small performance gain --- src/Layer.js | 31 +++++++++++++++++++++++++++++ src/Shape.js | 6 ++++-- src/Stage.js | 32 +++++++----------------------- tests/js/unit/layerTests.js | 39 +++++++++++++++++++++++++++++++++++++ tests/js/unit/stageTests.js | 39 +++++++++++++++++++++++++++++++++++++ 5 files changed, 120 insertions(+), 27 deletions(-) diff --git a/src/Layer.js b/src/Layer.js index ddfe84cd..adee8d0b 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -43,6 +43,37 @@ } }, + /** + * get intersection object that contains shape and pixel data + * @name getIntersection + * @methodOf Kinetic.Stage.prototype + * @param {Object} pos point object + */ + getIntersection: function() { + var pos = Kinetic.Type._getXY(Array.prototype.slice.call(arguments)), + p, colorKey, shape; + + if(this.isVisible() && this.isListening()) { + p = this.hitCanvas.context.getImageData(pos.x | 0, pos.y | 0, 1, 1).data; + // this indicates that a hit pixel may have been found + if(p[3] === 255) { + colorKey = Kinetic.Type._rgbToHex(p[0], p[1], p[2]); + shape = Kinetic.Global.shapes[colorKey]; + return { + shape: shape, + pixel: p + }; + } + // if no shape mapped to that pixel, return pixel array + else if(p[0] > 0 || p[1] > 0 || p[2] > 0 || p[3] > 0) { + return { + pixel: p + }; + } + } + + return null; + }, drawScene: function(canvas) { var layer = this.getLayer(); diff --git a/src/Shape.js b/src/Shape.js index 7b0a944e..7d367b95 100644 --- a/src/Shape.js +++ b/src/Shape.js @@ -86,7 +86,9 @@ return this.nodeType === selector || this.shapeType === selector ? [this] : []; }, /** - * determines if point is in the shape + * determines if point is in the shape, regardless if other shapes are on top of it. Note: because + * this method clears a temp hit canvas, and redraws the shape, it performs very poorly if executed many times + * consecutively. If possible, it's better to use the stage.getIntersections() method instead * @name intersects * @methodOf Kinetic.Shape.prototype * @param {Object} point point can be an object containing @@ -100,7 +102,7 @@ var hitCanvas = stage.hitCanvas; hitCanvas.clear(); this.drawScene(hitCanvas); - var p = hitCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data; + var p = hitCanvas.context.getImageData(pos.x | 0, pos.y | 0, 1, 1).data; return p[3] > 0; }, /** diff --git a/src/Stage.js b/src/Stage.js index 0e167bfe..dd6c031a 100644 --- a/src/Stage.js +++ b/src/Stage.js @@ -265,35 +265,17 @@ * @methodOf Kinetic.Stage.prototype * @param {Object} pos point object */ - getIntersection: function(pos) { - var layers = this.getChildren(), + getIntersection: function() { + var pos = Kinetic.Type._getXY(Array.prototype.slice.call(arguments)), + layers = this.getChildren(), len = layers.length, end = len - 1, - n, layer, p, colorKey, shape; + n, obj; - /* - * traverse through layers from top to bottom and look - * for hit detection - */ for(n = end; n >= 0; n--) { - layer = layers[n]; - if(layer.isVisible() && layer.isListening()) { - p = layer.hitCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data; - // this indicates that a hit pixel may have been found - if(p[3] === 255) { - colorKey = Kinetic.Type._rgbToHex(p[0], p[1], p[2]); - shape = Kinetic.Global.shapes[colorKey]; - return { - shape: shape, - pixel: p - }; - } - // if no shape mapped to that pixel, return pixel array - else if(p[0] > 0 || p[1] > 0 || p[2] > 0 || p[3] > 0) { - return { - pixel: p - }; - } + obj = layers[n].getIntersection(pos); + if (obj) { + return obj; } } diff --git a/tests/js/unit/layerTests.js b/tests/js/unit/layerTests.js index 32235b8b..12a96170 100644 --- a/tests/js/unit/layerTests.js +++ b/tests/js/unit/layerTests.js @@ -27,6 +27,45 @@ Test.Modules.LAYER = { test(style.margin === '0px', 'canvas margin style should be 0px'); test(style.padding === '0px', 'canvas padding style should be 0px'); test(style.backgroundColor === 'transparent', 'canvas backgroundColor style should be transparent'); + }, + 'layer getIntersections()': function(containerId) { + var stage = new Kinetic.Stage({ + container: containerId, + width: 578, + height: 200, + throttle: 999 + }); + var layer = new Kinetic.Layer(); + + var redCircle = new Kinetic.Circle({ + x: 380, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + id: 'redCircle' + }); + + var greenCircle = new Kinetic.Circle({ + x: 300, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + id: 'greenCircle' + }); + + layer.add(redCircle); + layer.add(greenCircle); + stage.add(layer); + + test(layer.getIntersection(300, 100).shape.getId() === 'greenCircle', 'shape should be greenCircle'); + test(layer.getIntersection(380, 100).shape.getId() === 'redCircle', 'shape should be redCircle'); + test(layer.getIntersection(100, 100) === null, 'shape should be null'); + + }, 'redraw hit graph': function(containerId) { var stage = new Kinetic.Stage({ diff --git a/tests/js/unit/stageTests.js b/tests/js/unit/stageTests.js index 2af34aa1..24e4d984 100644 --- a/tests/js/unit/stageTests.js +++ b/tests/js/unit/stageTests.js @@ -76,6 +76,45 @@ Test.Modules.STAGE = { }); test(stage.getContent().className === 'kineticjs-content', 'stage DOM class name is wrong'); + }, + 'stage getIntersections()': function(containerId) { + var stage = new Kinetic.Stage({ + container: containerId, + width: 578, + height: 200, + throttle: 999 + }); + var layer = new Kinetic.Layer(); + + var redCircle = new Kinetic.Circle({ + x: 380, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'red', + stroke: 'black', + id: 'redCircle' + }); + + var greenCircle = new Kinetic.Circle({ + x: 300, + y: stage.getHeight() / 2, + radius: 70, + strokeWidth: 4, + fill: 'green', + stroke: 'black', + id: 'greenCircle' + }); + + layer.add(redCircle); + layer.add(greenCircle); + stage.add(layer); + + test(stage.getIntersection(300, 100).shape.getId() === 'greenCircle', 'shape should be greenCircle'); + test(stage.getIntersection(380, 100).shape.getId() === 'redCircle', 'shape should be redCircle'); + test(stage.getIntersection(100, 100) === null, 'shape should be null'); + + }, 'test getIntersections': function(containerId) { var stage = new Kinetic.Stage({