智慧 + 毅力 = 无所不能

正确性、健壮性、可靠性、效率、易用性、可读性、可复用性、兼容性、可移植性...

导航

超高效的不规则物体碰撞检测

Posted on 2013-01-22 11:55  Bill Yuan  阅读(3064)  评论(0编辑  收藏  举报

转自:http://blog.forfl.com/2012/03/super-efficient-collision-detection-of-irregular-objects/

package ws.tink.display
{
    
    import flash.display.BitmapData;
    import flash.display.BlendMode;
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    
    public class HitTest
    {
 
        public static function complexHitTestObject( target1:DisplayObject, target2:DisplayObject,  accurracy:Number = 1 ):Boolean
        {
            return complexIntersectionRectangle( target1, target2, accurracy ).width != 0;
        }
        
        public static function intersectionRectangle( target1:DisplayObject, target2:DisplayObject ):Rectangle
        {
            // If either of the items don't have a reference to stage, then they are not in a display list
            // or if a simple hitTestObject is false, they cannot be intersecting.
            if( !target1.root || !target2.root || !target1.hitTestObject( target2 ) ) return new Rectangle();
            
            // Get the bounds of each DisplayObject.
            var bounds1:Rectangle = target1.getBounds( target1.root );
            var bounds2:Rectangle = target2.getBounds( target2.root );
            
            // Determine test area boundaries.
            var intersection:Rectangle = new Rectangle();
            intersection.x   = Math.max( bounds1.x, bounds2.x );
            intersection.y    = Math.max( bounds1.y, bounds2.y );
            intersection.width      = Math.min( ( bounds1.x + bounds1.width ) - intersection.x, ( bounds2.x + bounds2.width ) - intersection.x );
            intersection.height = Math.min( ( bounds1.y + bounds1.height ) - intersection.y, ( bounds2.y + bounds2.height ) - intersection.y );
        
            return intersection;
        }
        
        public static function complexIntersectionRectangle( target1:DisplayObject, target2:DisplayObject, accurracy:Number = 1 ):Rectangle
        {             
            if( accurracy <= 0 ) throw new Error( "ArgumentError: Error #5001: Invalid value for accurracy", 5001 );
            
            // If a simple hitTestObject is false, they cannot be intersecting.
            if( !target1.hitTestObject( target2 ) ) return new Rectangle();
            
            var hitRectangle:Rectangle = intersectionRectangle( target1, target2 );
            // If their boundaries are no interesecting, they cannot be intersecting.
            if( hitRectangle.width * accurracy <1 || hitRectangle.height * accurracy <1 ) return new Rectangle();
            
            var bitmapData:BitmapData = new BitmapData( hitRectangle.width * accurracy, hitRectangle.height * accurracy, false, 0x000000 ); 
 
            // Draw the first target.
            bitmapData.draw( target1, HitTest.getDrawMatrix( target1, hitRectangle, accurracy ), new ColorTransform( 1, 1, 1, 1, 255, -255, -255, 255 ) );
            // Overlay the second target.
            bitmapData.draw( target2, HitTest.getDrawMatrix( target2, hitRectangle, accurracy ), new ColorTransform( 1, 1, 1, 1, 255, 255, 255, 255 ), BlendMode.DIFFERENCE );
            
            // Find the intersection.
            var intersection:Rectangle = bitmapData.getColorBoundsRect( 0xFFFFFFFF,0xFF00FFFF );
            
            bitmapData.dispose();
            
            // Alter width and positions to compensate for accurracy
            if( accurracy != 1 )
            {
                intersection.x /= accurracy;
                intersection.y /= accurracy;
                intersection.width /= accurracy;
                intersection.height /= accurracy;
            }
            
            intersection.x += hitRectangle.x;
            intersection.y += hitRectangle.y;
            
            return intersection;
        }
        
        
        protected static function getDrawMatrix( target:DisplayObject, hitRectangle:Rectangle, accurracy:Number ):Matrix
        {
            var localToGlobal:Point;;
            var matrix:Matrix;
            
            var rootConcatenatedMatrix:Matrix = target.root.transform.concatenatedMatrix;
            
            localToGlobal = target.localToGlobal( new Point( ) );
            matrix = target.transform.concatenatedMatrix;
            matrix.tx = localToGlobal.x - hitRectangle.x;
            matrix.ty = localToGlobal.y - hitRectangle.y;
            
            matrix.a = matrix.a / rootConcatenatedMatrix.a;
            matrix.d = matrix.d / rootConcatenatedMatrix.d;
            if( accurracy != 1 ) matrix.scale( accurracy, accurracy );
 
            return matrix;
        }
 
    }
 
} 

相信已经有很多人使用过了这个像素级精确碰撞类,作者很NB思路很清晰。

但是在使用过程中,这个方法的实际效率并不是很高效,对于大一点的对象或者碰撞面积比较大的话来个1000次循环就已经超15秒了。这限制了如此优秀的算法在实际项目中的运用。在此由于论坛有讨论精确碰撞的贴,于是在原算法上加了一点小想法来优化检测效率,并且分享给大家。
首先原算法判断效率的关键就是两个对象重叠的矩形面积,面积越大效率越低。那么控制每次draw的面积就成了控制效率的关键。举个简单例子,绘制检测一次100*100的面积总比不上检测3到4次10*10的来的快。

原先的BitmapData之间精确判断顺序是这样的。
1。判断2容器矩形是否有重叠
2。有重叠则取两个容器的重叠部分draw到一个新BitmapData中并且把有色彩的部分全部纯色化,A容器放进去后B容器以一个滤镜模式再绘制进去。
3。判断该容器是否有A容器的颜色和B容器的颜色经过滤镜后产生的颜色。有就表示有碰撞,无则无碰撞。

优化的判断顺序如下:
1。判断2容器矩形是否有重叠
2。计算2容器的尺寸大小缩小到20*20需要缩小多少倍率,同时根据这个倍率重新计算重叠部分,也就是缩小后的重叠部分。
3。向一个20*20的BitmapData中绘制2个容器对象,经过一系列计算获取碰撞部分。
4。这时的重叠部分是缩小后图像的重叠部分,实际尺寸这个区域内可能并未碰撞,所以在压缩检测时有碰撞就需要进行再次检测。而检测时候把之前的倍率重新计算,计算原理是:由于获得碰撞区域一般都比原先矩形重叠部分小,那么用计算得来的这个区域代替原来的矩形重叠区域重新计算(跳到第2步)。
5。一直到缩小倍率降为1时仍然有碰撞,则说明2容器对象确实产生了碰撞。那么实际检测面积就是20*20*检测次数。(实际检测中就算测试对象尺寸6K*3K也很少会进行4次以上)

优化结果上对于超大尺寸可以提升5倍效率,小尺寸20*20以内打平,大于20*20的提升50%-500%不等。
不过里面有一些判断并非完善,希望大家在使用过程中多多改进。