1

我正在尝试实现用于碰撞检测的矩形四叉树(而不是点)。

由于某种原因,并不总是检测到重叠/交叉/碰撞。我怀疑这与插入的对撞机查找附近对撞机的方式有关(参见 Quadtree.as 的“碰撞 A”和“碰撞 B”部分)。谁能确定为什么会发生这种情况?

下面是我的四叉树代码,以及其他几个类,当它们一起编译时,当我说“四叉树”“不准确”时,它们会告诉你我的意思。您只需要查看 Quadtree.as,但是将它与其他类一起编译应该可以帮助您理解我的问题。

谢谢!

四叉树.as:

package
{
    import flash.geom.Rectangle;
    import flash.display.Sprite;

    public class Quadtree
    {
        private var root:Node;

        public function Quadtree( width:Number, height:Number, depth:int )
        {
            root = new Node( 0, 0, width, height, depth );
        }

        public function insert( collider:Collider ) : void
        {
            var current:Node = root;
            var colliderRectangle:Rectangle = collider.colliderRectangle;

            // Insertion is not recursive, to reduce function calls and make the tree faster.
            // A break is called when the collider is inserted into a particular node.
            while( true )
            {
                // BEGIN COLLISION A
                // Collide the collider being inserted with the colliders already present in the tree.
                // At least, it will see all of the colliders it passes by as it goes down.
                var colliders:Array = current.colliders;
                var max:int = colliders.length;
                for( var i:int = 0; i < max; i++ )
                {
                    // Narrow collision detection (and response) is performed by the Collider.collide() function.
                    var other:Collider = colliders[i];
                    collider.collide( other );
                    other.collide( collider );
                }
                // END COLLISION A

                // If the current node is at the maximum depth (lower is higher),
                // insert the collider into the current node.
                if( current.depth == 0 )
                {
                    current.colliders.push( collider );
                    break;
                }

                // Determine which quadrants the collider intersects with.
                var currentX:Number = current.x;
                var currentY:Number = current.y;
                var halfWidth:Number = current.halfWidth;
                var halfHeight:Number = current.halfHeight;
                var onLeft:Boolean = ( currentX + current.width ) - colliderRectangle.right >= halfWidth;
                var onTop:Boolean = ( currentY + current.height ) - colliderRectangle.bottom >= halfHeight;
                var onRight:Boolean = colliderRectangle.left - currentX >= halfWidth;
                var onBottom:Boolean = colliderRectangle.top - currentY >= halfHeight;

                // If the collider is not completely contained in one quadrant of the current node,
                // insert the collider into the current node, then check if the collider collides with
                // any of the colliders in the descendants of the current node (see COLLISION B).
                if( ( !onTop && !onBottom ) || ( !onLeft && !onRight ) )
                {
                    current.colliders.push( collider );

                    // BEGIN COLLISION B
                    var tl:Node = current.tl;
                    var tr:Node = current.tr;
                    var bl:Node = current.bl;
                    var br:Node = current.br;
                    if( tl )
                    {
                        tl.collideDescendants( collider );
                    }
                    if( tr )
                    {
                        tr.collideDescendants( collider );
                    }
                    if( bl )
                    {
                        bl.collideDescendants( collider );
                    }
                    if( br )
                    {
                        br.collideDescendants( collider );
                    }
                    // END COLLISION B

                    break;
                }
                // If the collider does not span more than one quadrant, determine which of the quadrants contain it.
                else if( onTop && onLeft )
                {
                    if( !current.tl )
                    {
                        current.tl = new Node( currentX, currentY, halfWidth, halfHeight, current.depth );
                    }
                    current = current.tl;
                }
                else if( onTop && onRight )
                {
                    if( !current.tr )
                    {
                        current.tr = new Node( currentX + halfWidth, currentY, halfWidth, halfHeight, current.depth );
                    }
                    current = current.tr;
                }
                else if( onBottom && onLeft )
                {
                    if( !current.bl )
                    {
                        current.bl = new Node( currentX, currentY + halfHeight, halfWidth, halfHeight, current.depth );
                    }
                    current = current.bl;
                }
                else if( onBottom && onRight )
                {
                    if( !current.br )
                    {
                        current.br = new Node( currentX + halfWidth, currentY + halfHeight, halfWidth, halfHeight, current.depth );
                    }
                    current = current.br;
                }
            }
        }

        public function clear() : void
        {
            root.clear();
        }
    }
}

import flash.display.Sprite;
import flash.geom.Rectangle;

class Node
{
    public var extent:Rectangle;
    public var tl:Node;
    public var tr:Node;
    public var bl:Node;
    public var br:Node;
    public var colliders:Array;
    public var depth:int;

    public var x:Number;
    public var y:Number;
    public var width:Number;
    public var height:Number;
    public var halfWidth:Number;
    public var halfHeight:Number;

    public function Node( x:Number, y:Number, width:Number, height:Number, depth:int )
    {
        extent = new Rectangle( x, y, width, height );
        halfWidth = width * 0.5;
        halfHeight = height * 0.5;
        this.depth = depth - 1;
        colliders = [];

        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    public function collideDescendants( collider:Collider ) : void
    {
        var max:int = 0;
        var other:Collider;
        for( var i:int = 0; i < max; i++ )
        {
            other = colliders[i];
            collider.collide( other );
            other.collide( collider );
        }

        if( depth == 0 )
        {
            return;
        }

        var colliderRectangle:Rectangle = collider.colliderRectangle;
        var left:Number = colliderRectangle.left;
        var right:Number = colliderRectangle.right;
        var top:Number = colliderRectangle.top;
        var bottom:Number = colliderRectangle.bottom;
        if( tl && !( bottom < tl.y || tl.y + tl.height < top || right < tl.x || tl.x + width < left ) )
        {
            tl.collideDescendants( collider );
        }
        if( tr && !( bottom < tr.y || tr.y + tr.height < top || right < tr.x || tr.x + width < left ) )
        {
            tr.collideDescendants( collider );
        }
        if( bl && !( bottom < bl.y || bl.y + bl.height < top || right < bl.x || bl.x + width < left ) )
        {
            bl.collideDescendants( collider );
        }
        if( br && !( bottom < br.y || br.y + br.height < top || right < br.x || br.x + width < left ) )
        {
            br.collideDescendants( collider );
        }
    }

    public function clear() : void
    {
        colliders.splice( 0, colliders.length );

        if( tl )
        {
            tl.clear();
        }
        if( tr )
        {
            tr.clear();
        }
        if( bl )
        {
            bl.clear();
        }
        if( br )
        {
            br.clear();
        }
    }
}

主要作为:

package 
{
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.text.TextField;
    import flash.text.TextFormat;
    public class Main extends Sprite 
    {
        public function Main()
        {
            if (stage) init();
            else addEventListener( Event.ADDED_TO_STAGE, init );
        }
        private function init( e:Event = null ) : void 
        {
            removeEventListener( Event.ADDED_TO_STAGE, init );

            var objects:Array = [];
            var quadtree:Quadtree = new Quadtree( 640, 480, 5 );
            var max:int = 50;
            for( var i:int = 0; i < max; i++ )
            {
                var object:TestObject = new TestObject();
                addChild( object );
                objects.push( object );
            }

            var bruteMsg:String = "Using BRUTE FORCE! Click to switch to QUADTREE";
            var quadMsg:String = "Using QUADTREE! Click to switch to BRUTE FORCE";

            var text:TextField = new TextField();
            text.text = quadMsg;
            text.selectable = false;
            text.width = 300;
            text.defaultTextFormat = new TextFormat( "Arial" );
            text.setTextFormat( text.defaultTextFormat );
            addChild( text );

            var last:Date = new Date();
            var useQuadtree:Boolean = true;

            addEventListener( Event.ENTER_FRAME, function( f:Event ) : void {
                var now:Date = new Date();
                var deltaTime:Number = ( now.getTime() - last.getTime() ) * 0.001;
                last = now;
                for( i = 0; i < max; i++ )
                {
                    object = objects[i];
                    object.update( deltaTime );
                }
                if( useQuadtree )
                {
                    quadtree.clear();
                    for( i = 0; i < max; i++ )
                    {
                        object = objects[i];
                        quadtree.insert( object.collider );
                    }
                }
                else
                {
                    for( i = 0; i < max; i++ )
                    {
                        var a:TestObject = objects[i];
                        for( var j:int = i + 1; j < max; j++ )
                        {
                            var b:TestObject = objects[j];
                            a.collider.collide( b.collider );
                            b.collider.collide( a.collider );
                        }
                    }
                }
            } );
            stage.addEventListener( MouseEvent.CLICK, function( f:Event ) : void {
                text.text = useQuadtree ? bruteMsg : quadMsg;
                useQuadtree = !useQuadtree;
            } );
        }
    }
}

测试对象.as:

package
{
    import flash.display.Sprite;
    import flash.geom.Point;

    public class TestObject extends Sprite
    {
        private static const WIDTH:int = 20;
        private static const HEIGHT:int = 30;
        private static const SPEED:Number = 100;
        private static const CANVAS_WIDTH:Number = 640;
        private static const CANVAS_HEIGHT:Number = 480;
        public var direction:Point;
        public var collider:TestCollider;
        public function TestObject()
        {
            super();
            graphics.lineStyle( 1, 0x0000FF );
            graphics.drawRect( 0, 0, WIDTH, HEIGHT );
            x = Math.random() * ( CANVAS_WIDTH - WIDTH );
            y = Math.random() * ( CANVAS_HEIGHT - HEIGHT );
            direction = new Point( Math.random() - 0.5, Math.random() - 0.5 );
            direction.normalize( 1 );
            collider = new TestCollider( this, x, y, width, height );
        }
        public function update( deltaTime:Number ) : void
        {
            var displacement:Number = SPEED * deltaTime;
            x = x + direction.x * displacement;
            y = y + direction.y * displacement;

            if( x < 0 )
            {
                x = 0;
                direction.x *= -1;
            }
            else if( x + WIDTH > CANVAS_WIDTH )
            {
                x = CANVAS_WIDTH - WIDTH;
                direction.x *= -1;
            }
            if( y < 0 )
            {
                y = 0;
                direction.y *= -1;
            }
            else if( y + HEIGHT > CANVAS_HEIGHT )
            {
                y = CANVAS_HEIGHT - HEIGHT;
                direction.y *= -1;
            }

            collider.colliderRectangle.x = x;
            collider.colliderRectangle.y = y;
        }
    }
}

对撞机.as:

package  
{
    import flash.geom.Rectangle;

    public class Collider
    {
        virtual public function get colliderRectangle() : Rectangle { return null; };
        virtual public function collide( collider:Collider ) : void { /* NARROW COLLISION DETECTION AND COLLISION RESPONSE CODE HERE */ }
    }
}

TestCollider.as:

package  
{
    import flash.geom.Rectangle;

    public class TestCollider extends Collider
    {
        private var rectangle:Rectangle;
        private var parent:TestObject;

        public function TestCollider( parent:TestObject, x:Number, y:Number, width:Number, height:Number )
        {
            this.parent = parent;
            rectangle = new Rectangle( x, y, width, height );
        }

        override public function get colliderRectangle() : Rectangle
        {
            return rectangle;
        }

        override public function collide( collider:Collider ) : void 
        {
            var otherRect:Rectangle = collider.colliderRectangle;
            if( rectangle.intersects( otherRect ) )
            {
                var intersection:Rectangle = rectangle.intersection( otherRect );
                if( intersection.width < intersection.height )
                {
                    parent.x += intersection.width * 0.5 * ( rectangle.x < otherRect.x ? -1 : 1 );
                }
                else
                {
                    parent.y += intersection.height * 0.5 * ( rectangle.y < otherRect.y ? -1 : 1 );
                }
                parent.direction.x *= ( ( parent.direction.x > 0 && otherRect.x > rectangle.x ) || ( parent.direction.x < 0 && otherRect.x < rectangle.x ) ? -1 : 1 );
                parent.direction.y *= ( ( parent.direction.y > 0 && otherRect.y > rectangle.y ) || ( parent.direction.y < 0 && otherRect.y < rectangle.y ) ? -1 : 1 );
            }
        }
    }
}
4

1 回答 1

4

这是一个如此尴尬和笨拙的错误。如果你查看 Quadtree.as 下的 Node.collideDescendants(),你会注意到第一行是

var max:int = 0;

什么时候应该

var max:int = colliders.length;

难怪新插入的对撞机将无法看到属于其下方节点的对撞机(属于该对撞机插入的节点的后代的节点)。

尴尬,真的。

于 2010-01-04T23:35:10.913 回答