Joining shapes together and tracing their outline

EDIT: Found solution. I've added it after the question.

I'm designing a game for Android and I'm trying to come up with ways to reduce the calculations at the render stage. I've got a method at the moment which takes all the metal and rubber blocks in the level and stores a texture id into a int[][] grid so that the renderer just reads that instead of calculating every blocks tiled textures every frame.

This works fine but now I'm trying to create a list of corner and straight pieces for the level edges and the laser blocks in the level. The level bounds and laser blocks are drawn using a set of straight laser textures and corner textures. I'm not sure how best to tackle working out where not to render lasers where blocks overlap with other blocks and with the level edges. The pictures below shows what I mean:

drawing

ingame

Here you can see the level edge (the L shaped laser path extending beyond the pictures edges) and three/two internal laser blocks (respectively to picture order). As far as I can tell I should create a similar grid to above but with booleans so any square that kills the player on upon touching (highlighted in red) is true and the safe squares are false.

I then first thought of going through all the true (red) cells in the grid and work out what the laser outline would look like using their neighbouring grid cells but I realised this could very difficult so I'm certain now that I use the false squares to find it out. I'm sure I could get a rough solution working by starting at the lower left square of the level bounds and iterate through the grid until I find a false tile (unless the first square is false) and then travel through the grid going right until I reach a true cell to the right and so I would turn left and continue up through the grid until a true is found above to turn left OR a false is found on the right to turn right. I'd repeat this process until I reach my starting false cell.

I came up with this while writing this question out lol. It seems like the easiest way so I guess my question is is this a good way to do it and how would I work out the laser blocks which touch each other but not touch the level bounds as the above method would only trace the outer most laser path.

Thank you for taking the time to read through this. I hope I've explained it well enough and I look forward to any light that can be shed on this subject.


SOLUTION:

    public static boolean[][] laserField = new boolean[(int) Static.WORLD_SIZE][(int)Static.WORLD_SIZE];
    public static List<LaserData> laserData = new ArrayList<LaserData>();
    public static void calcLaserBoundryAreas() {
        laserField = new boolean[(int) Level.levelBounds.bounds.width+5][(int) Level.levelBounds.bounds.height+5];
        for (int i=0;i<laserField.length;i++) {
            for (int j=0;j<laserField[i].length;j++) {
                if(i==0 || i==laserField.length-1 || j==0 || j==laserField[i].length-1)
                    laserField[i][j] = true;
                else
                    laserField[i][j] = false;
            }
        }
        for (LaserBlock lBlock : lBlocks) {
            int cols = (int)lBlock.bounds.width;
            int rows = (int)lBlock.bounds.height;
            float startX = lBlock.position.x - (cols-1f)/2f;
            float startY = lBlock.position.y - (rows-1f)/2f;
            for (int i=0;i<cols;i++) {
                for (int j=0;j<rows;j++) {
                    addLaserCell(startX+i, startY+j);
                }
            }
        }
        addLaserData();
    }

private static void addLaserCell(float x, float y) {
    int cellX = (int)(x- Level.levelBounds.bounds.lowerLeft.x+2);
    int cellY = (int)(y- Level.levelBounds.bounds.lowerLeft.y+2);
    if (cellX < 0 || cellX > laserField.length-1)           return;
    if (cellY < 0 || cellY > laserField[cellX].length-1)    return;
    laserField[cellX][cellY] = true;
}

private static void addLaserData() {
    laserData = new ArrayList<LaserData>();
    for (int i=1;i<laserField.length-1;i++) {
        for (int j=1;j<laserField[i].length-1;j++) {
            if (!laserField[i][j]) {
                checkNeighbours(i,j);
            }   
        }
    }
    optimiseLaserData();
}

private static void checkNeighbours(int x, int y) {
    boolean u = laserField[x][y+1];
    boolean ul = laserField[x-1][y+1];
    boolean l = laserField[x-1][y];
    boolean bl = laserField[x-1][y-1];
    boolean b = laserField[x][y-1];
    boolean br = laserField[x+1][y-1];
    boolean r = laserField[x+1][y];
    boolean ur = laserField[x+1][y+1];

    /*
     *  TOP LEFT CORNER 
     */
    float posX, posY;
    posX = Level.levelBounds.bounds.lowerLeft.x+x-2.5f;
    posY = Level.levelBounds.bounds.lowerLeft.y+y-1.5f;
    if(u && ul && l)
        laserData.add(new LaserData(posX, posY, true, 0, 0));
    else if(!u && ul && l)
        laserData.add(new LaserData(posX, posY, false, 1, 0));
    else if(!u && ul && !l)
        laserData.add(new LaserData(posX, posY, true, 0, 2));

    /*
     *  BOTTOM LEFT CORNER 
     */
    posX = Level.levelBounds.bounds.lowerLeft.x+x-2.5f;
    posY = Level.levelBounds.bounds.lowerLeft.y+y-2.5f;
    if(l && bl && b)
        laserData.add(new LaserData(posX, posY, true, 0, 1));
    else if(!l && bl && b)
        laserData.add(new LaserData(posX, posY, false, 1, 1));
    else if(!l && bl && !b)
        laserData.add(new LaserData(posX, posY, true, 0, 3));

    /*
     *  BOTTOM RIGHT CORNER 
     */
    posX = Level.levelBounds.bounds.lowerLeft.x+x-1.5f;
    posY = Level.levelBounds.bounds.lowerLeft.y+y-2.5f;
    if(b && br && r)
        laserData.add(new LaserData(posX, posY, true, 0, 2));
    else if(!b && br && r)
        laserData.add(new LaserData(posX, posY, false, 1, 2));
    else if(!b && br && !r)
        laserData.add(new LaserData(posX, posY, true, 0, 0));

    /*
     *  TOP RIGHT CORNER 
     */
    posX = Level.levelBounds.bounds.lowerLeft.x+x-1.5f;
    posY = Level.levelBounds.bounds.lowerLeft.y+y-1.5f;
    if(r && ur && u)
        laserData.add(new LaserData(posX, posY, true, 0, 3));
    else if(!r && ur && u)
        laserData.add(new LaserData(posX, posY, false, 1, 3));
    else if(!r && ur && !u)
        laserData.add(new LaserData(posX, posY, true, 0, 1));

}

private static void optimiseLaserData() {
    List<LaserData> optiLaserData = new ArrayList<LaserData>();
    for(LaserData ld : laserData) {
        if(ld.cornerPiece)
            optiLaserData.add(ld);
        else if(ld.dir == 0 || ld.dir == 2){
            float x = ld.x;
            float bottomY = ld.y;
            float topY = ld.y;
            float count = 1;
            while (searchStraightLaserData(laserData, x, topY+1, ld.dir)) {
                count++;
                topY++;
            }
            while (searchStraightLaserData(laserData, x, bottomY-1, ld.dir)) {
                count++;
                bottomY--;
            }
            float centerY = bottomY + (topY-bottomY)/2;
            if(!searchStraightLaserData(optiLaserData, x, centerY, ld.dir))
                optiLaserData.add(new LaserData(x, centerY, false, count, ld.dir));
        } else {
            float y = ld.y;
            float leftX = ld.x;
            float rightX = ld.x;
            float count = 1;
            while (searchStraightLaserData(laserData, rightX+1, y, ld.dir)) {
                count++;
                rightX++;
            }
            while (searchStraightLaserData(laserData, leftX-1, y, ld.dir)) {
                count++;
                leftX--;
            }
            float centerX = leftX + (rightX-leftX)/2;
            if(!searchStraightLaserData(optiLaserData, centerX, y, ld.dir))
                optiLaserData.add(new LaserData(centerX, y, false, count, ld.dir));
        }
    }
    laserData = optiLaserData;
}

private static boolean searchStraightLaserData(List<LaserData> data, float x, float y, int dir) {
    for(LaserData ld : data)
        if(ld.x == x && ld.y == y && ld.dir == dir && !ld.cornerPiece)
            return true;
    return false;
}

These methods first create a boolean grid that is the size of the level edge bounds with a 1 square extra edge on each side. This is initialised to false to represent safe areas and the extra edge is set to true so that we have a kind of hollow box. The extra edge helps later by eliminating the need to check for incorrect indices on the laserField.

After the level extents are mapped to a grid individual cells are changed to true where ever is covered by a laser block.

Once the boolean grid is fully mapped it then iterates through each grid cell and when it finds a cell which is false it passes it grid coordinates to the next method which looks at 12 different neighbour patterns to determine if any lasers should be rendered around this cell. The LaserData constructor takes the following args (float x, float y, boolean cornerPiece, float length, int direction)

The last section does a brute force search to check if any adjacent straight pieces can be replaced by a single longer straight piece to save rendering extra sprites.

The renderer can then just read through the laserData list each frame and it has all the information it needs to render the correct texture, its position, length etc...

NOTE: The width and height of the Level bounds are 3 units smaller than the actual playing area to account for width of player outside the boundry. That's where the levelBounds.lowerleft+5, +2 and + 1.5f etc... come from. It's a little hacky I know but it's old code and I daren't touch it xD

链接地址: http://www.djcxy.com/p/66570.html

上一篇: 奇怪的碰撞行为

下一篇: 连接形状并追踪轮廓