#include "gameRoom.h" #include <exception> #include <glm/gtx/rotate_vector.hpp> gameRoom::gameRoom(int x, int z, int wallFillPercent, int floorFillPercent, int xOrigin, int zOrigin, const RoomModels& roomModels, int seed, const std::vector<std::pair<int, int>>& doors) : m_models(roomModels), m_seed(seed), m_doors(doors) { // Save x and z size if (x < 1) { x = 1; } if (z < 1) { z = 1; } // Save floor dimensions m_floor.xMax = x; m_floor.zMax = z; m_floor.max = z * x; // Save floor size plus walls const int corners{2}; m_wall.xMax = x + corners; m_wall.zMax = z + corners; m_wall.max = x * 2 + z * 2; // Set room dimensions m_dimensions.xMax = x + corners; m_dimensions.zMax = z + corners; m_dimensions.max = m_dimensions.xMax * m_dimensions.zMax; // Set room map size m_accessible = new accessTypes[m_dimensions.max]; // Set it empty memset(m_accessible, accessTypes::empty, sizeof(accessTypes) * m_dimensions.max); // Save origin offsets m_offset.xCent = xOrigin + m_dimensions.xMax / 2; m_offset.zCent = zOrigin + m_dimensions.zMax / 2; m_offset.xMax = xOrigin + m_dimensions.xMax; m_offset.zMax = zOrigin + m_dimensions.zMax; m_offset.xMin = xOrigin; m_offset.zMin = zOrigin; // Create itself createRoom(); addDecoration((float)floorFillPercent, (float)wallFillPercent); generateWallPoints(); generateEntityBoundingSpheres(); } gameRoom::~gameRoom() { delete[] m_accessible; } gameRoom::gameRoom(const gameRoom& other) : m_accessible(nullptr), m_wallPoints(other.m_wallPoints), m_entityBoundingSpheres(other.m_entityBoundingSpheres), m_floor(other.m_floor), m_wall(other.m_wall), m_dimensions(other.m_dimensions), m_offset(other.m_offset), m_normals(other.m_normals), m_models(other.m_models), m_objects(other.m_objects), m_debugData(other.m_debugData), m_debugObjects(other.m_debugObjects), m_seed(other.m_seed), m_randomRotationOffset(other.m_randomRotationOffset), m_doors(other.m_doors) { m_accessible = new accessTypes[other.m_dimensions.max]; std::memcpy(m_accessible, other.m_accessible, other.m_dimensions.max); } gameRoom& gameRoom::operator=(const gameRoom& other) { if (this != &other) { if (m_accessible != nullptr) { free(m_accessible); m_accessible = nullptr; } m_wallPoints = other.m_wallPoints; m_entityBoundingSpheres = other.m_entityBoundingSpheres; m_floor = other.m_floor; m_wall = other.m_wall; m_dimensions = other.m_dimensions; m_offset = other.m_offset; m_normals = other.m_normals; m_models = other.m_models; m_objects = other.m_objects; m_debugData = other.m_debugData; m_debugObjects = other.m_debugObjects; m_seed = other.m_seed; m_randomRotationOffset = other.m_randomRotationOffset; m_doors = other.m_doors; if (other.m_accessible != nullptr) { m_accessible = new accessTypes[other.m_dimensions.max]; std::memcpy(m_accessible, other.m_accessible, other.m_dimensions.max); } } return *this; } const std::vector<std::vector<glm::vec3>>& gameRoom::getWallPoints() const { return m_wallPoints; } const std::vector<std::pair<glm::vec3, float>>& gameRoom::getBoundingSpheres() const { return m_entityBoundingSpheres; } glm::vec3 gameRoom::getLocation() const { return glm::vec3{m_offset.xCent, 0, m_offset.zCent}; } glm::vec3 gameRoom::getMin() const { return glm::vec3{m_offset.xMin, 0, m_offset.zMin}; } glm::vec3 gameRoom::getMax() const { return glm::vec3{m_offset.xMax, 0, m_offset.zMax}; } void gameRoom::setDoors() { for (auto& door : m_doors) { if (isWall(door.first, door.second)) { m_accessible[getIndex(door.first, door.second)] = accessTypes::door; } } } void gameRoom::createRoom() { setDoors(); for (int row{0}; row < m_dimensions.xMax; ++row) { for (int col{0}; col < m_dimensions.zMax; ++col) { if (isWall(row, col)) { if (m_accessible[getIndex(row, col)] == accessTypes::door) { createWallType(row, col, m_models.walls[1], accessTypes::door); } else { createWallType(row, col, m_models.walls[0], accessTypes::wall); } } else if (!isCorner(row, col)) { createFloorType(row, col, m_models.floors[0], accessTypes::floor); } } } } float gameRoom::randomRotation() { std::mt19937 gen{static_cast<std::random_device::result_type>( m_seed + m_randomRotationOffset++)}; /// seed the generator std::uniform_int_distribution<> distribution{0, 3}; /// define the range float angles[4] = {0, 90, 180, 270}; return angles[distribution(gen)]; } bool gameRoom::isCorner(int row, int col) const { return ((row == 0 && col == 0) || (row == 0 && col == m_dimensions.zMax - 1) || (row == m_dimensions.xMax - 1 && col == 0) || (row == m_dimensions.xMax - 1 && col == m_dimensions.zMax - 1)); } bool gameRoom::isWall(int row, int col) const { return ((row == 0 || col == 0 || row == m_dimensions.xMax - 1 || col == m_dimensions.zMax - 1) && row < m_dimensions.xMax && col < m_dimensions.zMax && row >= 0 && col >= 0 && !isCorner(row, col)); } gameRoom::direction gameRoom::wallPosition(int row, int col) const { if (row == 0) { return direction::negX; } else if (row == m_dimensions.xMax - 1) { return direction::posX; } else if (col == 0) { return direction::negZ; } else if (col == m_dimensions.zMax - 1) { return direction::posZ; } else { return direction::NaD; } } void gameRoom::createFloorType(int row, int col, std::shared_ptr<Model>& model, const accessTypes accessType) { assert(accessType == accessTypes::floor || accessType == accessTypes::floorEntity); m_accessible[getIndex(row, col)] = accessType; std::string name{GR_FLOOR + std::to_string(getIndex(row, col))}; float rotationY = randomRotation(); glm::vec3 rotation{0, rotationY, 0}; // Set name and rotation storing depending on accessType if (accessType == accessTypes::floorEntity) { name = GR_ENTITY_FLOOR + std::to_string(getIndex(row, col)); // Store angle for point generation m_rotationAngles.insert( {GR_ROTATION + std::to_string(getIndex(row, col)), rotationY}); } glm::vec3 location{m_offset.xMin + row, 0, m_offset.zMin + col}; glm::vec3 scale{GR_OBJ_SCALE, GR_OBJ_SCALE, GR_OBJ_SCALE}; Object floorObj{name, model, location, rotation, scale}; auto obj = m_objects.find(name); if (obj != m_objects.end()) { obj->second = floorObj; } else { m_objects.insert({name, floorObj}); } } void gameRoom::createWallType(int row, int col, std::shared_ptr<Model>& model, const accessTypes accessType) { assert(accessType == accessTypes::wall || accessType == accessTypes::wallEntity || accessType == accessTypes::door); // Set to wall for wall & wallEntity, this is because wall entities will // have no collision and can be treated the same m_accessible[getIndex(row, col)] = accessTypes::wall; std::string name{GR_WALL + std::to_string(getIndex(row, col))}; if (accessType == accessTypes::wallEntity) { name = GR_ENTITY_WALL + std::to_string(getIndex(row, col)); } else if (accessType == accessTypes::door) { name = GR_DOOR + std::to_string(getIndex(row, col)); m_accessible[getIndex(row, col)] = accessTypes::door; } glm::vec3 location{m_offset.xMin + row, 0, m_offset.zMin + col}; glm::vec3 scale{GR_OBJ_SCALE, GR_OBJ_SCALE, GR_OBJ_SCALE}; glm::vec3 rotation{0, 0, 0}; direction wallPos = wallPosition(row, col); switch (wallPos) { case direction::negZ: location.z += GR_WALL_OFFSET; rotation.y += 180; break; case direction::negX: location.x += GR_WALL_OFFSET; rotation.y = -90.f; break; case direction::posZ: location.z -= GR_WALL_OFFSET; break; case direction::posX: location.x -= GR_WALL_OFFSET; rotation.y = 90.f; break; default: throw std::runtime_error( "gameRoom::createWallType found no valid wall position.\n"); } Object wallObj{name, model, location, rotation, scale}; auto obj = m_objects.find(name); if (obj != m_objects.end()) { obj->second = wallObj; } else { m_objects.insert({name, wallObj}); } } void gameRoom::addDecoration(float floorPercent, float wallPercent) { int targetFloorFill = (int)((float)m_dimensions.max * (floorPercent / 100)); int targetWallFill = (int)((float)m_dimensions.max * (wallPercent / 100)); int floorFill{0}; int wallFill{0}; int row{0}, col{0}; std::mt19937 gen{static_cast<std::random_device::result_type>(m_seed)}; std::uniform_int_distribution<> spawnDist{0, 1000}; int floorMax = (int)m_models.floors.size() - 1; if (floorMax <= 0) { targetFloorFill = 0; floorMax = 1; // For uniform distribution } int wallMax = (int)m_models.walls.size() - 1; if (wallMax <= 2) { targetWallFill = 0; wallMax = 2; // For uniform distribution } // Dont include defaults / door std::uniform_int_distribution<> floorDist{1, floorMax}; std::uniform_int_distribution<> wallDist{2, wallMax}; int failCounter{0}; const int failLimit{250}; while ((floorFill != targetFloorFill || wallFill != targetWallFill) && failCounter < failLimit) { // 0.5% chance to spawn an entity if (spawnDist(gen) > 995) { if (m_accessible[getIndex(row, col)] == accessTypes::wall && wallFill != targetWallFill) { createWallType(row, col, m_models.walls[wallDist(gen)], accessTypes::wallEntity); ++wallFill; } else if (m_accessible[getIndex(row, col)] == accessTypes::floor && floorFill != targetFloorFill) { createFloorType(row, col, m_models.floors[floorDist(gen)], accessTypes::floorEntity); ++floorFill; m_accessible[getIndex(row, col)] = accessTypes::floorEntity; } else { ++failCounter; } } ++row; if (row >= m_dimensions.xMax) { row = 0; ++col; } if (col >= m_dimensions.zMax) { col = 0; } } // Don't check, floor's empty if (floorFill == 0) { return; } // Walk from one row end to the other row = 1; bool doorFoundMin{false}; bool doorFoundMax{false}; int xCent = m_floor.xMax / 2; int zCent = m_floor.zMax / 2; for (col = 0; col <= m_floor.zMax; ++col) { if (m_accessible[getIndex(0, col)] == accessTypes::door) { walkRoom({row, col}, wallPosition(0, col)); doorFoundMin = true; } if (m_accessible[getIndex(m_dimensions.xMax - 1, col)] == accessTypes::door) { walkRoom({m_floor.xMax, col}, wallPosition(m_dimensions.xMax - 1, col)); doorFoundMax = true; } } if (!doorFoundMin) { walkRoom({1, zCent}, direction::negX); } if (!doorFoundMax) { walkRoom({m_floor.xMax, zCent}, direction::posX); } doorFoundMin = false; doorFoundMax = false; col = 1; for (row = 0; row <= m_floor.xMax; ++row) { if (m_accessible[getIndex(row, 0)] == accessTypes::door) { walkRoom({row, col}, wallPosition(row, 0)); doorFoundMin = true; } if (m_accessible[getIndex(row, m_dimensions.zMax - 1)] == accessTypes::door) { walkRoom({row, m_floor.zMax}, wallPosition(row, m_dimensions.zMax - 1)); doorFoundMax = true; } } if (!doorFoundMin) { walkRoom({xCent, 1}, direction::negZ); } if (!doorFoundMax) { walkRoom({xCent, m_floor.zMax}, direction::posZ); } } void gameRoom::walkRoom(std::pair<int, int> start, direction doorAxis) { if (doorAxis == NaD) { return; } // Door or wall centre int row = start.first; int col = start.second; // Walking positions int walkingStart{0}; int walkingEnd{0}; setWalkingStart(walkingStart, walkingEnd, doorAxis); // Strafe (side) checking int strafe{0}; int strafeEnd{0}; while (walkingStart != walkingEnd) { // Replace Entity if blocking if (isFloorEntity(row, col)) { removeFloorEntity(row, col); createFloorType(row, col, m_models.floors[0], accessTypes::floor); } // Check next step bool walk{false}; bool swapped{false}; setStrafe(strafe, strafeEnd, doorAxis, row, col); int startingStrafe = strafe; int startingRow = row, startingCol = col; while (strafe != strafeEnd) { std::pair<bool, bool> result = checkStrafeCollision(strafe, doorAxis, row, col); // Current position is valid if (result.first && result.second) { walk = true; break; } // Check other direction if ((!result.first && !result.second) || strafe == strafeEnd) { if (swapped) { break; } strafeEnd = 0; strafe = startingStrafe; swapped = true; } // Update strafe movement strafe = (strafe < strafeEnd) ? ++strafe : --strafe; } // If both directions failed, walk forward if (!walk) { row = startingRow; col = startingCol; walkForward(row, col, doorAxis); } // Increment / Decrement walking start updateWalking(walkingStart, doorAxis); } } void gameRoom::setWalkingStart(int& start, int& end, direction dir) { switch (dir) { case posX: start = m_floor.xMax; end = 0; return; case negX: start = 1; end = m_floor.xMax + 1; return; case posZ: start = m_floor.zMax; end = 0; return; case negZ: start = 1; end = m_floor.zMax + 1; return; default: throw std::runtime_error("gameRoom::setWalkingStart NaD\n"); } } void gameRoom::walkForward(int& row, int& col, direction dir) { switch (dir) { case negX: ++row; return; case posX: --row; return; case negZ: ++col; return; case posZ: --col; return; default: throw std::runtime_error("gameRoom::walkForward NaD\n"); } } std::pair<bool, bool> gameRoom::checkStrafeCollision(int strafe, direction dir, int& row, int& col) const { if (dir == direction::negX || dir == direction::posX) { if (strafe <= m_floor.zMax && isFloor(row, strafe)) { if (dir == direction::posX) { if (isFloor(row - 1, strafe)) { --row; col = strafe; return {true, true}; } } else { if (isFloor(row + 1, strafe)) { ++row; col = strafe; return {true, true}; } } return {true, false}; } } else { if (strafe <= m_floor.xMax && isFloor(strafe, col)) { if (dir == direction::posZ) { if (isFloor(strafe, col - 1)) { row = strafe; --col; return {true, true}; } } else { if (isFloor(strafe, col + 1)) { row = strafe; ++col; return {true, true}; } } return {true, false}; } } return {false, false}; } void gameRoom::setStrafe(int& strafe, int& strafeEnd, direction dir, int row, int col) const { strafe = (dir == direction::negX || dir == direction::posX) ? col : row; switch (dir) { case negX: case posX: strafeEnd = m_floor.zMax + 1; return; case posZ: case negZ: strafeEnd = m_floor.xMax + 1; return; case NaD: throw std::runtime_error("gameRoom::setStrafe NAD encountered\n"); } } void gameRoom::updateWalking(int& walking, direction dir) { walking = (dir == direction::negX || dir == direction::negZ) ? ++walking : --walking; } bool gameRoom::isFloor(int row, int col) const { return m_accessible[getIndex(row, col)] == accessTypes::floor; } bool gameRoom::isFloorEntity(int row, int col) const { return m_accessible[getIndex(row, col)] == accessTypes::floorEntity; } void gameRoom::removeFloorEntity(int row, int col) { auto obj = m_objects.find(GR_ENTITY_FLOOR + std::to_string(getIndex(row, col))); if (obj != m_objects.end()) { m_objects.erase(obj); } } std::size_t gameRoom::getIndex(int row, int col) const { return row * m_dimensions.zMax + col; } void gameRoom::draw() { for (auto& obj : m_objects) { obj.second.draw(); } } void gameRoom::updateWallPointLocation(glm::vec3& location, direction wallPos) const { switch (wallPos) { case gameRoom::direction::negZ: location.z += GR_OBJ_SCALE; break; case gameRoom::direction::negX: location.x += GR_OBJ_SCALE; break; case gameRoom::direction::posZ: location.z -= GR_OBJ_SCALE; break; case gameRoom::direction::posX: location.x -= GR_OBJ_SCALE; break; default: throw std::runtime_error( "gameRoom::updateWallPointLocation found no valid wall " "position.\n"); } } glm::vec3 gameRoom::getNormal(direction wallPos) const { switch (wallPos) { case gameRoom::direction::negZ: return m_normals.posZ; case gameRoom::direction::negX: return m_normals.posX; case gameRoom::direction::posZ: return m_normals.negZ; case gameRoom::direction::posX: return m_normals.negX; default: throw std::runtime_error( "gameRoom::getNormal found no valid wall position.\n"); } } void gameRoom::insertWallPoints(int row, int col, std::vector<glm::vec3>& wall, bool& pOneInserted, int& wallCount, direction dir) { assert(wall.size() == 3); direction wallPos = wallPosition(row, col); glm::vec3 point{m_offset.xMin + row, 0, m_offset.zMin + col}; // Default on the x Axis bool lowerCorner = (col <= m_dimensions.zMax / 2); bool xAxisAligned{true}; bool valid{dir == direction::negZ || dir == direction::posZ}; if (wallPos == direction::negX || wallPos == direction::posX) { valid = dir == direction::negX || dir == direction::posX; lowerCorner = (row <= m_dimensions.xMax / 2); xAxisAligned = false; } if (m_accessible[getIndex(row, col)] == accessTypes::wall && valid) { updateWallPointLocation(point, wallPos); // Get the corner of the wall and set first point & normal if (!pOneInserted) { if (xAxisAligned) { point.x -= GR_CORNER_OFFSET; } else { point.z -= GR_CORNER_OFFSET; } wall[0] = point; wall[2] = getNormal(wallPos); pOneInserted = true; ++wallCount; } else { if (xAxisAligned) { point.x += GR_CORNER_OFFSET; } else { point.z += GR_CORNER_OFFSET; } wall[1] = point; ++wallCount; } } else if (pOneInserted) { // A one tile wall if (wallCount <= 1) { // Get the corner of the wall if (lowerCorner) { if (xAxisAligned) { point.x -= GR_CORNER_OFFSET; point.z += GR_CORNER_OFFSET; } else { point.x += GR_CORNER_OFFSET; point.z -= GR_CORNER_OFFSET; } } else { if (dir == negZ) { point.x -= GR_CORNER_OFFSET; point.z += GR_CORNER_OFFSET; } else { point.x -= GR_CORNER_OFFSET; point.z -= GR_CORNER_OFFSET; } } wall[1] = point; } m_wallPoints.push_back(wall); pOneInserted = false; wallCount = 0; } } void gameRoom::generateEntityBoundingSpheres() { for (int row = 1; row < m_dimensions.xMax; ++row) { for (int col = 1; col < m_dimensions.zMax; ++col) { // Add all entities if (m_accessible[getIndex(row, col)] == accessTypes::floorEntity) { auto obj = m_objects.find(GR_ENTITY_FLOOR + std::to_string(getIndex(row, col))); glm::vec3 centre = obj->second.model->boundingSphere.centreNoY; auto angle = m_rotationAngles.find( GR_ROTATION + std::to_string(getIndex(row, col))); centre = glm::rotateY(centre, angle->second); centre.x += (float)(m_offset.xMin + row); centre.z += (float)(m_offset.zMin + col); float radius = obj->second.model->boundingSphere.radiusNoY * GR_OBJ_SCALE; m_entityBoundingSpheres.emplace_back(centre, radius); } } } } void gameRoom::generateWallPoints() { std::vector<glm::vec3> wallMin(3); std::vector<glm::vec3> wallMax(3); bool pOneInsertedMin{false}, pOneInsertedMax{false}; int wallCountMin{0}, wallCountMax{0}; for (int row{1}; row < m_dimensions.xMax; ++row) { insertWallPoints(row, 0, wallMin, pOneInsertedMin, wallCountMin, direction::negZ); insertWallPoints(row, m_dimensions.zMax - 1, wallMax, pOneInsertedMax, wallCountMax, direction::posZ); } // A wall that runs to the end of the row if (pOneInsertedMin) { m_wallPoints.push_back(wallMin); } if (pOneInsertedMax) { m_wallPoints.push_back(wallMax); } pOneInsertedMin = false; pOneInsertedMax = false; wallCountMin = 0; wallCountMax = 0; for (int col{1}; col < m_dimensions.zMax; ++col) { insertWallPoints(0, col, wallMin, pOneInsertedMin, wallCountMin, direction::negX); insertWallPoints(m_dimensions.xMax - 1, col, wallMax, pOneInsertedMax, wallCountMax, direction::posX); } // A wall that runs to the end of the col if (pOneInsertedMin) { m_wallPoints.push_back(wallMin); } if (pOneInsertedMax) { m_wallPoints.push_back(wallMax); } } void gameRoom::drawDebug() { if (!m_debugData) { generateDebugData(); } glPushAttrib(GL_POLYGON_BIT | GL_LIGHTING_BIT); glDisable(GL_LIGHTING); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); for (auto& debug : m_debugObjects) { debug.draw(); } glPopAttrib(); } void gameRoom::generateDebugData() { std::string name{"debug"}; glm::vec3 rotation{0}; glm::vec3 scale{0.1f, 2, 0.1f}; for (auto& wall : m_wallPoints) { m_debugObjects.emplace_back(name, m_models.debugCube, wall[0], rotation, scale); m_debugObjects.emplace_back(name, m_models.debugCube, wall[1], rotation, scale); } for (auto& sphere : m_entityBoundingSpheres) { glm::vec3 sScale{sphere.second}; m_debugObjects.emplace_back(name, m_models.debugSphere, sphere.first, rotation, sScale); } m_debugData = true; }