Emax
Emax

Reputation: 1403

Java group tile matrix by largest possible AABB

i'm making a 2D tile based game and i was working with AABB collision algorithm when i got stucked in this problem, i have a matrix of tile:

Tile[][] matrix = new Tile[WIDTH][HEIGHT];

interface Tile {
    public boolean isSolid();
}

based on this matrix i want calculate the AABB pool that is a simple list, defined here:

List<AABB> aabbPool = new ArrayList<AABB>();

class AABB {

    private int x;
    private int y;
    private int w;
    private int h;

    // Getter and Setter // 

}

i'm looking for an algorithm that is capable of iterate the tile matrix and find the largest possible AABB-Rectangle of solid attached tiles in the matrix, let me explain:

Tile representation

Grid = The matrix
White = Unsolid tile
Black = Solid Tile

Given this matrix the algorithm will create the aabb pool, like this Tile result

Red outline = Y preferred aabb
Green outline = X preferred aabb (Y is not possible)
Blue outline = XY group

At the end, i created this script for debug the algorithm

public class AABBDebugger {

    // Public field just for debug
    static class AABB {

        public int xPosition;
        public int yPosition;
        public int width;
        public int height;

    }

    // Public field just for debug
    static class Tile {

        public static final int SIZE = 10;

        public boolean solid;
    }

    public static void main(String[] args) {

        // Matrix size
        int WIDTH = 50;
        int HEIGHT = 50;

        // Declaration of matrix and random initialization
        Tile[][] matrix = new Tile[WIDTH][HEIGHT];
        for (int xCoord = 0; xCoord < WIDTH; xCoord++) {
            for (int yCoord = 0; yCoord < HEIGHT; yCoord++) {
                matrix[xCoord][yCoord] = new Tile();
                matrix[xCoord][yCoord].solid = Math.random() > 0.5;
            }
        }

        // Inizialization of the collission pool
        List<AABB> aabbPool = new ArrayList<AABB>();

        // Magic method that create the collision pool
        magicMethod(matrix, WIDTH, HEIGHT, aabbPool);


        // Rendering of result
        Canvas canvas = new Canvas();
        canvas.setPreferredSize(new Dimension(WIDTH * Tile.SIZE, HEIGHT * Tile.SIZE));

        JFrame frame = new JFrame();
        frame.add(canvas);
        frame.pack();
        frame.setVisible(true);

        while (!Thread.interrupted()) {
            BufferStrategy bufferStrategy;
            while ((bufferStrategy = canvas.getBufferStrategy()) == null) {
                canvas.createBufferStrategy(2);
            }
            Graphics graphics = bufferStrategy.getDrawGraphics();

            for (int xCoord = 0; xCoord < WIDTH; xCoord++) {
                for (int yCoord = 0; yCoord < HEIGHT; yCoord++) {
                    graphics.setColor(matrix[xCoord][yCoord].solid ? Color.BLACK : Color.WHITE);
                    graphics.fillRect(xCoord * Tile.SIZE, yCoord * Tile.SIZE, Tile.SIZE, Tile.SIZE);
                }
            }

            for (AABB aabb : aabbPool) {
                graphics.setColor(Color.RED);
                graphics.drawRect(aabb.xPosition, aabb.yPosition, aabb.width, aabb.height);
            }

            bufferStrategy.show();
            graphics.dispose();
        }

        System.exit(0);
    }


    /*
     * The algorithm that i'm looking for
     * for cycle start from Y rather then X
     */
    private static void magicMethod(Tile[][] matrix, int WIDTH, int HEIGHT, List<AABB> aabbPool) {
        for (int yCoord = 0; yCoord < HEIGHT; yCoord++) {
            AABB aabb = null;
            for (int xCoord = 0; xCoord < WIDTH; xCoord++) {
                if (matrix[xCoord][yCoord].solid) {
                    if (aabb == null) {
                        aabb = new AABB();
                        aabb.yPosition = yCoord * Tile.SIZE;
                        aabb.xPosition = xCoord * Tile.SIZE;
                        aabb.height = Tile.SIZE;
                        aabb.width = Tile.SIZE;
                    } else {
                        aabb.width += Tile.SIZE;
                    }
                } else if (aabb != null) {
                    aabbPool.add(aabb);
                    aabb = null;
                }
            }
            if (aabb != null) {
                aabbPool.add(aabb);
                aabb = null;
            }
        }
    }

}

The script produce this result:

Script Result

but isn't what i expect because this algorithm group the tiles by y and is ok, but not by x when i can, like there

Script Result Expected

Finally (sorry for the long post) the algorithm must respect this rules:

Upvotes: 2

Views: 240

Answers (1)

Kostas Kryptos
Kostas Kryptos

Reputation: 4111

You can still use your method with a slight change: Do not directly store single-tile AABB rectangles grouped by Y (those with width==Tile.SIZE); but store them in a temp HashMap: Map<Integer, List<Integer>>, where the key is the xCoord of each tile and the value yCoord is added to the list). This way you can track which tiles per xCoord have not been grouped already by Y. So, check if width!=Tile.SIZE before adding:

if (aabb.width != Tile.SIZE) {
    aabbPool.add(aabb);
} else {
    addToMap(xCoord,yCoord);
}
aabb = null;

The method addToMap can be implemented as follows:

private static void addToMap(Integer x, Integer y, Map<Integer, List<Integer>> xMap) {
        if (xMap.containsKey(x)) {
            xMap.get(x).add(y);
        } else {
            List<Integer> yList = new ArrayList<Integer>();
            yList.add(y);
            xMap.put(x, yList);
        }
    }

Then, call the following method as a last step of your magicMethod; it will either add single-tile AABB rectangles (not possible to be grouped anyway), or grouped by X (that are not grouped by Y already).

private static void groupByX(Map<Integer, List<Integer>> xMap, List<AABB> aabbPool) {
    //for each X
    for (Entry<Integer, List<Integer>> entry : xMap.entrySet()) {
        List<Integer> curList = entry.getValue();
        if (curList != null) {
            int curY = curList.get(0); //current yCoord
            int curH = 1; //current height
            //for each Y
            for (int i = 1; i < curList.size(); i++) {
                if (curY + curH == curList.get(i)) {
                    curH++;
                } else {
                    aabbPool.add(new AABB(entry.getKey()*Tile.SIZE, curY*Tile.SIZE, Tile.SIZE, curH*Tile.SIZE)); // *Tile.SIZE()
                    //reset
                    curY = curList.get(i);
                    curH = 1;
                }
            }
            //add the last AABB
            aabbPool.add(new AABB(entry.getKey()*Tile.SIZE, curY*Tile.SIZE, Tile.SIZE, curH*Tile.SIZE));
        }
    }
}

This is what you get:

enter image description here

Upvotes: 1

Related Questions