Working on minor fixing, working on pathfinding

This commit is contained in:
Vladislav Khorev 2026-08-19 03:40:02 +05:00
parent a11c4614ea
commit c9c6b7e7fb
9 changed files with 290 additions and 47 deletions

View File

@ -25,7 +25,7 @@ macro(check_and_download URL ARCHIVE_NAME EXTRACTED_DIR_NAME CHECK_FILE)
endmacro()
# 1) ZLIB (Нужна только для инклудов, если не используете emscripten порты)
check_and_download("https://www.zlib.net/zlib132.zip" "zlib132.zip" "zlib-1.3.2" "CMakeLists.txt")
check_and_download("https://github.com/madler/zlib/releases/download/v1.3.2/zlib132.zip" "zlib132.zip" "zlib-1.3.2" "CMakeLists.txt")
# 2) SDL2
check_and_download("https://github.com/libsdl-org/SDL/archive/refs/tags/release-2.32.10.zip" "release-2.32.10.zip" "SDL-release-2.32.10" "CMakeLists.txt")

View File

@ -173,8 +173,8 @@ void Character::resetPlayerAfterDeath() {
state.attack = 0;
}
void Character::forceReplan() {
if (!pathPlanner) return;
bool Character::forceReplan() {
if (!pathPlanner) return false;
const Eigen::Vector3f normalizedTarget(state.requestedWalkTarget.x(), 0.f, state.requestedWalkTarget.z());
state.pathWaypoints = pathPlanner(state.position, normalizedTarget);
@ -185,12 +185,13 @@ void Character::forceReplan() {
waypoint.y() = 0.f;
}
state.walkTarget = state.pathWaypoints.back();
return;
return true;
}
state.walkTarget = Eigen::Vector3f(state.position.x(), 0.f, state.position.z());
state.onArrivedCallback = nullptr;
state.onArrivedCallbackName.clear();
return false;
}
void Character::setTexture(std::shared_ptr<FRG::Texture> texture) {

View File

@ -59,7 +59,7 @@ public:
void setTarget(const Eigen::Vector3f& target, std::function<void()> onArrived = nullptr, std::string callbackName = "");
void stopAndRotateToTarget(int npcId);
void forceReplan();
bool forceReplan();
void stopInPlace() { state.stopInPlace(); }
// ---- Reset ----

View File

@ -103,7 +103,8 @@ public:
float modelScale = 0.12f;
Eigen::Quaternionf modelCorrectionRotation = Eigen::Quaternionf::Identity();
bool useGpuSkinning = true;
float collisionRadius = 0.45f;
//float collisionRadius = 0.45f;
float collisionRadius = 0.65f;
float interactionRadius = 0.0f;
// --- Pathfinding data (serializable parts) ---

View File

@ -22,8 +22,8 @@ int Environment::width = CONST_DEFAULT_WIDTH;
int Environment::height = CONST_DEFAULT_HEIGHT;
float Environment::zoom = DEFAULT_ZOOM;
bool Environment::enableLogging = false;
//bool Environment::enableLogging = true;
//bool Environment::enableLogging = false;
bool Environment::enableLogging = true;
SDL_Window* Environment::window = nullptr;

View File

@ -797,7 +797,7 @@ namespace FRG
"resources/navigation/dorm3_b.json",
"resources/navigation/dorm3_all_open.json",
};*/
params_dorm.navigationJsonPaths = {
"resources/navigation/dorm3_bca.txt", //0
"resources/navigation/dorm3_ca.txt", //1
@ -806,7 +806,7 @@ namespace FRG
"resources/navigation/dorm3_b.txt",
"resources/navigation/dorm3_all_open.txt", //5
};
/*
params_dorm.navigationJsonPaths = {
"resources/navigation/dorm0_large.json",
@ -1452,6 +1452,18 @@ namespace FRG
if (event.key.keysym.sym == SDLK_BACKSPACE) {
//menuManager.uiManager.onKeyBackspace();
}
if (event.key.keysym.sym == SDLK_1)
{
startNightTransition();
}
if (event.key.keysym.sym == SDLK_2)
{
startDarklandsTransition();
}
if (event.key.keysym.sym == SDLK_3)
{
logger() << "Player position: " << currentLocation()->player->state.position.transpose() << std::endl;
}
}
if (event.type == SDL_TEXTINPUT) {

View File

@ -450,7 +450,16 @@ namespace FRG
const auto addCharacter = [&](const Character* other) {
if (!other || other == self) return;
if (other->getHp() <= 0.f || !other->state.enabled) return;
if (other->isMoving()) return;
// Проверяем, находятся ли персонажи в состоянии фактического столкновения
const float dist = (other->state.position - self->state.position).norm();
const float collisionDist = self->state.collisionRadius + other->state.collisionRadius;
// Добавляем epsilon (0.1f) для учета погрешностей при входе в зону коллизии
const bool isColliding = dist <= (collisionDist + 0.1f);
// Игнорируем движущегося персонажа, только если мы с ним НЕ сталкиваемся в данный момент
if (other->isMoving() && !isColliding) return;
if (distancePointToSegmentXZ(other->state.position, start, end) > kDynamicObstacleInfluenceDist) {
return;
@ -460,7 +469,7 @@ namespace FRG
obs.position = Eigen::Vector3f(other->state.position.x(), navigation->getFloorY(), other->state.position.z());
obs.radius = (std::max)(0.0f, other->state.collisionRadius * 0.6f);
dynamicObstacles.push_back(obs);
};
};
addCharacter(player.get());
for (const auto& npc : npcs) {
@ -468,8 +477,8 @@ namespace FRG
}
return navigation->findPathToNearest(start, end, dynamicObstacles);
};
};
};
if (player) {
player->setPathPlanner(makePlanner(player.get()));
@ -591,6 +600,9 @@ namespace FRG
}
Character* Location::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) {
//return nullptr;
// Every NPC is treated as a vertical cylinder: radius 1.0m, height 1.85m,
// base at npc->position (the model's foot). Intersection = circle hit in the
// XZ plane, then clip the entry/exit t against the [yFoot, yFoot+height] slab.
@ -654,7 +666,6 @@ namespace FRG
else {
//logger() << "[RAYCAST_NPC] No NPC hit" << std::endl;
}
return closestNpc;
}
@ -1413,53 +1424,83 @@ namespace FRG
continue;
}
Eigen::Vector2f normal(1.f, 0.f);
if (dist > kMinSeparationEps) {
normal = delta / dist;
}
const float penetration = (minDist - dist);
const float push = penetration * 0.5f;
Eigen::Vector3f newA = a->state.position;
Eigen::Vector3f newB = b->state.position;
newA.x() -= normal.x() * push;
newA.z() -= normal.y() * push;
newA.y() = 0.f;
newB.x() += normal.x() * push;
newB.z() += normal.y() * push;
newB.y() = 0.f;
// 1. ПЕРЕСТРОЕНИЕ ПУТИ (выполняется при пересечении обычного радиуса коллизии)
const bool aWasMoving = a->isMoving();
const bool bWasMoving = b->isMoving();
if (navigation && navigation->isReady()) {
const bool aOk = navigation->isWalkable(newA);
const bool bOk = navigation->isWalkable(newB);
auto tryReplan = [&](Character* c) {
if (replanCooldownRemainingMs.find(c) != replanCooldownRemainingMs.end()) {
return; // Уже находится в откате
}
if (c->forceReplan()) {
replanCooldownRemainingMs[c] = 500; // таймаут на спам
}
};
if (aOk && bOk) {
if (aWasMoving || bWasMoving) {
if (aWasMoving && bWasMoving) {
tryReplan(a);
tryReplan(b);
}
else if (aWasMoving) {
tryReplan(a);
}
else if (bWasMoving) {
tryReplan(b);
}
}
// 2. ФИЗИЧЕСКОЕ ВЫТАЛКИВАНИЕ (только если дистанция меньше 0.5 * minDist)
const float hardMinDist = minDist * 0.5f;
if (dist < hardMinDist) {
Eigen::Vector2f normal(1.f, 0.f);
if (dist > kMinSeparationEps) {
normal = delta / dist;
}
// Вычисляем выталкивание относительно hardMinDist, а не minDist
const float penetration = (hardMinDist - dist);
const float push = penetration * 0.5f;
Eigen::Vector3f newA = a->state.position;
Eigen::Vector3f newB = b->state.position;
newA.x() -= normal.x() * push;
newA.z() -= normal.y() * push;
newA.y() = 0.f;
newB.x() += normal.x() * push;
newB.z() += normal.y() * push;
newB.y() = 0.f;
if (navigation && navigation->isReady()) {
const bool aOk = navigation->isWalkable(newA);
const bool bOk = navigation->isWalkable(newB);
if (aOk && bOk) {
a->state.position = newA;
b->state.position = newB;
}
else if (aOk && !bOk) {
a->state.position = newA;
}
else if (!aOk && bOk) {
b->state.position = newB;
}
}
else {
a->state.position = newA;
b->state.position = newB;
}
else if (aOk && !bOk) {
a->state.position = newA;
}
else if (!aOk && bOk) {
b->state.position = newB;
}
}
else {
a->state.position = newA;
b->state.position = newB;
}
// 3. ОСТАНОВКА И КОЛЛБЭКИ
if (a->state.isPlayer && !aWasMoving) a->stopInPlace();
if (b->state.isPlayer && !bWasMoving) b->stopInPlace();
if (aWasMoving && !bWasMoving) {
nudgeCharacterAside(b, a->state.position);
fireBumpCallbacks(a, b);
} else if (bWasMoving && !aWasMoving) {
}
else if (bWasMoving && !aWasMoving) {
nudgeCharacterAside(a, b->state.position);
fireBumpCallbacks(b, a);
}

View File

@ -124,6 +124,68 @@ void PathFinder::build(const std::string& configPath,
<< ", cell=" << cellSize << ", areas=" << areas.size() << "\n";
}
std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
const Eigen::Vector3f& end) const
{
return runAStar(start, end, walkable, false);
}
std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<DynamicObstacle>& dynamicObstacles) const
{
if (!ready || walkable.empty()) {
return {};
}
std::vector<unsigned char> walkableGrid = walkable;
// --- Логика внесения dynamicObstacles остается здесь ---
if (!dynamicObstacles.empty()) {
for (const DynamicObstacle& obstacle : dynamicObstacles) {
const float radius = obstacle.radius + agentRadius;
if (radius <= 0.0f) continue;
const float minWorldX = obstacle.position.x() - radius;
const float maxWorldX = obstacle.position.x() + radius;
const float minWorldZ = obstacle.position.z() - radius;
const float maxWorldZ = obstacle.position.z() + radius;
const int minCellX = (std::max)(0, static_cast<int>(std::floor((minWorldX - minX) / cellSize)));
const int maxCellX = (std::min)(gridWidth - 1, static_cast<int>(std::floor((maxWorldX - minX) / cellSize)));
const int minCellZ = (std::max)(0, static_cast<int>(std::floor((minWorldZ - minZ) / cellSize)));
const int maxCellZ = (std::min)(gridDepth - 1, static_cast<int>(std::floor((maxWorldZ - minZ) / cellSize)));
const float radiusSq = radius * radius;
for (int z = minCellZ; z <= maxCellZ; ++z) {
for (int x = minCellX; x <= maxCellX; ++x) {
const Cell cell{ x, z };
const Eigen::Vector3f center = cellCenter(cell);
const float dx = center.x() - obstacle.position.x();
const float dz = center.z() - obstacle.position.z();
if (dx * dx + dz * dz <= radiusSq) {
const int idx = indexOf(cell);
if (idx >= 0 && idx < static_cast<int>(walkableGrid.size())) {
walkableGrid[static_cast<size_t>(idx)] = 0;
}
}
}
}
}
}
return runAStar(start, end, walkableGrid, false);
}
std::vector<Eigen::Vector3f> PathFinder::findNearestReachableImpl(
const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<unsigned char>& walkableGrid) const
{
return runAStar(start, end, walkableGrid, true);
}
/*
std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
const Eigen::Vector3f& end) const
{
@ -494,7 +556,7 @@ std::vector<Eigen::Vector3f> PathFinder::findNearestReachableImpl(
path.erase(path.begin());
return path;
}
}*/
std::vector<Eigen::Vector3f> PathFinder::findPathToNearest(
const Eigen::Vector3f& start, const Eigen::Vector3f& end) const
@ -1075,4 +1137,125 @@ bool PathFinder::pointInPolygon(float x, float z, const std::vector<Eigen::Vecto
return inside;
}
std::vector<Eigen::Vector3f> PathFinder::runAStar(
const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<unsigned char>& grid,
bool returnNearestOnFail) const
{
if (!ready || grid.empty()) return {};
Cell startCell, endCell;
if (!findNearestWalkableCell(start, startCell, grid) ||
!findNearestWalkableCell(end, endCell, grid)) {
return {};
}
if (startCell.x == endCell.x && startCell.z == endCell.z) {
return { cellCenter(endCell) };
}
struct QueueNode {
int index = 0;
float priority = 0.0f;
bool operator<(const QueueNode& other) const { return priority > other.priority; }
};
const int cellCount = gridWidth * gridDepth;
std::vector<float> cost(static_cast<size_t>(cellCount), (std::numeric_limits<float>::max)());
std::vector<int> cameFrom(static_cast<size_t>(cellCount), -1);
std::priority_queue<QueueNode> open;
const int startIndex = indexOf(startCell);
const int endIndex = indexOf(endCell);
cost[static_cast<size_t>(startIndex)] = 0.0f;
open.push({ startIndex, 0.0f });
int bestIndex = startIndex;
float bestDist = distanceCells(startCell, endCell);
static const int offsets[8][2] = {
{ 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 },
{ 1, 1 }, { 1, -1 }, { -1, 1 }, { -1, -1 }
};
bool reached = false;
while (!open.empty()) {
const QueueNode current = open.top();
open.pop();
if (current.index == endIndex) {
bestIndex = endIndex;
reached = true;
break;
}
const Cell currentCell{ current.index % gridWidth, current.index / gridWidth };
if (cost[static_cast<size_t>(current.index)] < current.priority - distanceCells(currentCell, endCell)) {
continue;
}
if (returnNearestOnFail) {
const float d = distanceCells(currentCell, endCell);
if (d < bestDist) {
bestDist = d;
bestIndex = current.index;
}
}
for (const auto& offset : offsets) {
Cell next{ currentCell.x + offset[0], currentCell.z + offset[1] };
if (!isCellWalkable(next, grid)) continue;
const bool diagonal = offset[0] != 0 && offset[1] != 0;
if (diagonal) {
Cell h{ currentCell.x + offset[0], currentCell.z };
Cell v{ currentCell.x, currentCell.z + offset[1] };
if (!isCellWalkable(h, grid) || !isCellWalkable(v, grid)) continue;
}
const int nextIndex = indexOf(next);
const float stepCost = diagonal ? 1.41421356f : 1.0f;
const float newCost = cost[static_cast<size_t>(current.index)] + stepCost;
if (newCost >= cost[static_cast<size_t>(nextIndex)]) continue;
cost[static_cast<size_t>(nextIndex)] = newCost;
cameFrom[static_cast<size_t>(nextIndex)] = current.index;
open.push({ nextIndex, newCost + distanceCells(next, endCell) });
}
}
if (!reached && !returnNearestOnFail) return {};
if (bestIndex == startIndex) return {};
std::vector<Cell> cells;
for (int cur = bestIndex; cur != -1; cur = cameFrom[static_cast<size_t>(cur)]) {
cells.push_back({ cur % gridWidth, cur / gridWidth });
if (cur == startIndex) break;
}
std::reverse(cells.begin(), cells.end());
cells = smoothCells(cells, grid);
std::vector<Eigen::Vector3f> path;
path.reserve(cells.size());
for (const Cell& cell : cells) path.push_back(cellCenter(cell));
if (!path.empty() && (path.front() - Eigen::Vector3f(start.x(), floorY, start.z())).norm() < cellSize * 0.75f) {
path.erase(path.begin());
}
if (reached && !path.empty()) {
Cell requestedEndCell;
if (worldToCell(end, requestedEndCell) && requestedEndCell.x == endCell.x && requestedEndCell.z == endCell.z) {
path.back() = Eigen::Vector3f(end.x(), floorY, end.z());
}
}
return path;
}
} // namespace FRG

View File

@ -109,6 +109,11 @@ private:
std::vector<Eigen::Vector3f> findNearestReachableImpl(const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<unsigned char>& walkableGrid) const;
std::vector<Eigen::Vector3f> runAStar(const Eigen::Vector3f& start,
const Eigen::Vector3f& end,
const std::vector<unsigned char>& grid,
bool returnNearestOnFail) const;
};
} // namespace FRG