/** * Use as you will. * * 2010 indiemaps.com * * */ package com.indiemaps.graphics.adjacency { import __AS3__.vec.Vector; import flash.display.BitmapData; import flash.display.DisplayObject; import flash.filters.GlowFilter; import flash.geom.ColorTransform; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; public class AdjacencyTest { public function AdjacencyTest() { } /** * The default width of the BitmapData instance used to test for adjacency. This is used if the user does not pass in a BitmapData object to use. * */ public static const TEST_BITMAP_WIDTH : int = 600; /** * The default height of the BitmapData instance used to test for adjacency. This is used if the user does not pass in a BitmapData object to use. * */ public static const TEST_BITMAP_HEIGHT : int = 600; /** * Returns a Vector of DisplayObject features determined to be neighbors of the passed-in feature (from a passed-in Vector of all features). * * @param feature The DisplayObject feature of which to determine the neighbors. * @param features A Vector of all features (all potential neighbors of the feature in question). * @param strokeWidth The width of the external stroke of the passed-in feature and feature. Is used to determine whether a (performance-costly) OuterGlow filter need be applied to the tested DisplayObject features. * @param commonTestBitmapData An optional BitmapData instance. Can be passed-in to improve performance when calling this method repeatedly. * @param commonSecondaryTestBitmapData Another optional BitmapData, of the same dimensions as the above commonTestBitmapData, which can be used to improve performance. * * @return A Vector of adjacenct DisplayObject features. * * @see getPotentialNeighborsForFeature * * */ public static function getNeighborsForFeature( feature : DisplayObject, features : Vector.< DisplayObject >, strokeWidth : int = 1, commonTestBitmapData : BitmapData = null, commonSecondaryTestBitmapData : BitmapData = null ) : Vector.< DisplayObject > { var neighbors : Vector.< DisplayObject > = new Vector.< DisplayObject >(); var potentialNeighbors : Vector.< DisplayObject > = getPotentialNeighborsForFeature( feature, features ); var tempBounds : Rectangle = feature.getBounds( feature.parent ); var fullBounds : Rectangle = new Rectangle( tempBounds.x, tempBounds.y, tempBounds.width, tempBounds.height ); for each ( var potentialNeighbor : DisplayObject in potentialNeighbors ) { tempBounds = potentialNeighbor.getBounds( potentialNeighbor.parent ); if ( tempBounds.left < fullBounds.left ) fullBounds.left = tempBounds.left; if ( tempBounds.right > fullBounds.right ) fullBounds.right = tempBounds.right; if ( tempBounds.top < fullBounds.top ) fullBounds.top = tempBounds.top; if ( tempBounds.bottom > fullBounds.bottom ) fullBounds.bottom = tempBounds.bottom; } var img : BitmapData; if ( commonTestBitmapData ) { img = commonTestBitmapData; img.fillRect( new Rectangle( 0, 0, img.width, img.height ), 0x00000000 ); } else { img = new BitmapData( TEST_BITMAP_WIDTH, TEST_BITMAP_HEIGHT, true, 0 ); } var zoomy : Number = Math.min( img.width / fullBounds.width, img.height / fullBounds.height ); var matrix : Matrix = new Matrix( zoomy, 0, 0, zoomy, .5 * img.width + ( feature.x - ( fullBounds.x + .5 * fullBounds.width ) ) * zoomy, .5 * img.height + ( feature.y - ( fullBounds.y + .5 * fullBounds.height ) ) * zoomy ); img.draw( feature, matrix, new ColorTransform( 1, 1, 1, 1, -255, -255, 255 ) ); if ( strokeWidth < 2 ) { img.applyFilter( img, new Rectangle( 0, 0, img.width, img.height ), new Point(), new GlowFilter( 0xff0000, 1, 2 - strokeWidth, 2 - strokeWidth, 255 ) ); } for each ( potentialNeighbor in potentialNeighbors ) { if ( feature == potentialNeighbor ) continue; matrix = new Matrix( zoomy, 0, 0, zoomy, .5 * img.width + ( potentialNeighbor.x - ( fullBounds.x + .5 * fullBounds.width ) ) * zoomy, .5 * img.height + ( potentialNeighbor.y - ( fullBounds.y + .5 * fullBounds.height ) ) * zoomy ); if ( strokeWidth < 2 ) { var tempBitmapData : BitmapData; if ( commonSecondaryTestBitmapData ) { tempBitmapData = commonSecondaryTestBitmapData; tempBitmapData.fillRect( new Rectangle( 0, 0, img.width, img.height ), 0x00000000 ); } else { tempBitmapData = new BitmapData( img.width, img.height, true, 0 ); } tempBitmapData.draw( potentialNeighbor, matrix, new ColorTransform( 1, 1, 1, 1, 255, -255, -255 ) ); tempBitmapData.applyFilter( tempBitmapData, new Rectangle( 0, 0, tempBitmapData.width, tempBitmapData.height ), new Point(), new GlowFilter( 0x0000ff, 1, 2 - strokeWidth, 2 - strokeWidth, 255 ) ); img.draw( tempBitmapData, null, null, "difference" ); } else { img.draw( potentialNeighbor, matrix, new ColorTransform( 1, 1, 1, 1, 255, -255, -255 ), "difference" ); } var overlappingPixels : int = img.threshold( img, new Rectangle( 0, 0, img.width, img.height ), new Point(), ">", 0xFFFF0000, 0xff00ff00 ); if ( overlappingPixels ) neighbors.push( potentialNeighbor ); } return neighbors; } /** * Returns a Vector of DisplayObject features with bounding boxes that overlap the bounding box of the passed-in feature (from a passed-in Vector of all features). * * @param feature The DisplayObject feature of which to determine the potential neighbors. * @param features A Vector of all features (all potential neighbors of the feature in question). * * @return a Vector of DisplayObject features with bounding boxes that overlap the bounding box of the passed-in feature * * */ public static function getPotentialNeighborsForFeature( feature : DisplayObject, features : Vector.< DisplayObject > ) : Vector.< DisplayObject > { var potentialNeighbors : Vector.< DisplayObject > = new Vector.< DisplayObject >(); for each ( var otherFeature : DisplayObject in features ) { if ( feature == otherFeature ) continue; if ( feature.getBounds( feature.parent ).intersects( otherFeature.getBounds( feature.parent ) ) ) potentialNeighbors.push( otherFeature ); } return potentialNeighbors; } /** * Used to determine whether two polygonal features (DisplayObject instances) are adjacent or direct neighbors. Utilizes a bitmap-based test and returns the number of overlapping pixels. * * @param feature1 The DisplayObject feature to test against feature2 * @param feature2 The DisplayObject feature to test against feature1 * @param strokeWidth The width of the external stroke of the passed-in feature and feature. Is used to determine whether a (performance-costly) OuterGlow filter need be applied to the tested DisplayObject features. * @param commonTestBitmapData An optional BitmapData instance. Can be passed-in to improve performance when calling this method repeatedly. * @param commonSecondaryTestBitmapData Another optional BitmapData, of the same dimensions as the above commonTestBitmapData, which can be used to improve performance. * * @return The number of overlapping pixels (0 means the features aren't adjacent, though a threshold may be used to discriminate against features that share a corner but no common border). * * */ public static function checkFeatureAdjacency( feature1 : DisplayObject, feature2 : DisplayObject, strokeWidth : int = 1, commonTestBitmapData : BitmapData = null, commonSecondaryTestBitmapData : BitmapData = null ) : int { var img : BitmapData; if ( commonTestBitmapData ) { img = commonTestBitmapData; img.fillRect( new Rectangle( 0, 0, img.width, img.height ), 0x00000000 ); } else { img = new BitmapData( TEST_BITMAP_WIDTH, TEST_BITMAP_HEIGHT, true, 0 ); } var feature1Bounds : Rectangle = feature1.getBounds( feature1.parent ); var feature2Bounds : Rectangle = feature2.getBounds( feature2.parent ); if ( !feature1Bounds.intersects( feature2Bounds ) ) return 0; var bothBounds : Rectangle = feature1Bounds.clone(); if ( feature2Bounds.left < bothBounds.left ) bothBounds.left = feature2Bounds.left; if ( feature2Bounds.right > bothBounds.right ) bothBounds.right = feature2Bounds.right; if ( feature2Bounds.top < bothBounds.top ) bothBounds.top = feature2Bounds.top; if ( feature2Bounds.bottom > bothBounds.bottom ) bothBounds.bottom = feature2Bounds.bottom; var zoomy : Number = Math.min( img.width / bothBounds.width, img.height / bothBounds.height ); var matrix : Matrix = new Matrix( zoomy, 0, 0, zoomy, .5 * img.width + ( feature1.x - ( bothBounds.x + .5 * bothBounds.width ) ) * zoomy, .5 * img.height + ( feature1.y - ( bothBounds.y + .5 * bothBounds.height ) ) * zoomy ); img.draw( feature1, matrix, new ColorTransform( 1, 1, 1, 1, 255, -255, -255 ) ); if ( strokeWidth < 2 ) { img.applyFilter( img, new Rectangle( 0, 0, img.width, img.height ), new Point(), new GlowFilter( 0xff0000, 1, 2 - strokeWidth, 2 - strokeWidth, 255 ) ); } matrix = new Matrix( zoomy, 0, 0, zoomy, .5 * img.width + ( feature2.x - ( bothBounds.x + .5 * bothBounds.width ) ) * zoomy, .5 * img.height + ( feature2.y - ( bothBounds.y + .5 * bothBounds.height ) ) * zoomy ); if ( strokeWidth < 2 ) { var tempBitmapData : BitmapData; if ( commonSecondaryTestBitmapData ) { tempBitmapData = commonSecondaryTestBitmapData; tempBitmapData.fillRect( new Rectangle( 0, 0, img.width, img.height ), 0x00000000 ); } else { tempBitmapData = new BitmapData( img.width, img.height, true, 0 ); } tempBitmapData.draw( feature2, matrix, new ColorTransform( 1, 1, 1, 1, -255, -255, 255 ) ); tempBitmapData.applyFilter( tempBitmapData, new Rectangle( 0, 0, tempBitmapData.width, tempBitmapData.height ), new Point(), new GlowFilter( 0x0000ff, 1, 2 - strokeWidth, 2 - strokeWidth, 255 ) ); img.draw( tempBitmapData, null, null, "difference" ); } else { img.draw( feature2, matrix, new ColorTransform( 1, 1, 1, 1, -255, -255, 255 ), "difference" ); } var overlappingPixels : int = img.threshold( img, new Rectangle( 0, 0, img.width, img.height ), new Point(), ">", 0xFFFF0000, 0xff000000 ); return overlappingPixels; } } }