From 2ff0b557ac48539f410db6bfc7368de0e8c9aab6 Mon Sep 17 00:00:00 2001 From: jmoenig Date: Fri, 29 Nov 2019 18:06:27 +0100 Subject: [PATCH] optimized color collision detection --- HISTORY.md | 2 + snap.html | 4 +- src/objects.js | 203 +++++++++++++++++++------------------------------ src/threads.js | 62 +-------------- 4 files changed, 82 insertions(+), 189 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 3be47a0f..6fc3fb44 100755 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,11 +3,13 @@ ## in development: * **New Features:** * **Notable Changes:** + * optimized color collision detection * **Notable Fixes:** * **Translation Updates:** ### 2019-11-29 * new dev version +* objects, threads: optimized color collision detection ## v5.3.7: * **Notable Fixes:** diff --git a/snap.html b/snap.html index aee385f8..320d1fe6 100755 --- a/snap.html +++ b/snap.html @@ -7,8 +7,8 @@ - - + + diff --git a/src/objects.js b/src/objects.js index 3b8e2798..e36d2877 100644 --- a/src/objects.js +++ b/src/objects.js @@ -84,7 +84,7 @@ BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, BooleanSlotMorph, localize, TableMorph, TableFrameMorph, normalizeCanvas, VectorPaintEditorMorph, HandleMorph, AlignmentMorph, Process, XML_Element, WorldMap, copyCanvas*/ -modules.objects = '2019-November-15'; +modules.objects = '2019-November-29'; var SpriteMorph; var StageMorph; @@ -890,29 +890,6 @@ SpriteMorph.prototype.initBlocks = function () { category: 'sensing', spec: 'color %clr is touching %clr ?' }, - colorFiltered: { - dev: true, - type: 'reporter', - category: 'sensing', - spec: 'filter %clr tolerance %n %', - defaults: [null, 15] - }, - reportFuzzyTouchingColor: { - dev: true, - only: SpriteMorph, - type: 'predicate', - category: 'sensing', - spec: 'touching %clr tolerance %n ?', - defaults: [null, 15] - }, - reportFuzzyColorIsTouchingColor: { - dev: true, - only: SpriteMorph, - type: 'predicate', - category: 'sensing', - spec: 'color %clr is touching %clr tolerance %n ?', - defaults: [null, null, 15] - }, reportAspect: { type: 'reporter', category: 'sensing', @@ -1980,46 +1957,6 @@ SpriteMorph.prototype.rotationCenter = function () { return this.position().add(this.rotationOffset); }; -SpriteMorph.prototype.colorFiltered = function (aColor, tolerance) { - // answer a new Morph containing my image filtered by aColor - // ignore transparency (alpha) - var morph = new Morph(), - ext = this.extent(), - ctx, - src, - clr, - i, - dta; - - src = normalizeCanvas(this.image, true).getContext('2d').getImageData( - 0, - 0, - ext.x, - ext.y - ); - morph.image = newCanvas(ext, true); - morph.bounds = this.bounds.copy(); - ctx = morph.image.getContext('2d'); - dta = ctx.createImageData(ext.x, ext.y); - for (i = 0; i < ext.x * ext.y * 4; i += 4) { - clr = new Color( - src.data[i], - src.data[i + 1], - src.data[i + 2] - ); - if ((tolerance && clr.isCloseTo(aColor, false, tolerance)) || - clr.eq(aColor) - ) { - dta.data[i] = src.data[i]; - dta.data[i + 1] = src.data[i + 1]; - dta.data[i + 2] = src.data[i + 2]; - dta.data[i + 3] = 255; - } - } - ctx.putImageData(dta, 0, 0); - return morph; -}; - SpriteMorph.prototype.getImageData = function () { // used for video motion detection. // Get sprite image data scaled to 1 an converted to ABGR array, @@ -2490,9 +2427,6 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(watcherToggle('reportThreadCount')); blocks.push(block('reportThreadCount')); - blocks.push(block('colorFiltered')); - blocks.push(block('reportFuzzyTouchingColor')); - blocks.push(block('reportFuzzyColorIsTouchingColor')); blocks.push(block('reportStackSize')); blocks.push(block('reportFrameCount')); } @@ -4294,18 +4228,80 @@ SpriteMorph.prototype.goBack = function (layers) { this.parent.changed(); }; -// SpriteMorph collision detection optimization +// SpriteMorph collision detection + +SpriteMorph.prototype.reportTouchingColor = function (aColor) { + var stage = this.parentThatIsA(StageMorph), + data, len, i; + + if (stage) { + if (this.wantsRedraw && this.isWarped) { + this.endWarp(); + this.startWarp(); + } + data = this.overlappingPixels(stage); + if (!data) {return false; } + len = data[0].length; + for (i = 3; i < len; i += 4) { + if (data[0][i] && data[1][i]) { + if ( + data[1][i - 3] === aColor.r && + data[1][i - 2] === aColor.g && + data[1][i - 1] === aColor.b + ) { + return true; + } + } + } + } + return false; +}; + +SpriteMorph.prototype.reportColorIsTouchingColor = function ( + thisColor, + thatColor +) { + var stage = this.parentThatIsA(StageMorph), + data, len, i; + + if (stage) { + if (this.wantsRedraw && this.isWarped) { + this.endWarp(); + this.startWarp(); + } + data = this.overlappingPixels(stage); + if (!data) {return false; } + len = data[0].length; + for (i = 3; i < len; i += 4) { + if (data[0][i] && data[1][i]) { + if ( + data[0][i - 3] === thisColor.r && + data[0][i - 2] === thisColor.g && + data[0][i - 1] === thisColor.b && + data[1][i - 3] === thatColor.r && + data[1][i - 2] === thatColor.g && + data[1][i - 1] === thatColor.b + ) { + return true; + } + } + } + } + return false; +}; SpriteMorph.prototype.overlappingPixels = function (otherSprite) { // overrides method from Morph because Sprites aren't nested Morphs var oRect = this.bounds.intersect(otherSprite.bounds), thisImg = this.image, thatImg = otherSprite.image; - - if (oRect.width() < 1 || oRect.height() < 1 || - !this.image || !otherSprite.image || - !this.image.width || !this.image.height || - !otherSprite.image.width || !otherSprite.image.height + + if (otherSprite instanceof StageMorph) { + // only check for color collision + thatImg = otherSprite.thumbnail(otherSprite.extent(), this, true); + } + if (oRect.width() < 1 || oRect.height() < 1 || !thisImg || !thatImg || + !thisImg.width || !thisImg.height || !thatImg.width || !thatImg.height ) { return false; } @@ -7675,52 +7671,6 @@ StageMorph.prototype.clearProjectionLayer = function () { this.changed(); }; -StageMorph.prototype.colorFiltered = function ( - aColor, - excludedSprite, - tolerance -) { - // answer a new Morph containing my image filtered by aColor - // ignore the excludedSprite, because its collision is checked - // ignore transparency (alpha) - var morph = new Morph(), - ext = this.extent(), - img = this.thumbnail(ext, excludedSprite), - ctx, - src, - clr, - i, - dta; - - src = normalizeCanvas(img, true).getContext('2d').getImageData( - 0, - 0, - ext.x, - ext.y - ); - morph.bounds = this.bounds.copy(); - morph.image = newCanvas(ext, true); - ctx = morph.image.getContext('2d'); - dta = ctx.createImageData(ext.x, ext.y); - for (i = 0; i < ext.x * ext.y * 4; i += 4) { - clr = new Color( - src.data[i], - src.data[i + 1], - src.data[i + 2] - ); - if ((tolerance && clr.isCloseTo(aColor, false, tolerance)) || - clr.eq(aColor) - ) { - dta.data[i] = src.data[i]; - dta.data[i + 1] = src.data[i + 1]; - dta.data[i + 2] = src.data[i + 2]; - dta.data[i + 3] = 255; - } - } - ctx.putImageData(dta, 0, 0); - return morph; -}; - // StageMorph video capture StageMorph.prototype.startVideo = function() { @@ -8570,7 +8520,6 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(watcherToggle('reportThreadCount')); blocks.push(block('reportThreadCount')); - blocks.push(block('colorFiltered')); blocks.push(block('reportStackSize')); blocks.push(block('reportFrameCount')); } @@ -8843,18 +8792,20 @@ StageMorph.prototype.edit = SpriteMorph.prototype.edit; // StageMorph thumbnail -StageMorph.prototype.thumbnail = function (extentPoint, excludedSprite) { -/* - answer a new Canvas of extentPoint dimensions containing - my thumbnail representation keeping the originial aspect ratio -*/ +StageMorph.prototype.thumbnail = function ( + extentPoint, + excludedSprite, + nonRetina +) { + // answer a new Canvas of extentPoint dimensions containing + // my thumbnail representation keeping the originial aspect ratio var myself = this, src = this.image, scale = Math.min( (extentPoint.x / src.width), (extentPoint.y / src.height) ), - trg = newCanvas(extentPoint), + trg = newCanvas(extentPoint, nonRetina), ctx = trg.getContext('2d'), fb, fimg; diff --git a/src/threads.js b/src/threads.js index 13bc97cb..8d218d1e 100644 --- a/src/threads.js +++ b/src/threads.js @@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy, isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph, Color, TableFrameMorph, ColorSlotMorph, isSnapObject, Map, newCanvas, Symbol*/ -modules.threads = '2019-November-19'; +modules.threads = '2019-November-29'; var ThreadManager; var Process; @@ -4053,66 +4053,6 @@ Process.prototype.objectTouchingObject = function (thisObj, name) { ); }; -Process.prototype.reportTouchingColor = function (aColor, tolerance) { - // also check for any parts (subsprites) - var thisObj = this.blockReceiver(), - stage; - - if (thisObj) { - stage = thisObj.parentThatIsA(StageMorph); - if (stage) { - if (thisObj.isTouching( - stage.colorFiltered(aColor, thisObj, tolerance)) - ) { - return true; - } - return thisObj.parts.some( - function (any) { - return any.isTouching( - stage.colorFiltered(aColor, any, tolerance) - ); - } - ); - } - } - return false; -}; - -Process.prototype.reportFuzzyTouchingColor = - Process.prototype.reportTouchingColor; - -Process.prototype.reportColorIsTouchingColor = function ( - color1, - color2, - tolerance -) { - // also check for any parts (subsprites) - var thisObj = this.blockReceiver(), - stage; - - if (thisObj) { - stage = thisObj.parentThatIsA(StageMorph); - if (stage) { - if (thisObj.colorFiltered(color1, tolerance).isTouching( - stage.colorFiltered(color2, thisObj, tolerance) - )) { - return true; - } - return thisObj.parts.some( - function (any) { - return any.colorFiltered(color1, tolerance).isTouching( - stage.colorFiltered(color2, any, tolerance) - ); - } - ); - } - } - return false; -}; - -Process.prototype.reportFuzzyColorIsTouchingColor = - Process.prototype.reportColorIsTouchingColor; - Process.prototype.reportAspect = function (aspect, location) { // sense colors and sprites anywhere, // use sprites to read/write data encoded in colors.