Rechtecke anordnen, aber wie?

Hallo

Ich hätte eine Frage bezüglich eines Problems wo ich keinen Plan habe wie man es lösen könnte.
Es geht um das Anordnen von Rechtecken im 2D Raum.

public class Item{
 int xPos,yPos,height,width;
}

Ich generiere mir dann 150 normal verteilte Items.

Zu Test zwecken mappe ich dann alle Items auf ein Array. dabei wird jeder index um 1 inkrementiert wenn es belegt ist.

Das Ziel:
-Keine Überlappungen
-Jedes Item soll an mindestens einer Stelle mit einem anderen zusammentreffen.

Achtung: Ich versuche nicht den Array zu minimieren. Es sollten immer noch Freiräume zwischen den Items bestehen bleiben.

Hier ist Testprogrammchen:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package test;

import java.util.ArrayList;
import java.util.Random;

/**
 *
 * @author MichaelZuegg
 */
public class Test {

    

    public static void main(String[] args) {
        Test test = new Test();
        test.generate();
    }

    public void generate() {
        DungeonGenerator generator = new DungeonGenerator(100, 5, 6, 20);
        generator.generate(2);
        generator.map2Array();
    }

    class GaussianRandom {

        int seed;
        int mean;
        int variance;
        Random random;

        public GaussianRandom(int seed, int mean, int variance) {
            this.seed = seed;
            this.mean = mean;
            this.variance = variance;
            random = new Random(seed);
        }

        public int getNextInteger() {
            double retval = (this.mean + random.nextGaussian() * this.variance);
            //System.out.println(retval);
            return (int) retval;
        }
    }

    class Room {

        public int xPos, yPos, zPos;
        public int height, width;

        public Room(int xPos, int yPos, int height, int width) {
            this.xPos = xPos;
            this.yPos = yPos;
            this.height = height;
            this.width = width;
        }
    }

    class DungeonGenerator {

        int roomCount;
        int roomSizeAvg;
        int roomSizeDev;
        int mapSize;
        ArrayList<Room> rooms;

        public DungeonGenerator(int roomCount, int roomSizeAvg, int roomSizeDev, int mapSize) {
            this.roomCount = roomCount;
            this.roomSizeAvg = roomSizeAvg;
            this.roomSizeDev = roomSizeDev;
            this.mapSize = mapSize;

            rooms = new ArrayList<>();
        }

        public void generate(int seed) {
            GaussianRandom roomGenerator = new GaussianRandom(seed, this.roomSizeAvg, this.roomSizeDev);
            GaussianRandom positionGenerator = new GaussianRandom(seed, 0, this.mapSize);
            for (int i = 0; i < this.roomCount; i++) {
                //System.out.println("Generate Room: " + i);
                this.rooms.add(new Room(positionGenerator.getNextInteger(), positionGenerator.getNextInteger(), Math.abs(roomGenerator.getNextInteger()), Math.abs(roomGenerator.getNextInteger())));
            }
            System.out.println("Generated " + this.rooms.size() + " rooms");
        }

        public void map2Array() {
            int minX = Integer.MAX_VALUE;
            int maxX = Integer.MIN_VALUE;
            int minY = Integer.MAX_VALUE;
            int maxY = Integer.MIN_VALUE;
            for (Room r : this.rooms) {
                System.out.println(r.xPos);
                minX = Math.min(minX, r.xPos - r.width);
                maxX = Math.max(maxX, r.xPos + r.width);
                minY = Math.min(minY, r.yPos - r.height);
                maxY = Math.max(maxY, r.yPos + r.height);
            }
            System.out.println("MapSize: " + (maxX - minX) + ":" + (maxY - minY));
            int array[][] = new int[maxX - minX][maxY - minY];
            for (Room r : this.rooms) {
                for (int x = r.xPos - r.width; x < r.xPos + r.width; x++) {
                    for (int y = r.yPos - r.height; y < r.yPos + r.height; y++) {
                        array[x - minX][y - minY] += 1;
                    }
                }
            }
            for (int x = 0; x < array.length; x++) {
                for (int y = 0; y < array[x].length; y++) {
                    System.out.print(array[x][y]);
                }
                System.out.println();
            }
        }
    }
}


Danke schonmal an alle die drüber nachdenken. Bin für jeden Lösungsansatz dankbar.

Was du da mit dem Array machst, habe ich nicht ganz kapiert (nur eine diffuse Ahnung). Aber… könntest du “Überlappungen” und “Zusammentreffen” genauer beschreiben?

Danke schon mal für die Antwort. Bin da gerade ein bisschen am verzweifeln.

Hallo, naja, das Array ist zur Zeit nur dazu da eine Testausgabe zu haben, da werden die überlappungen an punkt [x,y] gezählt.

Wenn mal fertig sollte das ganze sowas werden:
http://tinykeep.com/dungen/

Also ein dungeon generator.

Überlappung: wenn sich 2 Rechtecke überscheinden. Das sollte nicht sein.

Zusammentreffen: Jedes Rechteck sollte an einem anderen Rechteck anschließen. Können mehrere sein, müssen aber nicht.

Wenn es sonst keine Vorgaben gibt dann reicht doch eigentlich ein ganz banales Vorgehen. So in der Art von

für jeden Raum
    1. verschiebe ihn in eine zufällig Richtung bis es keine 
       Überlappung mit anderen Räumen mehr gibt
    2. verschiebe ihn in Richtung Zentrum oder in Richtung
       eines anderen Raumes bis er eine Verbindung zu einem
       anderen Raum hat

Ich hab’ das mal so versucht. Scheint auch fehlerfrei zu funktionieren (btw, sind Räume der Größe 0 beabsichtigt?). :slight_smile:

Source
[spoiler]

import java.awt.Point;
import java.awt.Rectangle;
import java.util.List;
import java.util.Random;


public class DungeonRoomArranger {

    private static final Random random = new Random( System.currentTimeMillis() );


    /**
     * moves the rooms around until they are not overlapping
     * anymore. then make sure they are connected to another
     * room.
     *
     * @param generatedRooms
     */
    void arrangeRooms(List<Room> generatedRooms) {

        // ensure that no room has zero size
        for(Room room : generatedRooms) {
            room.width += (room.width==0 ? 1 : 0);
            room.height += (room.height==0 ? 1 : 0);
        }

        // the first room doesn't need any checks, so let's start with room 1
        for(int roomNumber=1; roomNumber<generatedRooms.size(); roomNumber++) {

            // move room in a random direction until it does not
            // overlap other rooms anymore
            eleminateOverlapping(roomNumber, generatedRooms);

            // move room towards the center of the current dungeon
            // until it is connected to other rooms
            ensureConnection(roomNumber, generatedRooms);

        }
    }

    /**
     * moves the room around until it is not overlapping
     * anymore.
     * the general direction of movement is kept constant,
     * to avoid moving in circles.
     *
     * @param roomNumber
     * @param rooms
     */
    private void eleminateOverlapping(int roomNumber, List<Room> rooms) {
        int[][] mainDirections = { {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, {0,-1}, {1,-1} };
        Room room = rooms.get(roomNumber);
        int generalDirection = random.nextInt(8);

        while( isRoomOverlapping(roomNumber, rooms) ) {
            int direction = (generalDirection + random.nextInt(4) ) & 0x07;
            room.xPos += mainDirections[direction][0];
            room.yPos += mainDirections[direction][1];
        }
    }

    /**
     * moves the room towards the center of the dungeon
     * until it is adjacent to another room.
     *
     * @param roomNumber
     * @param rooms
     */
    private void ensureConnection(int roomNumber, List<Room> rooms) {
        Point center = getDungeonCenter( roomNumber, rooms );
        Room room = rooms.get(roomNumber);

        // move room
        while( ! isRoomConnected(roomNumber, rooms) ) {

            // move room
            if( random.nextBoolean() ) {
                room.xPos -= (int)Math.signum( room.xPos - center.x );
            } else {
                room.yPos -= (int)Math.signum( room.yPos - center.y );
            }

            // catch the case when we get stuck in the (empty) center
            if( (room.xPos == center.x) && (room.yPos == center.y) ) {
                // pick a random room as new center
                Room randomRoom = rooms.get( random.nextInt(roomNumber) );
                center.x = randomRoom.xPos;
                center.y = randomRoom.yPos;
            }
        }
    }

    /**
     * tests if the specified room overlaps other rooms.
     * tests are made only against the previous rooms in the list.
     *
     * @param roomNumber
     * @param rooms
     * @return
     */
    private boolean isRoomOverlapping(int roomNumber, List<Room> rooms) {
        boolean isOverlapping = false;

        Room testeeRoom = rooms.get(roomNumber);
        Rectangle testeeRectangle = new Rectangle(
                testeeRoom.xPos - testeeRoom.width, testeeRoom.yPos - testeeRoom.height,
                testeeRoom.width*2, testeeRoom.height*2 );

        // iterate over rooms
        for(int i=0; i<roomNumber; i++) {
            Room testRoom = rooms.get(i);
            Rectangle testRectangle = new Rectangle(
                    testRoom.xPos - testRoom.width, testRoom.yPos - testRoom.height,
                    testRoom.width*2, testRoom.height*2 );
            // do the intersection test
            if( testeeRectangle.intersects(testRectangle) ) {
                isOverlapping = true;
                break;
            }
        }

        return isOverlapping;
    }

    /**
     * tests if this room is adjacent to another room.
     * cheap trick: we enlarge the size of the room
     * and test for overlapping.
     *
     * @param roomNumber
     * @param rooms
     * @return
     */
    private boolean isRoomConnected(int roomNumber, List<Room> rooms) {
        assert !isRoomOverlapping( roomNumber, rooms );
        boolean isConnected;
        Room room = rooms.get(roomNumber);

        room.width++;
        isConnected = isRoomOverlapping( roomNumber, rooms);
        room.width--;

        if( ! isConnected ) {
            room.height++;
            isConnected = isRoomOverlapping( roomNumber, rooms );
            room.height--;
        }

        return isConnected;
    }

    /**
     *
     * @param roomNumber
     * @param rooms
     * @return
     */
    private Point getDungeonCenter(int roomNumber, List<Room> rooms) {
        Point center = new Point(0, 0);
        for(int i=0; i<roomNumber; i++) {
            center.x += rooms.get(i).xPos;
            center.y += rooms.get(i).yPos;
        }
        center.x /= roomNumber;
        center.y /= roomNumber;
        return center;
    }
}

[/spoiler]

[QUOTE=Dow_Jones;28137]Wenn es sonst keine Vorgaben gibt dann reicht doch eigentlich ein ganz banales Vorgehen. So in der Art von

für jeden Raum
    1. verschiebe ihn in eine zufällig Richtung bis es keine 
       Überlappung mit anderen Räumen mehr gibt
    2. verschiebe ihn in Richtung Zentrum oder in Richtung
       eines anderen Raumes bis er eine Verbindung zu einem
       anderen Raum hat

Ich hab’ das mal so versucht. Scheint auch fehlerfrei zu funktionieren (btw, sind Räume der Größe 0 beabsichtigt?). :slight_smile:

Source
[spoiler]

import java.awt.Point;
import java.awt.Rectangle;
import java.util.List;
import java.util.Random;


public class DungeonRoomArranger {

    private static final Random random = new Random( System.currentTimeMillis() );


    /**
     * moves the rooms around until they are not overlapping
     * anymore. then make sure they are connected to another
     * room.
     *
     * @param generatedRooms
     */
    void arrangeRooms(List<Room> generatedRooms) {

        // ensure that no room has zero size
        for(Room room : generatedRooms) {
            room.width += (room.width==0 ? 1 : 0);
            room.height += (room.height==0 ? 1 : 0);
        }

        // the first room doesn't need any checks, so let's start with room 1
        for(int roomNumber=1; roomNumber<generatedRooms.size(); roomNumber++) {

            // move room in a random direction until it does not
            // overlap other rooms anymore
            eleminateOverlapping(roomNumber, generatedRooms);

            // move room towards the center of the current dungeon
            // until it is connected to other rooms
            ensureConnection(roomNumber, generatedRooms);

        }
    }

    /**
     * moves the room around until it is not overlapping
     * anymore.
     * the general direction of movement is kept constant,
     * to avoid moving in circles.
     *
     * @param roomNumber
     * @param rooms
     */
    private void eleminateOverlapping(int roomNumber, List<Room> rooms) {
        int[][] mainDirections = { {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, {0,-1}, {1,-1} };
        Room room = rooms.get(roomNumber);
        int generalDirection = random.nextInt(8);

        while( isRoomOverlapping(roomNumber, rooms) ) {
            int direction = (generalDirection + random.nextInt(4) ) & 0x07;
            room.xPos += mainDirections[direction][0];
            room.yPos += mainDirections[direction][1];
        }
    }

    /**
     * moves the room towards the center of the dungeon
     * until it is adjacent to another room.
     *
     * @param roomNumber
     * @param rooms
     */
    private void ensureConnection(int roomNumber, List<Room> rooms) {
        Point center = getDungeonCenter( roomNumber, rooms );
        Room room = rooms.get(roomNumber);

        // move room
        while( ! isRoomConnected(roomNumber, rooms) ) {

            // move room
            if( random.nextBoolean() ) {
                room.xPos -= (int)Math.signum( room.xPos - center.x );
            } else {
                room.yPos -= (int)Math.signum( room.yPos - center.y );
            }

            // catch the case when we get stuck in the (empty) center
            if( (room.xPos == center.x) && (room.yPos == center.y) ) {
                // pick a random room as new center
                Room randomRoom = rooms.get( random.nextInt(roomNumber) );
                center.x = randomRoom.xPos;
                center.y = randomRoom.yPos;
            }
        }
    }

    /**
     * tests if the specified room overlaps other rooms.
     * tests are made only against the previous rooms in the list.
     *
     * @param roomNumber
     * @param rooms
     * @return
     */
    private boolean isRoomOverlapping(int roomNumber, List<Room> rooms) {
        boolean isOverlapping = false;

        Room testeeRoom = rooms.get(roomNumber);
        Rectangle testeeRectangle = new Rectangle(
                testeeRoom.xPos - testeeRoom.width, testeeRoom.yPos - testeeRoom.height,
                testeeRoom.width*2, testeeRoom.height*2 );

        // iterate over rooms
        for(int i=0; i<roomNumber; i++) {
            Room testRoom = rooms.get(i);
            Rectangle testRectangle = new Rectangle(
                    testRoom.xPos - testRoom.width, testRoom.yPos - testRoom.height,
                    testRoom.width*2, testRoom.height*2 );
            // do the intersection test
            if( testeeRectangle.intersects(testRectangle) ) {
                isOverlapping = true;
                break;
            }
        }

        return isOverlapping;
    }

    /**
     * tests if this room is adjacent to another room.
     * cheap trick: we enlarge the size of the room
     * and test for overlapping.
     *
     * @param roomNumber
     * @param rooms
     * @return
     */
    private boolean isRoomConnected(int roomNumber, List<Room> rooms) {
        assert !isRoomOverlapping( roomNumber, rooms );
        boolean isConnected;
        Room room = rooms.get(roomNumber);

        room.width++;
        isConnected = isRoomOverlapping( roomNumber, rooms);
        room.width--;

        if( ! isConnected ) {
            room.height++;
            isConnected = isRoomOverlapping( roomNumber, rooms );
            room.height--;
        }

        return isConnected;
    }

    /**
     *
     * @param roomNumber
     * @param rooms
     * @return
     */
    private Point getDungeonCenter(int roomNumber, List<Room> rooms) {
        Point center = new Point(0, 0);
        for(int i=0; i<roomNumber; i++) {
            center.x += rooms.get(i).xPos;
            center.y += rooms.get(i).yPos;
        }
        center.x /= roomNumber;
        center.y /= roomNumber;
        return center;
    }
}

[/spoiler]
[/QUOTE]

Aloah. Danke vielmals für den code. Dachte ich müsste da viel mehr Prüfen. Funkioniert einwandfrei.
Lediglich dein Methode um die Räume zu bewegen hat mir nicht so gefallen, da sehr oft Sternförmige Dungeons generiert wurden.

Hab einfach simples Vector basiertes bewegen hinzugefügt und jetzt funktionierts einwandfrei :slight_smile:

Source
[spoiler] private void eleminateOverlapping(int roomNumber, List<Room> rooms) { Vector2f direction=new Vector2f(random.nextInt(),random.nextInt()).normalizeLocal(); //int[][] mainDirections = { {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0}, {-1,-1}, {0,-1}, {1,-1} }; Room room = rooms.get(roomNumber); //int generalDirection = random.nextInt(8); Vector2f displacement=direction.clone(); while( isRoomOverlapping(roomNumber, rooms) ) { //int direction = (generalDirection + random.nextInt(4) ) & 0x07; int shiftX=(int) displacement.x; int shiftY=(int) displacement.y; room.xPos+=shiftX; room.yPos+=shiftY; displacement.x-=shiftX; displacement.y-=shiftY; displacement.addLocal(direction); //room.xPos += mainDirections[direction][0]; //room.yPos += mainDirections[direction][1]; } }[/spoiler]