具有指定z-index值的最佳渲染绘制顺序函数

时间:2015-09-02 22:38:11

标签: java sorting libgdx

我最近发现LibGDX中的默认可渲染排序功能并不完全符合我的需要。 (见; Draw order changes strangely as camera moves?
当它们应该在后面呈现时,基本上是在前面呈现的一些对象。

幸运的是,所讨论的可更换物总是有一种保证关系。物体彼此连接,因此当一个物体移动另一个物体时。一个对象可以被视为字面上“固定”到另一个对象,所以总是在前面。

这让我觉得如果我为每个对象指定了一个“z-index”(int)和“groupname”(String),我可以手动接管绘制顺序,对于具有相同组名的东西,确保它们按照z-index指定的顺序位于列表中彼此相邻的位置。 (从低到高)

 //For example an array of renderables like
 0."testgroup2",11
 1."testgroup",20
 2."testgroup2",10 
 3.(no zindex attribute)
 4."testgroup",50
 //Should sort to become
 0."testgroup",20
 1."testgroup",50
 2.(no zindex attribute)
 3."testgroup2",10
 4."testgroup2",11

 // assuming the object2 in testgroup2 are closer to the camera, the one without a index second closest, and the rest furthest<br>
 //(It is assumed that things within the same group wont be drastically different distances)

我在libgdx中实现了一个排序系统来执行此操作,如下所示;

/**
 * The goal of this sorter is to sort the renderables the same way LibGDX would do normally (in DefaultRenderableSorter)<br>
 * except if they have a ZIndex Attribute.<br>
 * A Zindex attribute provides a groupname string and a number.<br>
 * Renderables with the attribute are placed next to others of the same group, with the order within the group determined by the number<br>
 * 
 * For example an array of renderables like;<br><br>
 * 0."testgroup",20<br>
 * 1."testgroup2",10<br>
 * 2.(no zindex attribute)<br>
 * 3."testgroup",50<br>
 * <br>Should become;<br><br>
 * 0."testgroup",20<br>
 * 1."testgroup",50<br>
 * 2.(no zindex attribute)<br>
 * 3."testgroup2",10<br>
 * <br> 
 * assuming the object in testgroup2 is closer to the camera, the one without a index second closest, and the rest furthest<br>
 * (It is assumed that things within the same group wont be drastically different distances)<br>
 * 
 * @param camera - the camera in use to determine normal sort order when we cant place in a existing group
 * @param resultList - an array of renderables to change the order of
 */
private void customSorter(Camera camera, Array<Renderable> resultList) {

    //make a copy of the list to sort. (This is probably a bad start)
    Array <Renderable> renderables = new Array <Renderable> (resultList);

    //we work by clearing and rebuilding the Renderables array (probably not a good method)
    resultList.clear();

    //loop over the copy we made
    for (Renderable o1 : renderables) {

        //depending of if the Renderable as a ZIndexAttribute or not, we sort it differently
        //if it has one we do the following....
        if (o1.material.has(ZIndexAttribute.ID)){

            //get the index and index group name of it.
            int      o1Index   =  ((ZIndexAttribute)o1.material.get(ZIndexAttribute.ID)).zIndex;
            String o1GroupName =  ((ZIndexAttribute)o1.material.get(ZIndexAttribute.ID)).group;

            //setup some variables
            boolean placementFound = false; //Determines if a placement was found for this renderable (this happens if it comes across another with the same groupname)
            int defaultPosition = -1; //if it doesn't find another renderable with the same groupname, this will be its position in the list. Consider this the "natural" position based on distance from camera

            //start looping over all objects so far in the results (urg, told you this was probably not a good method)
            for (int i = 0; i < resultList.size; i++) {

                //first get the renderable and its ZIndexAttribute (null if none found)
                Renderable o2 = resultList.get(i);
                ZIndexAttribute o2szindex = ((ZIndexAttribute)o2.material.get(ZIndexAttribute.ID));

                if (o2szindex!=null){
                    //if the renderable we are comparing too has a zindex, then we get its information
                    int    o2index    = o2szindex.zIndex;
                    String o2groupname = o2szindex.group;       

                    //if its in the same group as o1, then we start the processing of placing them nexto eachother
                    if (o2groupname.equals(o1GroupName)){

                        //we either place it in front or behind based on zindex
                        if (o1Index<o2index){
                            //if lower z-index then behind it
                            resultList.insert(i, o1);
                            placementFound = true;
                            break;
                        }

                        if (o1Index>o2index){
                            //if higher z-index then it should go in front UNLESS there is another of this group already there too
                            //in which case we just continue (which will cause this to fire again on the next renderable in the inner loop)
                            if (resultList.size>(i+1)){

                                Renderable o3 = resultList.get(i+1);
                                ZIndexAttribute o3szindex = ((ZIndexAttribute)o3.material.get(ZIndexAttribute.ID));

                                if (o3szindex!=null){
                                    String o3groupname = o3szindex.group;   
                                    if (o3groupname!=null && o3groupname.equals(o1GroupName)){
                                        //the next element is also a renderable with the same groupname, so we loop and test that one instead   
                                        continue;
                                    }
                                }

                            }
                        //  Gdx.app.log("zindex", "__..placeing at:"+(i+1));
                            //else we place after the current one
                            resultList.insert(i+1, o1);
                            placementFound = true;
                            break;
                        }

                    }

                }


                //if no matching groupname found we need to work out a default placement.
                int placement = normalcompare(o1, o2); //normal compare is the compare function in DefaultRenderableSorter. 

                if (placement>0){
                    //after then we skip
                    //(we are waiting till we are either under something or at the end

                } else {
                    //if placement is before, then we remember this position as the default (but keep looking as there still might be matching groupname, which should take priority)                                           
                    defaultPosition = i;
                    //break; //break out the loop
                }


            }

            //if we have checked all the renderables positioned in the results list, and none were found with matching groupname
            //then we use the defaultposition to insert it
            if (!placementFound){
                //Gdx.app.log("zindex", "__no placement found using default which is:"+defaultPosition);
                if (defaultPosition>-1){
                    resultList.insert(defaultPosition, o1);
                } else {
                    resultList.add(o1);
                }

            }

            continue;

        }

        //...(breath out)...
        //ok NOW we do placement for things that have no got a ZIndexSpecified
        boolean placementFound = false;

        //again, loop over all the elements in results
        for (int i = 0; i < resultList.size; i++) {

            Renderable o2 = resultList.get(i);

            //if not we compare by default to place before/after
            int placement = normalcompare(o1, o2);

            if (placement>0){
                //after then we skip
                //(we are waiting till we are either under something or at the end)
                continue;
            } else {
                //before                    
                resultList.insert(i, o1);
                placementFound = true;
                break; //break out the loop
            }


        }
        //if no placement found we go at the end by default
        if (!placementFound){
            resultList.add(o1);

        };


    } //go back to check the next element in the incomeing list of renderables (that is, the copy we made at the start)

    //done


}


//Copy of the default sorters compare function
//;
private Camera camera;
private final Vector3 tmpV1 = new Vector3();
private final Vector3 tmpV2 = new Vector3();

public int normalcompare (final Renderable o1, final Renderable o2) {
    final boolean b1 = o1.material.has(BlendingAttribute.Type) && ((BlendingAttribute)o1.material.get(BlendingAttribute.Type)).blended;
    final boolean b2 = o2.material.has(BlendingAttribute.Type) && ((BlendingAttribute)o2.material.get(BlendingAttribute.Type)).blended;
    if (b1 != b2) return b1 ? 1 : -1;
    // FIXME implement better sorting algorithm
    // final boolean same = o1.shader == o2.shader && o1.mesh == o2.mesh && (o1.lights == null) == (o2.lights == null) &&
    // o1.material.equals(o2.material);
    o1.worldTransform.getTranslation(tmpV1);
    o2.worldTransform.getTranslation(tmpV2);
    final float dst = (int)(1000f * camera.position.dst2(tmpV1)) - (int)(1000f * camera.position.dst2(tmpV2));
    final int result = dst < 0 ? -1 : (dst > 0 ? 1 : 0);
    return b1 ? -result : result;
}

据我所知,我的customSorter函数产生了我想要的顺序 - 现在看来它们是按照正确的顺序绘制的。

然而,这似乎也是一个hackjob,我相信我的排序算法非常低效。

我想咨询如何;

a)改进我自己的算法,特别是在进行跨平台LibGDX开发时要记住的任何怪癖(即数组类型,关于android / web等的内存管理)

b)替代更有效的解决方案,具有与正常绘制顺序排序类似的“z索引覆盖”。

注释;  。分组是必要的。这是因为虽然事物在一组中相对于彼此牢固地被卡住,但是组本身也​​可以在彼此前面/后面移动。 (但不是之间)。这使得绘制顺序的“全局”覆盖变得棘手,而不是每个组的本地覆盖。

。如果它有帮助,我可以以任何方式添加/更改zindexattribute对象。

。我想某种方式“预先存储”数组中的每组对象可以帮助事情,但不是100%确定如何。

1 个答案:

答案 0 :(得分:1)

首先,如果不需要,请不要复制列表。带有renderables的列表可能非常庞大,因为它也可能包含资源。复制将非常缓慢。如果你需要一些本地的东西并且需要性能,那么试着把它作为最终的,因为它可以提高性能。

因此,一个简单的方法是Java的默认排序。您需要为您的类实现一个Comperator,例如带有z索引的Class可能如下所示:

public class MyRenderable {
    private float z_index;
    public MyRenderable(float i)
    {
        z_index = i;
    }

    public float getZ_index() {
        return z_index;
    }

    public void setZ_index(float z_index) {
        this.z_index = z_index;
    }
}

如果你想要更快的排序,因为你的列表在运行时不会改变那么多,你可以实现一个插入排序,因为如果列表是预先排序的那样,它可以做得更快。如果它没有预先排序,它确实需要更长的时间,但一般来说它应该只是第一个排序调用,在你的情况下它很多无序。

private void sortList(ArrayList<MyRenderable> array) {
    // double starttime = System.nanoTime();
    for (int i = 1; i < array.size(); i++) {
        final MyRenderable temp = array.get(i);
        int j = i - 1;

        while (j >= 0 && array.get(j).getZ_index() < temp.getZ_index()) {
            array.set(j + 1, array.get(j));
            j--;
        }
        array.set(j + 1, temp);
    }
    // System.out.println("Time taken: " + (System.nanoTime() - starttime));
}

要使用此方法,只需使用数组

调用它即可
sortList(renderbales);

在您的情况下,您需要处理没有Z索引的那些。也许你可以给他们一个0,因为他们会在正确的位置排序(我猜)。否则,您可以使用z情况下的给定方法和非z情况下的常规方法。

在评论中的对话之后。我不认为将所有内容都推入一个列表是个好主意。它很难排序,而且速度很慢。更好的方法是列出一组。由于您想拥有组,因此编程组。不要使用字符串名称,使用ID或类型(更容易排序,它并不重要)。所以一个简单的小组就是这样:

public class Group{
//think about privates and getters or methods to add things which also checks some conditions and so on
    public int groupType; 
    public ArrayList<MyRenderable> renderables;
}

现在你所有的小组都进入了一个列表。 (这包含所有的渲染文件)

ArrayList<Group> allRenderables = new ArrayList<>();

最后但并非最不重要的是对组进行排序并对可渲染项进行排序。因为我不认为您的组ID /名称会在运行时更改,所以将它们排序一次,甚至使用SortedSet而不是ArrayList。但基本上整个排序看起来像这样:

    for(Group g: allRenderables)
        sortRenderables(g.renderables); //now every group is sorted
    //now sort by group names
    sortGroup(allRenderables);

使用如下所示的以​​下insertionsorts

public static void sortRenderables(ArrayList<MyRenderable> array) {
    for (int i = 1; i < array.size(); i++) {
        final MyRenderable temp = array.get(i);
        int j = i - 1;

        while (j >= 0 && array.get(j).getZ_index() < temp.getZ_index()) {
            array.set(j + 1, array.get(j));
            j--;
        }
        array.set(j + 1, temp);
    }
}

public static void sortGroup(ArrayList<Group> array) {
    for (int i = 1; i < array.size(); i++) {
        final Group temp = array.get(i);
        int j = i - 1;

        while (j >= 0 && array.get(j).groupType < temp.groupType) {
            array.set(j + 1, array.get(j));
            j--;
        }
        array.set(j + 1, temp);
    }
}