ICT290 / src / scene / theArchanist / map / gameMap.cpp
gameMap.cpp
Raw
#include "gameMap.h"
#include <algorithm>

gameMap::gameMap(std::size_t numRooms,
                 const Resources& resources,
                 std::string& modelList,
                 int seed)
    : m_numRooms(numRooms),
      m_resources(resources),
      m_seed(seed) {
    mapGen(modelList);
}

void gameMap::mapGen(std::string& modelList) {
    readModelFile(modelList);
    m_corridorModels = m_models;
    m_corridorModels.walls[1] = nullptr;
    memset(m_map, false, sizeof(m_map));
    generateRooms();
    generateDoors();
    createRooms();
    createCorridors();
}

const WallPoints& gameMap::getWallPoints() {
    if (m_wallPoints.empty()) {
        std::for_each(m_rooms.begin(),
                      m_rooms.end(),
                      [this](const gameRoom& gR) {
                          m_wallPoints.push_back(gR.getWallPoints());
                      });
    }
    return m_wallPoints;
}

const WallPoints& gameMap::getCorridorWallPoints() {
    if (m_corridorWallPoints.empty()) {
        std::for_each(m_corridors.begin(),
                      m_corridors.end(),
                      [this](const gameRoom& gR) {
                          m_corridorWallPoints.push_back(gR.getWallPoints());
                      });
    }
    return m_corridorWallPoints;
}

const EntityBSphere& gameMap::getBoundingSpheres() {
    if (m_entityBSpheres.empty()) {
        std::for_each(m_rooms.begin(),
                      m_rooms.end(),
                      [this](const gameRoom& gR) {
                          m_entityBSpheres.push_back(gR.getBoundingSpheres());
                      });
    }
    return m_entityBSpheres;
}

const std::vector<std::pair<glm::vec3, glm::vec3>>& gameMap::getRoomSizes() {
    if (m_roomSizes.empty()) {
        std::for_each(m_rooms.begin(),
                      m_rooms.end(),
                      [this](const gameRoom& gR) {
                          m_roomSizes.emplace_back(gR.getMin(), gR.getMax());
                      });

        std::for_each(m_corridors.begin(),
                      m_corridors.end(),
                      [this](const gameRoom& gR) {
                          m_roomSizes.emplace_back(gR.getMin(), gR.getMax());
                      });
    }
    return m_roomSizes;
}

void gameMap::readModelFile(std::string& modelList) {
    std::ifstream inFile(modelList);

    if (!inFile) {
        throw std::runtime_error("Failed to open " + modelList
                                 + " when creating a map.\n");
    }

    std::string line;
    inFile >> line;
    if (line != "wall") {
        throw std::runtime_error("Wrong file format for map resources.\n");
    }

    inFile >> line;
    while (!inFile.eof() && line != "floor") {
        m_models.walls.push_back(m_resources.modelManager.getModel(line));
        inFile >> line;
    }

    inFile >> line;
    while (!inFile.eof() && line != "debug") {
        m_models.floors.push_back(m_resources.modelManager.getModel(line));
        inFile >> line;
    }

    if (!inFile.eof()) {
        inFile >> line;
        m_models.debugSphere = m_resources.modelManager.getModel(line);
    }
    if (!inFile.eof()) {
        inFile >> line;
        m_models.debugCube = m_resources.modelManager.getModel(line);
    }
}

void gameMap::draw() {
    for (auto& room : m_rooms) {
        room.draw();
        if (m_debug) {
            room.drawDebug();
        }
    }

    for (auto& corridor : m_corridors) {
        corridor.draw();
        if (m_debug) {
            corridor.drawDebug();
        }
    }
}

void gameMap::draw(glm::vec3 camPos, float distance) {
    camPos.y = 0;
    float camOffset{camPos.z + 10};
    for (auto& room : m_rooms) {
        if (glm::distance(camPos, room.getLocation()) < distance
            && room.getLocation().z < camOffset) {
            room.draw();
            if (m_debug) {
                room.drawDebug();
            }
        }
    }

    for (auto& corridor : m_corridors) {
        if (glm::distance(camPos, corridor.getLocation()) < distance
            && corridor.getLocation().z < camOffset) {
            corridor.draw();
            if (m_debug) {
                corridor.drawDebug();
            }
        }
    }
}

bool gameMap::updateMap(int xSize, int zSize, int xOrigin, int zOrigin) {
    if (xSize + xOrigin < GM_X_MAX - 1 && zSize + zOrigin < GM_Z_MAX - 1
        && xOrigin > 0 && zOrigin > 0) {
        // Check map area is clear
        for (int x = xOrigin; x <= xOrigin + xSize; ++x) {
            for (int z = zOrigin; z <= zOrigin + zSize; ++z) {
                if (m_map[x][z] || m_map[x + 1][z] || m_map[x - 1][z]
                    || m_map[x][z + 1] || m_map[x][z - 1] || m_map[x - 1][z - 1]
                    || m_map[x + 1][z + 1]) {
                    return false;
                }
            }
        }

        // update map
        for (int x = xOrigin; x <= xOrigin + xSize; ++x) {
            for (int z = zOrigin; z <= zOrigin + zSize; ++z) {
                m_map[x][z] = true;
            }
        }
        return true;
    }
    return false;
}

gameMap::RoomInfo gameMap::generateSpawn() {
    glm::vec3 directionNormal{1, 0, 1};
    glm::vec3 nextRoomDistance{GM_X_MAX / 2, 0, GM_Z_MAX / 2};
    glm::vec3 finalLocation{directionNormal * nextRoomDistance};
    std::vector<std::pair<int, int>> doors;

    int x{6}, z{6}, wallFill{15}, floorFill{0};
    int xOrigin{(int)finalLocation.x}, zOrigin{(int)finalLocation.z};
    RoomInfo roomInfo(x, z, wallFill, floorFill, xOrigin, zOrigin, doors);

    if (!updateMap(x, z, xOrigin, zOrigin)) {
        throw std::runtime_error("Invalid spawn room for map\n");
    }

    m_roomInfo.push_back(roomInfo);

    return roomInfo;
}

void gameMap::generateRooms() {
    RoomInfo roomInfo;
    if (m_rooms.empty()) {
        roomInfo = generateSpawn();
    }

    glm::vec3 normal{0};
    // Set to spawn
    glm::vec3 finalLocation{GM_X_MAX / 2, 0, GM_Z_MAX / 2};

    std::uniform_int_distribution<> distRoomDistance{5, 30};
    std::uniform_int_distribution<> distRoomSizes{5, 20};
    int wallFill{15};
    int floorFill{8};

    int nRooms = (int)m_numRooms - 1;
    int failCount{0}, reset{0};
    while (nRooms > 0) {
        std::mt19937 genX{static_cast<std::random_device::result_type>(
            m_seed + m_seedOffset++)};
        std::mt19937 genZ{static_cast<std::random_device::result_type>(
            m_seed + m_seedOffset++)};
        std::uniform_int_distribution<> distX{roomInfo.xOrigin - roomInfo.xSize
                                                  + 1,
                                              roomInfo.xOrigin + roomInfo.xSize
                                                  - 1};
        std::uniform_int_distribution<> distZ{roomInfo.zOrigin - roomInfo.zSize
                                                  + 1,
                                              roomInfo.zOrigin + roomInfo.zSize
                                                  - 1};

        normal = randomNormal();
        glm::vec3 offset{distX(genX), 0, distZ(genZ)};
        glm::vec3 tempFinal = offset * normal
                              + glm::vec3{distRoomDistance(genX),
                                          0,
                                          distRoomDistance(genZ)};
        int xSize = distRoomSizes(genX);
        int zSize = distRoomSizes(genZ);
        if (updateMap(xSize, zSize, (int)tempFinal.x, (int)tempFinal.z)) {
            std::vector<std::pair<int, int>> doors;
            m_roomInfo.emplace_back(xSize,
                                    zSize,
                                    wallFill,
                                    floorFill,
                                    (int)tempFinal.x,
                                    (int)tempFinal.z,
                                    doors);
            --nRooms;
        } else {
            if (failCount++ > 30000) {
                memset(m_map, false, sizeof(m_map));
                m_roomInfo.clear();
                roomInfo = generateSpawn();
                failCount = 0;
                m_seed *= 3;
                nRooms = 9;
                if (++reset > 3) {
                    std::cerr << "Failed to generate a valid map\n";
                    throw std::runtime_error(
                        "Failed to generate a valid map\n");
                }
            }
        }
    }
}

int gameMap::insertDoors(RoomInfo& roomInfo,
                         std::vector<DoorData>& possibleDoorLocations) {
    std::mt19937 gen{
        static_cast<std::random_device::result_type>(m_seed + m_seedOffset++)};
    std::uniform_int_distribution<> dist{0,
                                         (int)possibleDoorLocations.size() - 1};
    int rand = dist(gen);
    roomInfo.doors.emplace_back(possibleDoorLocations[rand].x,
                                possibleDoorLocations[rand].z);

    auto inserted = possibleDoorLocations[rand];

    // find connecting room
    for (auto& room : m_roomInfo) {
        if (inserted.connectingRoomX >= room.xOrigin
            && inserted.connectingRoomX <= room.xOrigin + room.xSize
            && inserted.connectingRoomZ >= room.zOrigin
            && inserted.connectingRoomZ <= room.zOrigin + room.zSize) {
            int count{0};
            if (possibleDoorLocations[0].x == 0) {
                for (int z = room.zOrigin; z <= room.zOrigin + room.zSize;
                     ++z) {
                    if (z == inserted.connectingRoomZ) {
                        ++count;
                        room.doors.emplace_back(room.xSize + 1, count);
                    }
                    ++count;
                }
            } else {
                for (int x = room.xOrigin; x <= room.xOrigin + room.xSize;
                     ++x) {
                    if (x == inserted.connectingRoomX) {
                        ++count;
                        room.doors.emplace_back(count, room.zSize + 1);
                    }
                    ++count;
                }
            }
        }
    }
    return rand;
}

void gameMap::generateDoors() {
    for (auto& room : m_roomInfo) {
        std::vector<DoorData> possibleDoorLocations;
        DoorData doorData;

        int xCount{2};
        // find another room adjacent to x-axis
        for (int x = room.xOrigin + 1; x < room.xOrigin + room.xSize; ++x) {
            for (int z = room.zOrigin - 1; z > 0; --z) {
                if (m_map[x][z] && m_map[x - 1][z] && m_map[x + 1][z]) {
                    possibleDoorLocations.emplace_back(xCount, 0, x, z, room);
                    break;
                } else if (m_map[x][z]) {
                    break;
                }
            }
            ++xCount;
        }
        if (!possibleDoorLocations.empty()) {
            int index = insertDoors(room, possibleDoorLocations);
            // connect rooms
            auto inserted = possibleDoorLocations[index];

            generateCorridor(inserted);

            possibleDoorLocations.clear();
        }
        // find another room adjacent to z-axis
        int zCount{2};
        for (int z = room.zOrigin + 1; z < room.zOrigin + room.zSize; ++z) {
            for (int x = room.xOrigin - 1; x > 0; --x) {
                if (m_map[x][z] && m_map[x][z - 1] && m_map[x][z + 1]) {
                    possibleDoorLocations.emplace_back(0, zCount, x, z, room);
                    break;
                } else if (m_map[x][z]) {
                    break;
                }
            }
            ++zCount;
        }
        if (!possibleDoorLocations.empty()) {
            int index = insertDoors(room, possibleDoorLocations);

            // connect rooms
            auto inserted = possibleDoorLocations[index];

            generateCorridor(inserted);
        }
    }
    checkCorridorIntersections();
}

void gameMap::generateCorridor(const DoorData& doorData) {
    DoorSet doors;
    int originPlusX = doorData.info.xOrigin + doorData.x;
    int originPlusZ = doorData.info.zOrigin + doorData.z;
    if (doorData.x == 0) {
        doors.emplace_back(originPlusX - doorData.connectingRoomX + 1, 1);

        doors.emplace_back(0, 1);
        m_corridorInfo.emplace_back(originPlusX - doorData.connectingRoomX,
                                    1,
                                    15,
                                    0,
                                    originPlusX
                                        - (originPlusX
                                           - doorData.connectingRoomX),
                                    originPlusZ - 1,
                                    doors);
    } else {
        doors.emplace_back(1,
                           doorData.info.zOrigin + doorData.z
                               - doorData.connectingRoomZ + 1);

        doors.emplace_back(1, 0);
        m_corridorInfo.emplace_back(1,
                                    originPlusZ - doorData.connectingRoomZ,
                                    15,
                                    0,
                                    originPlusX - 1,
                                    originPlusZ
                                        - (originPlusZ
                                           - doorData.connectingRoomZ),
                                    doors);
    }
}

void gameMap::checkCorridorIntersections() {
    for (auto& info : m_corridorInfo) {
        for (auto& cmpInfo : m_corridorInfo) {
            if (&info != &cmpInfo) {
                if (info.xOrigin > cmpInfo.xOrigin
                    && info.xOrigin < cmpInfo.xOrigin + cmpInfo.xSize
                    && info.zOrigin < cmpInfo.zOrigin
                    && info.zOrigin + info.zSize > cmpInfo.zOrigin) {
                    // intersecting
                    info.doors.emplace_back(0,
                                            cmpInfo.zOrigin - info.zOrigin + 1);
                    info.doors.emplace_back(2,
                                            cmpInfo.zOrigin - info.zOrigin + 1);

                    cmpInfo.doors.emplace_back(info.xOrigin - cmpInfo.xOrigin
                                                   + 1,
                                               0);
                    cmpInfo.doors.emplace_back(info.xOrigin - cmpInfo.xOrigin
                                                   + 1,
                                               2);
                }
            }
        }
    }
}

void gameMap::createRooms() {
    std::for_each(m_roomInfo.begin(), m_roomInfo.end(), [this](RoomInfo& info) {
        m_rooms.emplace_back(info.xSize,
                             info.zSize,
                             info.wallFil,
                             info.floorFill,
                             info.xOrigin,
                             info.zOrigin,
                             m_models,
                             m_seed,
                             info.doors);
    });
}

void gameMap::createCorridors() {
    std::for_each(m_corridorInfo.begin(),
                  m_corridorInfo.end(),
                  [this](RoomInfo& info) {
                      m_corridors.emplace_back(info.xSize,
                                               info.zSize,
                                               info.wallFil,
                                               info.floorFill,
                                               info.xOrigin,
                                               info.zOrigin,
                                               m_corridorModels,
                                               m_seed,
                                               info.doors);
                  });
}

glm::vec3 gameMap::randomNormal() {
    std::mt19937 genX{
        static_cast<std::random_device::result_type>(m_seed + m_seedOffset++)};
    std::mt19937 genZ{
        static_cast<std::random_device::result_type>(m_seed + m_seedOffset++)};
    std::uniform_int_distribution<> dist{-1, 1};
    return glm::vec3{dist(genX), 0, dist(genZ)};
}

gameMap::RoomInfo::RoomInfo(int xS,
                            int zS,
                            int wF,
                            int fF,
                            int xO,
                            int zO,
                            const DoorSet& d)
    : xSize(xS),
      zSize(zS),
      wallFil(wF),
      floorFill(fF),
      xOrigin(xO),
      zOrigin(zO),
      doors(d) {}

gameMap::DoorData::DoorData(int xA,
                            int zA,
                            int crx,
                            int crz,
                            const RoomInfo& ri)
    : x(xA),
      z(zA),
      connectingRoomX(crx),
      connectingRoomZ(crz),
      info(ri) {}