Sorting and filtering arrays in script



Using arrays to manage lists of items is a common requirement in scripts. Sometimes you might want to sort the items in an array in a certain order, or filter them to remove some items from the list. Rubescript provides a method of sorting and filtering bodies, fixtures, joints, images and vertices, based on arbitrary characteristic of the items.

This is done by creating a comparison function to decide whether two items are in the correct order (for sorting) or a validation function to check whether an item should be included in the list (for filtering). The call signatures for these functions are like this:
    bool mysort(item a, item b);
    bool myfilter(item a);
Of course, the 'item' here would be replaced by the item type you are dealing with. After defining these functions, you can use them with the appropriate sort or filter function. For example, sorting and filter of bodies can be done with these functions:
    body[] sortBodies( body[], bodySortFunc@ );
    body[] filterBodies( body[], bodyFilterFunc@ );
This whole idea of defining a function and giving it to another function as a 'callback' can be a bit confusing at first, so let's take a look at some examples.


Filtering example
Let's suppose we want to write a script that will select all fixtures which have more than four vertices. First we will need to write the validation function, which is a function matching the call signature shown above, and should return true if the fixture in the parameter has more than four vertices.
    bool vertexCountFilter( fixture f ) {
        return f.getNumVertices() > 4;
    }
We can now pass this to the filterFixtures function. The initial list of fixures we want to start with is simply every fixture in the scene, which we can get with getAllFixtures(). The filterFixtures function does not alter the passed-in array, so we'll need to assign the result to another variable to make use of it later.
    fixture[] fixturesWithMoreThanFourVertices = filterFixtures( getAllFixtures(), vertexCountFilter );
Once we have obtained this list, which will only contain the fixtures we want, the fixtures can be selected with the select( item[] ) function. So the full script would be as below (please see the topic about defining functions in script for an explanation of using the main() function).
    bool vertexCountFilter( fixture f ) {
        return f.getNumVertices() > 4;
    }

    void main() {
        select( filterFixtures( getAllFixtures(), vertexCountFilter ) );
    }
Here is an example of what this script would do when run with some fixtures of various vertex counts.


Sorting example
Sorting follows the same procedure as filtering, except the comparison function checks to see if the two given parameters were passed in the correct order. Let's suppose that we would like to sort the bodies in the screenshot above in order of their vertex counts, and arrange them in a line.

The comparison function could look something like this (for brevity, the possibility that a body may have no fixtures at all is ignored):
    bool vertexCountComparison( body a, body b ) {
        return a.getFixtures()[0].getNumVertices() < b.getFixtures()[0].getNumVertices();
    }
We can now pass this to the sortBodies function. To narrow down the scope of this function, we'll use only the currently selected bodies as the initial list to sort. The sortBodies function does not alter the passed-in array, so we'll need to assign the result to another variable to make use of it later.
    body[] bodiesOrderedByVertexCount = sortBodies( getSelectedBodies(), vertexCountComparison );
Once we have sorted the list we can loop through it and set the position of the bodies with setPosition. The full script would turn out something like this:
    bool vertexCountComparison( body a, body b ) {
        return a.getFixtures()[0].getNumVertices() < b.getFixtures()[0].getNumVertices();
    }

    void main() {
        body[] bodiesOrderedByVertexCount = sortBodies( getSelectedBodies(), vertexCountComparison );
        for (uint i = 0; i < bodiesOrderedByVertexCount.length; i++) {
            bodiesOrderedByVertexCount[i].setPosition( i, 0 );
        }
    }
Here is an example of what this script would do when run with the same scene as above.


Class property availability Between all the script classes there is a huge number properties that can potentially be accessed, and the filtering and sorting methods discussed here depend on having access to these properties. At the time of writing (Nov 2012) a somewhat limited set of properties comprising of mainly the properties which have been necessary during development, along with some other commonly used properties, has been exposed to rubescript.

However, exposing additional properties is a relatively simple affair, so if there are properties you want to access from script please don't hesitate to drop a note in the feedback dialog to make a feature request, and they will be added as soon as possible.