Переход от grid-based raycaster к sector-based engine для поддержки:
- ✅ Разноуровневые полы и потолки
- ✅ Ступеньки и лестницы
- ✅ Окна и проемы
- ✅ Сложная геометрия карт
- ✅ Двери и подвижные платформы (в будущем)
Замкнутый полигональный регион с:
floorHeight- высота полаceilingHeight- высота потолкаfloorTexture- текстура полаceilingTexture- текстура потолкаlightLevel- уровень освещенияwalls- список стен сектора
Линия между двумя точками:
start- начальная точка (x, y)end- конечная точка (x, y)neighborSector- ID соседнего сектора (nullptr = твердая стена)upperTexture- текстура над проемомmiddleTexture- текстура стеныlowerTexture- текстура под проемом
Если wall.neighborSector != nullptr, стена является порталом:
- Можно видеть соседний сектор
- Если разница высот полов > 0 → нижняя текстура (ступенька вверх)
- Если разница высот потолков < 0 → верхняя текстура (окно)
include/
├── World/
│ ├── Sector.h # NEW - Класс сектора
│ ├── Wall.h # NEW - Класс стены
│ ├── SectorMap.h # NEW - Карта секторов (замена Map.h)
│ └── MapLoader.h # NEW - Загрузка карт из JSON/текста
│
├── Rendering/
│ ├── SectorRenderer.h # NEW - Рендерер секторов (замена RayCasterRenderer)
│ ├── PortalRenderer.h # NEW - Рендеринг порталов
│ └── TextureAtlas.h # NEW - Управление текстурами
│
└── Utils/
└── Geometry.h # NEW - Геометрические утилиты
src/
├── World/
│ ├── Sector.cpp
│ ├── Wall.cpp
│ ├── SectorMap.cpp
│ └── MapLoader.cpp
│
└── Rendering/
├── SectorRenderer.cpp
└── PortalRenderer.cpp
void renderSector(Sector& sector, FrustumClip& frustum) {
// 1. Отрендерить все видимые стены
for (auto& wall : sector.walls) {
if (!frustum.isVisible(wall)) continue;
if (wall.neighborSector == nullptr) {
// Твердая стена
renderSolidWall(wall, sector);
} else {
// Портал
Sector& neighbor = *wall.neighborSector;
// Рендер верхней/нижней частей
if (neighbor.ceilingHeight < sector.ceilingHeight)
renderUpperWall(wall, sector, neighbor);
if (neighbor.floorHeight > sector.floorHeight)
renderLowerWall(wall, sector, neighbor);
// Рекурсивно отрендерить соседний сектор
FrustumClip newFrustum = frustum.clipToPortal(wall);
renderSector(neighbor, newFrustum);
}
}
// 2. Отрендерить пол и потолок
renderFloorCeiling(sector, frustum);
}bool checkWallCollision(Vector2f pos, float radius, Wall& wall) {
// Найти ближайшую точку на линии стены
Vector2f closest = closestPointOnSegment(pos, wall.start, wall.end);
float dist = distance(pos, closest);
if (dist < radius) {
// Если стена - портал, проверить высоту
if (wall.neighborSector) {
float heightDiff = wall.neighborSector->floorHeight - currentSector->floorHeight;
if (heightDiff <= STEP_HEIGHT) {
// Можно пройти (ступенька)
return false;
}
}
return true; // Коллизия
}
return false;
}{
"sectors": [
{
"id": 0,
"floorHeight": 0.0,
"ceilingHeight": 2.5,
"floorTexture": "floor_stone.png",
"ceilingTexture": "ceiling_wood.png",
"lightLevel": 200,
"walls": [
{
"start": [0, 0],
"end": [5, 0],
"neighborSector": null,
"middleTexture": "wall_brick.png"
},
{
"start": [5, 0],
"end": [5, 5],
"neighborSector": 1,
"upperTexture": "wall_brick.png",
"lowerTexture": "wall_stone.png"
}
]
},
{
"id": 1,
"floorHeight": 0.5,
"ceilingHeight": 3.0,
"walls": [...]
}
],
"playerStart": {
"sector": 0,
"position": [2.5, 2.5],
"angle": 0
}
}class Player {
// NEW
float height; // Высота игрока (например, 1.8м)
float eyeHeight; // Высота глаз (1.6м от пола)
Sector* currentSector; // Текущий сектор
// Обновленная физика
void updatePhysics(float dt) {
// Гравитация
if (!onGround) {
velocityZ -= GRAVITY * dt;
}
// Проверка коллизий со стенами
for (auto& wall : currentSector->walls) {
if (checkWallCollision(position, PLAYER_RADIUS, wall)) {
// Обработка коллизии
}
}
// Обновление текущего сектора
updateCurrentSector();
}
};-
Сложность порталов - Рекурсия может быть медленной
-
Z-fighting на полах - Артефакты рендеринга
-
Невыпуклые сектора - Могут ломать рендеринг
-
Текстуры стен - UV-mapping сложнее чем в grid