等距拓扑排序问题

时间:2017-06-24 19:20:57

标签: javascript html5 canvas isometric

我使用本指南在我的等距游戏中实施了拓扑排序算法https://mazebert.com/2013/04/18/isometric-depth-sorting/


问题

这里有一个小例子(这只是一个绘图来说明我的问题,因为正如我们所说,一张图片胜过千言万语),我和我#39; m 期待左侧,拓扑排序算法的结果正确

example

所以在右图中,问题是该字符框是之前字符,我希望它可以被绘制 AFTER < / strong>喜欢左图



拓扑排序算法代码(Typescript)

private TopologicalSort2() {
    // https://mazebert.com/2013/04/18/isometric-depth-sorting/

    for(var i = 0; i < this.Stage.children.length; i++) {
        var a = this.Stage.children[i];
        var behindIndex = 0;

        for(var j = 0; j < this.Stage.children.length; j++) {
            if(i == j) {
                continue;
            }

            var b = this.Stage.children[j];

            if(!a.isoSpritesBehind) {
                a.isoSpritesBehind = [];
            }

            if(!b.isoSpritesBehind) {
                b.isoSpritesBehind = [];
            }

            if(b.posX < a.posX + a.sizeX && b.posY < a.posY + a.sizeY && b.posZ < a.posZ + a.sizeZ) {
                a.isoSpritesBehind[behindIndex++] = b;
            }
        }

        a.isoVisitedFlag = 0;
    }

    var _sortDepth = 0;

    for(var i = 0; i < this.Stage.children.length; ++i) {
        visitNode(this.Stage.children[i]);
    }

    function visitNode(n: PIXI.DisplayObject) {
        if(n.isoVisitedFlag == 0) {
            n.isoVisitedFlag = 1;

            if(!n.isoSpritesBehind) {
                return;
            }

            for(var i = 0; i < n.isoSpritesBehind.length; i++) {
                if(n.isoSpritesBehind[i] == null) {
                    break;
                } else {
                    visitNode(n.isoSpritesBehind[i]);
                    n.isoSpritesBehind[i] = null;
                }
            }

            n.isoDepth = _sortDepth++;
        }
    }

    this.Stage.children.sort((a, b) => {
        if(a.isoDepth - b.isoDepth != 0) {
            return a.isoDepth - b.isoDepth;
        }

        return 0;
    });
}


的信息

播放器:

posX: [the x coordinate of the player]
posY: [the y coordinate of the player]
posZ: 0

sizeX: 1
sizeY: 1
sizeZ: 1

箱:

posX: [the x coordinate of the box]
posY: [the y coordinate of the box]
posZ: 0

sizeX: 3
sizeY: 1
sizeZ: 1


X和Y轴

img


您是否知道此问题的根源?也许如何解决?

1 个答案:

答案 0 :(得分:2)

确定一个对象是否在另一个对象之前的方法需要更多的线性代数。

首先,我建议翻译来自&#34; world&#34;的坐标。坐标到&#34;视图&#34; 2D坐标,即显示器的行和列。

另请注意,原始Z坐标不会影响排序顺序(假设某个对象沿Z轴被抬起:我们可以找到一个排序顺序,此移动不会产生任何影响)。因此,上述翻译可以假设所有点都在Z = 0。

让我们采取这种设置,但是从上面的#34;&#34;描述,所以当沿着Z轴向下看到游戏场时:

enter image description here

在图片中有7个对象,从0到6编号。游戏中的视线将来自此图片的左下角。我建议翻译某些点的坐标系用红色的行/列轴表示。

每个对象中的白色对角线链接将在算法中翻译和使用的两个点。假设当一个物体在另一个物体前面时,它们的对角线不会相交。如果他们愿意,那就意味着物体在游戏世界中彼此重叠,这意味着它们就像气体,而不是固体:)我会假设情况并非如此。

当在新坐标系中时,一个对象A可以在另一个对象B的前面,B的最左列坐标落在A的两个列坐标之间(反之亦然)。 可能当它们的Z坐标相差足够时,它们实际上并不是这样的重叠,但是我们可以忽略它,因为当没有重叠时,我们无论如何都无法指定某个顺序。

现在,当坐标指示重叠时,必须将对角线(A和B)的坐标与一些线性代数公式进行比较,这将确定哪一个在另一个之前。

这是你改编的功能:

topologicalSort() {
    // Exit if sorting is a non-operation
    if (this.Stage.children.length < 2) return; 
    // Add two translated coordinates, where each of the resulting 
    //   coordinates has a row (top to bottom) and column 
    //   (left to right) part. They represent a position in the final 
    //   rendered view (the screen).  
    // The two pairs of coordinates are translations of the 
    //   points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
    // Z is ignored (0), since it does not influence the order.
    for (let obj of this.Stage.children) {
        obj.leftCol  = obj.posY - obj.posX - obj.sizeX; 
        obj.rightCol = obj.posY - obj.posX + obj.sizeY;
        obj.leftRow  = obj.posY + obj.posX + obj.sizeX;
        obj.rightRow = obj.posY + obj.posX + obj.sizeY;
        obj.isoSpritesBehind = [];
    }

    for(let i = 0; i < this.Stage.children.length; i++) {
        let a = this.Stage.children[i];
        // Only loop over the next objects
        for(let j = i + 1; j < this.Stage.children.length; j++) {
            let b = this.Stage.children[j];
            // Get the two objects in order of left column:
            let c = b.leftCol < a.leftCol ? b : a;
            let d = b.leftCol < a.leftCol ? a : b;
            // See if they overlap in the view (ignoring Z):
            if (d.leftCol < c.rightCol) {
                // Determine which is behind: some linear algebra
                if (d.leftRow < 
                        (d.leftCol - c.leftCol)/(c.rightCol - c.leftCol) 
                        * (c.rightRow - c.leftRow)  + c.leftRow) {
                    // c is in front of d
                    c.isoSpritesBehind.push(d);
                } else { // d is in front of c
                    d.isoSpritesBehind.push(c);
                }
            } // in the else-case it does not matter which one comes first
        }
    }

    // This replaces your visitNode function and call:
    this.Stage.children.forEach(function getDepth(obj) {
        // If depth was already assigned, this node was already visited
        if (!obj.isoDepth) {
            // Get depths recursively, and retain the maximum of those.
            // Add one more to get the depth for the current object
            obj.isoDepth = obj.isoSpritesBehind.length
                ? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
                : 1; // Depth when there is nothing behind it
        }
        return obj.isoDepth; // Return it for easier recursion
    });

    // Sort like you did, but in shorter syntax
    this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}

我添加了一个片段,其中我用最少的代码完成了该类,足以让它运行并输出最终顺序的对象索引号(最初插入时):

&#13;
&#13;
class Game {
    constructor() {
        this.Stage = { children: [] };
    }
    addObject(posX, posY, posZ, sizeX, sizeY, sizeZ) {
        this.Stage.children.push({posX, posY, posZ, sizeX, sizeY, sizeZ, 
                id: this.Stage.children.length}); // add a unique id
    }
    topologicalSort() {
        // Exit if sorting is a non-operation
        if (this.Stage.children.length < 2) return; 
        // Add two translated coordinates, where each of the resulting 
        //   coordinates has a row (top to bottom) and column 
        //   (left to right) part. They represent a position in the final 
        //   rendered view (the screen).  
        // The two pairs of coordinates are translations of the 
        //   points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
        // Z is ignored (0), since it does not influence the order.
        for (let obj of this.Stage.children) {
            obj.leftCol  = obj.posY - obj.posX - obj.sizeX; 
            obj.rightCol = obj.posY - obj.posX + obj.sizeY;
            obj.leftRow  = obj.posY + obj.posX + obj.sizeX;
            obj.rightRow = obj.posY + obj.posX + obj.sizeY;
            obj.isoSpritesBehind = [];
        }
        
        for(let i = 0; i < this.Stage.children.length; i++) {
            let a = this.Stage.children[i];
            // Only loop over the next objects
            for(let j = i + 1; j < this.Stage.children.length; j++) {
                let b = this.Stage.children[j];
                // Get the two objects in order of left column:
                let c = b.leftCol < a.leftCol ? b : a;
                let d = b.leftCol < a.leftCol ? a : b;
                // See if they overlap in the view (ignoring Z):
                if (d.leftCol < c.rightCol) {
                    // Determine which is behind: some linear algebra
                    if (d.leftRow < 
                            (d.leftCol - c.leftCol)/(c.rightCol - c.leftCol) 
                            * (c.rightRow - c.leftRow)  + c.leftRow) {
                        // c is in front of d
                        c.isoSpritesBehind.push(d);
                    } else { // d is in front of c
                        d.isoSpritesBehind.push(c);
                    }
                } // in the else-case it does not matter which one comes first
            }
        }
        
        // This replaces your visitNode function and call:
        this.Stage.children.forEach(function getDepth(obj) {
            // If depth was already assigned, this node was already visited
            if (!obj.isoDepth) {
                // Get depths recursively, and retain the maximum of those.
                // Add one more to get the depth for the current object
                obj.isoDepth = obj.isoSpritesBehind.length
                    ? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
                    : 1; // Depth when there is nothing behind it
            }
            return obj.isoDepth; // Return it for easier recursion
        });

        // Sort like you did, but in shorter syntax
        this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
    }
    toString() { // Just print the ids of the children
        return JSON.stringify(this.Stage.children.map( x => x.id ));
    }
}

const game = new Game();
game.addObject( 2, 2, 0, 1, 1, 1 );
game.addObject( 1, 3, 0, 3, 1, 1 );
game.addObject( 6, 1, 0, 1, 3, 1 );
game.addObject( 9, 3, 0, 1, 1, 1 );
game.addObject( 5, 3, 0, 1, 3, 1 );
game.addObject( 7, 2, 0, 1, 1, 1 );
game.addObject( 8, 2, 0, 3, 1, 1 );
game.topologicalSort();
console.log(game + '');
&#13;
&#13;
&#13;

摘录中的对象与具有相同数字的图片中的对象相同。输出顺序为[0,1,4,2,5,6,3],这是绘制对象的有效序列。