Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8df016c0a4 |
No files matched your search
@@ -131,8 +131,14 @@ set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT S
|
|||||||
# include-пути проекта
|
# include-пути проекта
|
||||||
target_include_directories(ShadowOverBishkekDemo PRIVATE
|
target_include_directories(ShadowOverBishkekDemo PRIVATE
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
|
"${CMAKE_CURRENT_SOURCE_DIR}/../src"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/Clipper2-Clipper2_2.0.1/CPP/build/Release/include"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#target_link_directories(ShadowOverBishkekDemo PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/Clipper2-Clipper2_2.0.1/CPP/build/Release/lib")
|
||||||
|
|
||||||
|
target_link_directories(ShadowOverBishkekDemo PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/Clipper2-Clipper2_2.0.1/CPP/build_debug/Debug/lib")
|
||||||
|
|
||||||
|
|
||||||
set_target_properties(ShadowOverBishkekDemo PROPERTIES
|
set_target_properties(ShadowOverBishkekDemo PROPERTIES
|
||||||
OUTPUT_NAME "ShadowOverBishkekDemo"
|
OUTPUT_NAME "ShadowOverBishkekDemo"
|
||||||
)
|
)
|
||||||
@@ -162,6 +168,7 @@ target_link_libraries(ShadowOverBishkekDemo PRIVATE
|
|||||||
sol2_external_lib
|
sol2_external_lib
|
||||||
SDL2_mixer_external_lib
|
SDL2_mixer_external_lib
|
||||||
openssl_ssl_external_lib
|
openssl_ssl_external_lib
|
||||||
|
Clipper2.lib
|
||||||
)
|
)
|
||||||
|
|
||||||
# Линкуем OpenGL (Windows)
|
# Линкуем OpenGL (Windows)
|
||||||
|
|||||||
+8
-12
@@ -93,8 +93,7 @@ void CharacterState::setTarget(
|
|||||||
const Eigen::Vector3f& target,
|
const Eigen::Vector3f& target,
|
||||||
std::string callbackName,
|
std::string callbackName,
|
||||||
const LocationNetworkState& location,
|
const LocationNetworkState& location,
|
||||||
const std::string& entityId,
|
const std::string& entityId)
|
||||||
const std::vector<PathFinder::DynamicObstacle>& dynamicObstacles)
|
|
||||||
{
|
{
|
||||||
const Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z());
|
const Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z());
|
||||||
const bool sameRequestedTarget =
|
const bool sameRequestedTarget =
|
||||||
@@ -115,10 +114,9 @@ void CharacterState::setTarget(
|
|||||||
movementBlockedTicks = 0;
|
movementBlockedTicks = 0;
|
||||||
navigationRetryTicks = 0;
|
navigationRetryTicks = 0;
|
||||||
blockingCharacterId.clear();
|
blockingCharacterId.clear();
|
||||||
pathWaypoints = location.navigationMaps.empty()
|
|
||||||
? std::vector<Eigen::Vector3f>{}
|
pathWaypoints = location.navigationMaps[location.activeNavigationIndex].findPathToNearest(this->position, target);
|
||||||
: location.planPathExceptMyself(
|
|
||||||
position, normalizedTarget, entityId, dynamicObstacles);
|
|
||||||
currentWaypointIndex = 0;
|
currentWaypointIndex = 0;
|
||||||
if (!pathWaypoints.empty()) {
|
if (!pathWaypoints.empty()) {
|
||||||
for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f;
|
for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f;
|
||||||
@@ -132,15 +130,13 @@ void CharacterState::setTarget(
|
|||||||
|
|
||||||
void CharacterState::forceReplan(
|
void CharacterState::forceReplan(
|
||||||
const LocationNetworkState& location,
|
const LocationNetworkState& location,
|
||||||
const std::string& entityId,
|
const std::string& entityId)
|
||||||
const std::vector<PathFinder::DynamicObstacle>& dynamicObstacles)
|
|
||||||
{
|
{
|
||||||
const Eigen::Vector3f normalizedTarget(
|
const Eigen::Vector3f normalizedTarget(
|
||||||
requestedWalkTarget.x(), 0.f, requestedWalkTarget.z());
|
requestedWalkTarget.x(), 0.f, requestedWalkTarget.z());
|
||||||
pathWaypoints = location.navigationMaps.empty()
|
|
||||||
? std::vector<Eigen::Vector3f>{}
|
pathWaypoints = location.navigationMaps[location.activeNavigationIndex].findPathToNearest(this->position, normalizedTarget);
|
||||||
: location.planPathExceptMyself(
|
|
||||||
position, normalizedTarget, entityId, dynamicObstacles);
|
|
||||||
currentWaypointIndex = 0;
|
currentWaypointIndex = 0;
|
||||||
movementBlockedTicks = 0;
|
movementBlockedTicks = 0;
|
||||||
navigationRetryTicks = 0;
|
navigationRetryTicks = 0;
|
||||||
|
|||||||
@@ -151,13 +151,9 @@ public:
|
|||||||
void setTarget(const Eigen::Vector3f& target,
|
void setTarget(const Eigen::Vector3f& target,
|
||||||
std::string callbackName,
|
std::string callbackName,
|
||||||
const LocationNetworkState& location,
|
const LocationNetworkState& location,
|
||||||
const std::string& entityId,
|
const std::string& entityId);
|
||||||
const std::vector<PathFinder::DynamicObstacle>&
|
|
||||||
dynamicObstacles = {});
|
|
||||||
void forceReplan(const LocationNetworkState& location,
|
void forceReplan(const LocationNetworkState& location,
|
||||||
const std::string& entityId,
|
const std::string& entityId);
|
||||||
const std::vector<PathFinder::DynamicObstacle>&
|
|
||||||
dynamicObstacles = {});
|
|
||||||
void stopAndRotateToTarget(std::string characterId);
|
void stopAndRotateToTarget(std::string characterId);
|
||||||
void resetPlayerAfterDeath();
|
void resetPlayerAfterDeath();
|
||||||
void tryAttackCombo();
|
void tryAttackCombo();
|
||||||
|
|||||||
+10
-11
@@ -851,8 +851,8 @@ namespace FRG
|
|||||||
float t0 = (worldMin[axis] - rayOrigin[axis]) * invD;
|
float t0 = (worldMin[axis] - rayOrigin[axis]) * invD;
|
||||||
float t1 = (worldMax[axis] - rayOrigin[axis]) * invD;
|
float t1 = (worldMax[axis] - rayOrigin[axis]) * invD;
|
||||||
if (invD < 0.0f) std::swap(t0, t1);
|
if (invD < 0.0f) std::swap(t0, t1);
|
||||||
tMin = max(tMin, t0);
|
tMin = std::max(tMin, t0);
|
||||||
tMax = min(tMax, t1);
|
tMax = std::min(tMax, t1);
|
||||||
if (tMax < tMin) { miss = true; break; }
|
if (tMax < tMin) { miss = true; break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1838,14 +1838,13 @@ namespace FRG
|
|||||||
state->activeNavigationIndex >= static_cast<int>(state->navigationMaps.size())) {
|
state->activeNavigationIndex >= static_cast<int>(state->navigationMaps.size())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const auto& areas =
|
const auto& area =
|
||||||
state->navigationMaps[state->activeNavigationIndex].getAreas();
|
state->navigationMaps[state->activeNavigationIndex].getArea();
|
||||||
const bool found = std::any_of(
|
if (area.name != areaName)
|
||||||
areas.begin(), areas.end(),
|
{
|
||||||
[&](const PathFinder::NavigationArea& area) {
|
return false;
|
||||||
return area.name == areaName;
|
}
|
||||||
});
|
|
||||||
if (!found) return false;
|
|
||||||
gameServer_.submit(SetNavigationAreaAvailableCommand{
|
gameServer_.submit(SetNavigationAreaAvailableCommand{
|
||||||
locationId_, areaName, available});
|
locationId_, areaName, available});
|
||||||
return true;
|
return true;
|
||||||
@@ -2253,7 +2252,7 @@ namespace FRG
|
|||||||
const float minInclination = M_PI * 30.f / 180.f;
|
const float minInclination = M_PI * 30.f / 180.f;
|
||||||
const float maxInclination = M_PI * 0.5f;
|
const float maxInclination = M_PI * 0.5f;
|
||||||
localState_->cameraInclination =
|
localState_->cameraInclination =
|
||||||
max(minInclination, min(maxInclination, inclination));
|
std::max(minInclination, std::min(maxInclination, inclination));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ namespace FRG
|
|||||||
state->activeNavigationIndex >= static_cast<int>(state->navigationMaps.size())) return;
|
state->activeNavigationIndex >= static_cast<int>(state->navigationMaps.size())) return;
|
||||||
const PathFinder& navigation =
|
const PathFinder& navigation =
|
||||||
state->navigationMaps[state->activeNavigationIndex];
|
state->navigationMaps[state->activeNavigationIndex];
|
||||||
const float y = navigation.getFloorY() + 0.02f;
|
const float y = static_cast<float>(navigation.getFloorY()) + 0.02f;
|
||||||
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
||||||
for (const auto& obs : navigation.getObstaclePolygons()) {
|
for (const auto& obs : navigation.getObstaclePolygons()) {
|
||||||
if (obs.polygon.size() < 3) continue;
|
if (obs.polygon.size() < 3) continue;
|
||||||
VertexRenderStruct mesh;
|
VertexRenderStruct mesh;
|
||||||
mesh.data = CreatePolygonFloor(obs.polygon, y, red);
|
mesh.data = CreatePolygonFloor(toEigenVector(obs.polygon), y, red);
|
||||||
mesh.RefreshVBO();
|
mesh.RefreshVBO();
|
||||||
navigationEditorNavMeshes.push_back(std::move(mesh));
|
navigationEditorNavMeshes.push_back(std::move(mesh));
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ namespace FRG
|
|||||||
if (!state || state->activeNavigationIndex < 0 ||
|
if (!state || state->activeNavigationIndex < 0 ||
|
||||||
state->activeNavigationIndex >= static_cast<int>(state->navigationMaps.size())) return;
|
state->activeNavigationIndex >= static_cast<int>(state->navigationMaps.size())) return;
|
||||||
const float y =
|
const float y =
|
||||||
state->navigationMaps[state->activeNavigationIndex].getFloorY() + 0.05f;
|
static_cast<float>(state->navigationMaps[state->activeNavigationIndex].getFloorY()) + 0.05f;
|
||||||
const float s = 0.2f;
|
const float s = 0.2f;
|
||||||
|
|
||||||
for (const auto& pt : navigationEditorPoints) {
|
for (const auto& pt : navigationEditorPoints) {
|
||||||
@@ -133,7 +133,7 @@ namespace FRG
|
|||||||
PathFinder::ObstaclePolygon poly;
|
PathFinder::ObstaclePolygon poly;
|
||||||
poly.name = "editor_obstacle_" + std::to_string(++navigationEditorObstacleCounter);
|
poly.name = "editor_obstacle_" + std::to_string(++navigationEditorObstacleCounter);
|
||||||
for (const auto& pt : navigationEditorPoints) {
|
for (const auto& pt : navigationEditorPoints) {
|
||||||
poly.polygon.emplace_back(pt.x(), pt.z());
|
poly.polygon.emplace_back(static_cast<FRG::fp>(pt.x()), static_cast<FRG::fp>(pt.z()));
|
||||||
}
|
}
|
||||||
|
|
||||||
loc.gameServer_.submit(AddNavigationObstacleCommand{
|
loc.gameServer_.submit(AddNavigationObstacleCommand{
|
||||||
@@ -146,10 +146,10 @@ namespace FRG
|
|||||||
if (!state || state->activeNavigationIndex < 0 ||
|
if (!state || state->activeNavigationIndex < 0 ||
|
||||||
state->activeNavigationIndex >= static_cast<int>(state->navigationMaps.size())) return;
|
state->activeNavigationIndex >= static_cast<int>(state->navigationMaps.size())) return;
|
||||||
const float y =
|
const float y =
|
||||||
state->navigationMaps[state->activeNavigationIndex].getFloorY() + 0.02f;
|
static_cast<float>(state->navigationMaps[state->activeNavigationIndex].getFloorY()) + 0.02f;
|
||||||
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
const Eigen::Vector3f red(1.0f, 0.0f, 0.0f);
|
||||||
VertexRenderStruct mesh;
|
VertexRenderStruct mesh;
|
||||||
mesh.data = CreatePolygonFloor(poly.polygon, y, red);
|
mesh.data = CreatePolygonFloor(toEigenVector(poly.polygon), y, red);
|
||||||
mesh.RefreshVBO();
|
mesh.RefreshVBO();
|
||||||
navigationEditorNavMeshes.push_back(std::move(mesh));
|
navigationEditorNavMeshes.push_back(std::move(mesh));
|
||||||
}
|
}
|
||||||
@@ -379,9 +379,9 @@ namespace FRG
|
|||||||
|
|
||||||
auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex];
|
auto& target = loc.interactiveObjects[selectedInteractiveObjectIndex];
|
||||||
target.state.boundsMin = Eigen::Vector3f(
|
target.state.boundsMin = Eigen::Vector3f(
|
||||||
min(localA.x(), localB.x()), 0.f, min(localA.z(), localB.z()));
|
std::min(localA.x(), localB.x()), 0.f, std::min(localA.z(), localB.z()));
|
||||||
target.state.boundsMax = Eigen::Vector3f(
|
target.state.boundsMax = Eigen::Vector3f(
|
||||||
max(localA.x(), localB.x()), 1.f, max(localA.z(), localB.z()));
|
std::max(localA.x(), localB.x()), 1.f, std::max(localA.z(), localB.z()));
|
||||||
|
|
||||||
boundsClickCount = 0;
|
boundsClickCount = 0;
|
||||||
buildInteractiveObjectBoundsMeshes();
|
buildInteractiveObjectBoundsMeshes();
|
||||||
|
|||||||
+36
-14
@@ -126,6 +126,7 @@ const CharacterState* LocationGameState::findCharacter(
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
std::vector<PathFinder::DynamicObstacle> LocationGameState::collectPathObstacles(
|
std::vector<PathFinder::DynamicObstacle> LocationGameState::collectPathObstacles(
|
||||||
const std::string& requesterId,
|
const std::string& requesterId,
|
||||||
const PlayerStateMap& players,
|
const PlayerStateMap& players,
|
||||||
@@ -154,7 +155,7 @@ std::vector<PathFinder::DynamicObstacle> LocationGameState::collectPathObstacles
|
|||||||
obstacles.push_back({character->position, character->collisionRadius});
|
obstacles.push_back({character->position, character->collisionRadius});
|
||||||
}
|
}
|
||||||
return obstacles;
|
return obstacles;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
bool LocationGameState::setCharacterDestination(
|
bool LocationGameState::setCharacterDestination(
|
||||||
const std::string& id,
|
const std::string& id,
|
||||||
@@ -166,8 +167,7 @@ bool LocationGameState::setCharacterDestination(
|
|||||||
CharacterState* character = findCharacter(id, players);
|
CharacterState* character = findCharacter(id, players);
|
||||||
if (!character || !location) return false;
|
if (!character || !location) return false;
|
||||||
character->setTarget(
|
character->setTarget(
|
||||||
target, std::move(callbackName), *location, id,
|
target, std::move(callbackName), *location, id);
|
||||||
collectPathObstacles(id, players, forcedObstacleId));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,8 +193,7 @@ bool LocationGameState::replanCharacter(
|
|||||||
const CharacterState* target = findCharacter(selectedTarget, players);
|
const CharacterState* target = findCharacter(selectedTarget, players);
|
||||||
if (target) {
|
if (target) {
|
||||||
character->setTarget(
|
character->setTarget(
|
||||||
target->position, {}, *location, id,
|
target->position, {}, *location, id);
|
||||||
collectPathObstacles(id, players, forcedObstacleId));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,13 +206,12 @@ bool LocationGameState::replanCharacter(
|
|||||||
if ((homeTarget - character->position).norm() >
|
if ((homeTarget - character->position).norm() >
|
||||||
CharacterState::kWalkThreshold) {
|
CharacterState::kWalkThreshold) {
|
||||||
character->setTarget(
|
character->setTarget(
|
||||||
homeTarget, {}, *location, id,
|
homeTarget, {}, *location, id);
|
||||||
collectPathObstacles(id, players, forcedObstacleId));
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
character->forceReplan(
|
character->forceReplan(
|
||||||
*location, id, collectPathObstacles(id, players, forcedObstacleId));
|
*location, id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +433,7 @@ LocationSimulationEvents LocationGameState::update(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
std::vector<CharacterMovementObstacle> movementObstacles;
|
std::vector<CharacterMovementObstacle> movementObstacles;
|
||||||
movementObstacles.reserve(playerIds.size() + npcIds.size());
|
movementObstacles.reserve(playerIds.size() + npcIds.size());
|
||||||
auto addMovementObstacle =
|
auto addMovementObstacle =
|
||||||
@@ -468,8 +466,9 @@ LocationSimulationEvents LocationGameState::update(
|
|||||||
std::sort(
|
std::sort(
|
||||||
movementObstacles.begin(), movementObstacles.end(),
|
movementObstacles.begin(), movementObstacles.end(),
|
||||||
[](const CharacterMovementObstacle& lhs,
|
[](const CharacterMovementObstacle& lhs,
|
||||||
const CharacterMovementObstacle& rhs) { return lhs.id < rhs.id; });
|
const CharacterMovementObstacle& rhs) { return lhs.id < rhs.id; });*/
|
||||||
|
|
||||||
|
/*
|
||||||
auto advance = [&](const std::string& id, CharacterState& state) {
|
auto advance = [&](const std::string& id, CharacterState& state) {
|
||||||
CharacterUpdateContext context;
|
CharacterUpdateContext context;
|
||||||
context.characterId = id;
|
context.characterId = id;
|
||||||
@@ -499,14 +498,36 @@ LocationSimulationEvents LocationGameState::update(
|
|||||||
events.deathAnimationComplete || events.navigationRequested) {
|
events.deathAnimationComplete || events.navigationRequested) {
|
||||||
result.characters.push_back({id, std::move(events)});
|
result.characters.push_back({id, std::move(events)});
|
||||||
}
|
}
|
||||||
};
|
};*/
|
||||||
|
/*
|
||||||
for (const std::string& id : playerIds)
|
for (const std::string& id : playerIds)
|
||||||
if (players.at(id)->enabled)
|
if (players.at(id)->enabled)
|
||||||
advance(id, *players.at(id));
|
advance(id, *players.at(id));
|
||||||
for (const std::string& id : npcIds)
|
for (const std::string& id : npcIds)
|
||||||
|
if (npcs.at(id)->enablesd)
|
||||||
|
advance(id, *npcs.at(id));*/
|
||||||
|
|
||||||
|
for (const std::string& id : playerIds)
|
||||||
|
{
|
||||||
|
if (players.at(id)->enabled)
|
||||||
|
{
|
||||||
|
CharacterUpdateContext context;
|
||||||
|
context.characterId = id;
|
||||||
|
auto& state = *players.at(id);
|
||||||
|
CharacterUpdateEvents events = state.update(deltaMs, context);
|
||||||
|
//advance(id, *players.at(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string& id : npcIds)
|
||||||
|
{
|
||||||
if (npcs.at(id)->enabled)
|
if (npcs.at(id)->enabled)
|
||||||
advance(id, *npcs.at(id));
|
{
|
||||||
|
//advance(id, *npcs.at(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (const CharacterSimulationEvent& source : result.characters) {
|
for (const CharacterSimulationEvent& source : result.characters) {
|
||||||
if (!source.events.attackImpact) continue;
|
if (!source.events.attackImpact) continue;
|
||||||
@@ -523,6 +544,7 @@ LocationSimulationEvents LocationGameState::update(
|
|||||||
source.events.attackDirection});
|
source.events.attackDirection});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
auto player = players.find("player1");
|
auto player = players.find("player1");
|
||||||
if (!interactiveObjectsLocked &&
|
if (!interactiveObjectsLocked &&
|
||||||
!location->targetInteractiveObjectId.empty() &&
|
!location->targetInteractiveObjectId.empty() &&
|
||||||
@@ -555,7 +577,7 @@ LocationSimulationEvents LocationGameState::update(
|
|||||||
result.npcInteractionId = location->targetInteractNpcId;
|
result.npcInteractionId = location->targetInteractNpcId;
|
||||||
location->targetInteractNpcId.clear();
|
location->targetInteractNpcId.clear();
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ struct LocationGameState {
|
|||||||
CharacterState* findCharacter(const std::string& id, PlayerStateMap& players);
|
CharacterState* findCharacter(const std::string& id, PlayerStateMap& players);
|
||||||
const CharacterState* findCharacter(
|
const CharacterState* findCharacter(
|
||||||
const std::string& id, const PlayerStateMap& players) const;
|
const std::string& id, const PlayerStateMap& players) const;
|
||||||
std::vector<PathFinder::DynamicObstacle> collectPathObstacles(
|
/*std::vector<PathFinder::DynamicObstacle> collectPathObstacles(
|
||||||
const std::string& requesterId,
|
const std::string& requesterId,
|
||||||
const PlayerStateMap& players,
|
const PlayerStateMap& players,
|
||||||
const std::string& forcedObstacleId = {}) const;
|
const std::string& forcedObstacleId = {}) const;*/
|
||||||
bool setCharacterDestination(
|
bool setCharacterDestination(
|
||||||
const std::string& id,
|
const std::string& id,
|
||||||
const Eigen::Vector3f& target,
|
const Eigen::Vector3f& target,
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ void loadSinglePlayerPersistence(
|
|||||||
|
|
||||||
|
|
||||||
// Entity ownership stays above this navigation-only layer.
|
// Entity ownership stays above this navigation-only layer.
|
||||||
std::vector<Eigen::Vector3f> LocationNetworkState::planPathExceptMyself(
|
/**td::vector<Eigen::Vector3f> LocationNetworkState::planPathExceptMyself(
|
||||||
const Eigen::Vector3f& start,
|
const Eigen::Vector3f& start,
|
||||||
const Eigen::Vector3f& end,
|
const Eigen::Vector3f& end,
|
||||||
const std::string& entityId,
|
const std::string& entityId,
|
||||||
@@ -135,7 +135,7 @@ void loadSinglePlayerPersistence(
|
|||||||
return {};
|
return {};
|
||||||
return navigationMaps[activeNavigationIndex].findPathToNearest(
|
return navigationMaps[activeNavigationIndex].findPathToNearest(
|
||||||
start, end, dynamicObstacles);
|
start, end, dynamicObstacles);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
void LocationNetworkState::initNavigation(
|
void LocationNetworkState::initNavigation(
|
||||||
const std::vector<std::string>& paths, const std::string& zipFile)
|
const std::vector<std::string>& paths, const std::string& zipFile)
|
||||||
|
|||||||
+2
-2
@@ -41,11 +41,11 @@ struct LocationNetworkState : public ISaveable {
|
|||||||
//This value is populated from the JSON configs and should not be changed during the regular gameplay.
|
//This value is populated from the JSON configs and should not be changed during the regular gameplay.
|
||||||
//It might be changed only in the editor mode during development.
|
//It might be changed only in the editor mode during development.
|
||||||
std::vector<PathFinder> navigationMaps;
|
std::vector<PathFinder> navigationMaps;
|
||||||
std::vector<Eigen::Vector3f> planPathExceptMyself(
|
/*std::vector<Eigen::Vector3f> planPathExceptMyself(
|
||||||
const Eigen::Vector3f& start,
|
const Eigen::Vector3f& start,
|
||||||
const Eigen::Vector3f& end,
|
const Eigen::Vector3f& end,
|
||||||
const std::string& entityId,
|
const std::string& entityId,
|
||||||
const std::vector<PathFinder::DynamicObstacle>& dynamicObstacles = {}) const;
|
const std::vector<PathFinder::DynamicObstacle>& dynamicObstacles = {}) const;*/
|
||||||
void initNavigation(
|
void initNavigation(
|
||||||
const std::vector<std::string>& paths,
|
const std::vector<std::string>& paths,
|
||||||
const std::string& zipFile = std::string());
|
const std::string& zipFile = std::string());
|
||||||
|
|||||||
+3
-7
@@ -17,10 +17,6 @@
|
|||||||
|
|
||||||
namespace FRG {
|
namespace FRG {
|
||||||
|
|
||||||
#ifdef __EMSCRIPTEN__
|
|
||||||
//extern Game* Game::s_instance;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Localization
|
// Localization
|
||||||
static const std::string EMPTY_LANGUAGE_RU = u8"(пусто)";
|
static const std::string EMPTY_LANGUAGE_RU = u8"(пусто)";
|
||||||
static const std::string EMPTY_LANGUAGE_EN = "(empty)";
|
static const std::string EMPTY_LANGUAGE_EN = "(empty)";
|
||||||
@@ -81,7 +77,7 @@ namespace FRG {
|
|||||||
pushWord(currentWord);
|
pushWord(currentWord);
|
||||||
if (!currentLine.empty()) flushLine();
|
if (!currentLine.empty()) flushLine();
|
||||||
|
|
||||||
return max(1, lines);
|
return std::max(1, lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string trimChatPreview(const std::string& msg, int maxChars = 22) {
|
static std::string trimChatPreview(const std::string& msg, int maxChars = 22) {
|
||||||
@@ -2257,9 +2253,9 @@ namespace FRG {
|
|||||||
|
|
||||||
float MenuManager::ToastEntry::currentAlpha() const {
|
float MenuManager::ToastEntry::currentAlpha() const {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case State::FadeIn: return min(timer / TOAST_FADE_MS, 1.0f);
|
case State::FadeIn: return std::min(timer / TOAST_FADE_MS, 1.0f);
|
||||||
case State::Visible: return 1.0f;
|
case State::Visible: return 1.0f;
|
||||||
case State::FadeOut: return max(1.0f - timer / TOAST_FADE_MS, 0.0f);
|
case State::FadeOut: return std::max(1.0f - timer / TOAST_FADE_MS, 0.0f);
|
||||||
}
|
}
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "../navigation/PathFinder.h"
|
||||||
|
|
||||||
namespace FRG {
|
namespace FRG {
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ struct SetNavigationAreaAvailableCommand {
|
|||||||
struct AddNavigationObstacleCommand {
|
struct AddNavigationObstacleCommand {
|
||||||
std::string locationId;
|
std::string locationId;
|
||||||
std::string name;
|
std::string name;
|
||||||
std::vector<Eigen::Vector2f> polygon;
|
std::vector<Vec2fp> polygon;
|
||||||
};
|
};
|
||||||
struct AddInventoryItemCommand { std::string itemId; };
|
struct AddInventoryItemCommand { std::string itemId; };
|
||||||
struct RemoveInventoryItemCommand { std::string itemId; };
|
struct RemoveInventoryItemCommand { std::string itemId; };
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::build(const std::string& configPath,
|
void PathFinderOld::build(const std::string& configPath,
|
||||||
const std::string& zipPath)
|
const std::string& zipPath)
|
||||||
{
|
{
|
||||||
ready = false;
|
ready = false;
|
||||||
@@ -115,22 +115,23 @@ void PathFinder::build(const std::string& configPath,
|
|||||||
<< ", cell=" << cellSize << ", areas=" << areas.size() << "\n";
|
<< ", cell=" << cellSize << ", areas=" << areas.size() << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
|
std::vector<Eigen::Vector3f> PathFinderOld::findPath(const Eigen::Vector3f& start,
|
||||||
const Eigen::Vector3f& end) const
|
const Eigen::Vector3f& end) const
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> lock(*queryMutex);
|
//const std::lock_guard<std::mutex> lock(*queryMutex);
|
||||||
return runSearch(start, end, 0, false);
|
//return runSearch(start, end, 0, false);
|
||||||
|
return std::vector<Eigen::Vector3f>{start, end};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Eigen::Vector3f> PathFinder::findPath(const Eigen::Vector3f& start,
|
/*
|
||||||
|
std::vector<Eigen::Vector3f> PathFinderOld::findPath(const Eigen::Vector3f& start,
|
||||||
const Eigen::Vector3f& end,
|
const Eigen::Vector3f& end,
|
||||||
const std::vector<DynamicObstacle>& dynamicObstacles) const
|
const std::vector<DynamicObstacle>& dynamicObstacles) const
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> lock(*queryMutex);
|
const std::lock_guard<std::mutex> lock(*queryMutex);
|
||||||
return runSearch(start, end, buildDynamicOverlay(dynamicObstacles), false);
|
return runSearch(start, end, buildDynamicOverlay(dynamicObstacles), false);
|
||||||
}
|
}*/
|
||||||
|
std::uint32_t PathFinderOld::buildDynamicOverlay(
|
||||||
std::uint32_t PathFinder::buildDynamicOverlay(
|
|
||||||
const std::vector<DynamicObstacle>& dynamicObstacles) const
|
const std::vector<DynamicObstacle>& dynamicObstacles) const
|
||||||
{
|
{
|
||||||
const size_t cellCount = walkable.size();
|
const size_t cellCount = walkable.size();
|
||||||
@@ -172,7 +173,7 @@ std::uint32_t PathFinder::buildDynamicOverlay(
|
|||||||
return dynamicGeneration;
|
return dynamicGeneration;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::isCellWalkableOverlay(
|
bool PathFinderOld::isCellWalkableOverlay(
|
||||||
const Cell& cell, std::uint32_t generation) const
|
const Cell& cell, std::uint32_t generation) const
|
||||||
{
|
{
|
||||||
if (!isCellWalkable(cell)) return false;
|
if (!isCellWalkable(cell)) return false;
|
||||||
@@ -180,7 +181,7 @@ bool PathFinder::isCellWalkableOverlay(
|
|||||||
dynamicBlockedGeneration[static_cast<size_t>(indexOf(cell))] != generation;
|
dynamicBlockedGeneration[static_cast<size_t>(indexOf(cell))] != generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::findNearestWalkableCellOverlay(
|
bool PathFinderOld::findNearestWalkableCellOverlay(
|
||||||
const Eigen::Vector3f& point, Cell& out, std::uint32_t generation) const
|
const Eigen::Vector3f& point, Cell& out, std::uint32_t generation) const
|
||||||
{
|
{
|
||||||
Cell origin;
|
Cell origin;
|
||||||
@@ -212,7 +213,7 @@ bool PathFinder::findNearestWalkableCellOverlay(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::hasLineOfSightOverlay(
|
bool PathFinderOld::hasLineOfSightOverlay(
|
||||||
const Cell& from, const Cell& to, std::uint32_t generation) const
|
const Cell& from, const Cell& to, std::uint32_t generation) const
|
||||||
{
|
{
|
||||||
int x = from.x;
|
int x = from.x;
|
||||||
@@ -244,7 +245,7 @@ bool PathFinder::hasLineOfSightOverlay(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PathFinder::Cell> PathFinder::smoothCellsOverlay(
|
std::vector<PathFinderOld::Cell> PathFinderOld::smoothCellsOverlay(
|
||||||
const std::vector<Cell>& cells, std::uint32_t generation) const
|
const std::vector<Cell>& cells, std::uint32_t generation) const
|
||||||
{
|
{
|
||||||
if (cells.size() <= 2) return cells;
|
if (cells.size() <= 2) return cells;
|
||||||
@@ -263,7 +264,7 @@ std::vector<PathFinder::Cell> PathFinder::smoothCellsOverlay(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Eigen::Vector3f> PathFinder::runSearch(
|
std::vector<Eigen::Vector3f> PathFinderOld::runSearch(
|
||||||
const Eigen::Vector3f& start,
|
const Eigen::Vector3f& start,
|
||||||
const Eigen::Vector3f& end,
|
const Eigen::Vector3f& end,
|
||||||
std::uint32_t obstacleGeneration,
|
std::uint32_t obstacleGeneration,
|
||||||
@@ -385,23 +386,25 @@ std::vector<Eigen::Vector3f> PathFinder::runSearch(
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Eigen::Vector3f> PathFinder::findPathToNearest(
|
std::vector<Eigen::Vector3f> PathFinderOld::findPathToNearest(
|
||||||
const Eigen::Vector3f& start, const Eigen::Vector3f& end) const
|
const Eigen::Vector3f& start, const Eigen::Vector3f& end) const
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> lock(*queryMutex);
|
//const std::lock_guard<std::mutex> lock(*queryMutex);
|
||||||
return runSearch(start, end, 0, true);
|
// return runSearch(start, end, 0, true);
|
||||||
|
return std::vector<Eigen::Vector3f>{start, end};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Eigen::Vector3f> PathFinder::findPathToNearest(
|
/*
|
||||||
|
std::vector<Eigen::Vector3f> PathFinderOld::findPathToNearest(
|
||||||
const Eigen::Vector3f& start, const Eigen::Vector3f& end,
|
const Eigen::Vector3f& start, const Eigen::Vector3f& end,
|
||||||
const std::vector<DynamicObstacle>& dynamicObstacles) const
|
const std::vector<DynamicObstacle>& dynamicObstacles) const
|
||||||
{
|
{
|
||||||
const std::lock_guard<std::mutex> lock(*queryMutex);
|
const std::lock_guard<std::mutex> lock(*queryMutex);
|
||||||
return runSearch(
|
return runSearch(
|
||||||
start, end, buildDynamicOverlay(dynamicObstacles), true);
|
start, end, buildDynamicOverlay(dynamicObstacles), true);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
bool PathFinder::setAreaAvailable(const std::string& areaName, bool available)
|
bool PathFinderOld::setAreaAvailable(const std::string& areaName, bool available)
|
||||||
{
|
{
|
||||||
bool found = false;
|
bool found = false;
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
@@ -420,13 +423,13 @@ bool PathFinder::setAreaAvailable(const std::string& areaName, bool available)
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::isWalkable(const Eigen::Vector3f& point) const
|
bool PathFinderOld::isWalkable(const Eigen::Vector3f& point) const
|
||||||
{
|
{
|
||||||
Cell cell;
|
Cell cell;
|
||||||
return worldToCell(point, cell) && isCellWalkable(cell, walkable);
|
return worldToCell(point, cell) && isCellWalkable(cell, walkable);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::addObstaclePolygon(const ObstaclePolygon& polygon)
|
void PathFinderOld::addObstaclePolygon(const ObstaclePolygon& polygon)
|
||||||
{
|
{
|
||||||
obstaclePolygons.push_back(polygon);
|
obstaclePolygons.push_back(polygon);
|
||||||
if (ready) {
|
if (ready) {
|
||||||
@@ -434,7 +437,7 @@ void PathFinder::addObstaclePolygon(const ObstaclePolygon& polygon)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::saveConfig(const std::string& path) const
|
bool PathFinderOld::saveConfig(const std::string& path) const
|
||||||
{
|
{
|
||||||
json root;
|
json root;
|
||||||
root["cellSize"] = cellSize;
|
root["cellSize"] = cellSize;
|
||||||
@@ -481,7 +484,7 @@ bool PathFinder::saveConfig(const std::string& path) const
|
|||||||
return saveJsonToFile(root, path);
|
return saveJsonToFile(root, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::saveGrid(const std::string& path) const
|
bool PathFinderOld::saveGrid(const std::string& path) const
|
||||||
{
|
{
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
if (!ready) return false;
|
if (!ready) return false;
|
||||||
@@ -510,7 +513,7 @@ bool PathFinder::saveGrid(const std::string& path) const
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::loadGrid(const std::string& path, const std::string& zipPath)
|
bool PathFinderOld::loadGrid(const std::string& path, const std::string& zipPath)
|
||||||
{
|
{
|
||||||
std::string content;
|
std::string content;
|
||||||
try {
|
try {
|
||||||
@@ -565,7 +568,7 @@ bool PathFinder::loadGrid(const std::string& path, const std::string& zipPath)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::loadConfig(const std::string& configPath, const std::string& zipPath)
|
void PathFinderOld::loadConfig(const std::string& configPath, const std::string& zipPath)
|
||||||
{
|
{
|
||||||
cellSize = 0.4f;
|
cellSize = 0.4f;
|
||||||
agentRadius = 0.45f;
|
agentRadius = 0.45f;
|
||||||
@@ -640,7 +643,7 @@ void PathFinder::loadConfig(const std::string& configPath, const std::string& zi
|
|||||||
boundaryPadding = (std::max)(boundaryPadding, 0.0f);
|
boundaryPadding = (std::max)(boundaryPadding, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::resetGridBounds()
|
void PathFinderOld::resetGridBounds()
|
||||||
{
|
{
|
||||||
if (areas.empty()) {
|
if (areas.empty()) {
|
||||||
gridWidth = 0;
|
gridWidth = 0;
|
||||||
@@ -682,14 +685,14 @@ void PathFinder::resetGridBounds()
|
|||||||
gridDepth = static_cast<int>(std::ceil((maxZ - minZ) / cellSize));
|
gridDepth = static_cast<int>(std::ceil((maxZ - minZ) / cellSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::rebuildWalkableGrid()
|
void PathFinderOld::rebuildWalkableGrid()
|
||||||
{
|
{
|
||||||
walkable.assign(static_cast<size_t>(gridWidth * gridDepth), 0);
|
walkable.assign(static_cast<size_t>(gridWidth * gridDepth), 0);
|
||||||
markAvailableAreasWalkable();
|
markAvailableAreasWalkable();
|
||||||
markObstaclePolygonsBlocked();
|
markObstaclePolygonsBlocked();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::markAvailableAreasWalkable()
|
void PathFinderOld::markAvailableAreasWalkable()
|
||||||
{
|
{
|
||||||
const auto insideAvailableArea = [this](const Eigen::Vector2f& point) {
|
const auto insideAvailableArea = [this](const Eigen::Vector2f& point) {
|
||||||
for (const NavigationArea& area : areas) {
|
for (const NavigationArea& area : areas) {
|
||||||
@@ -763,7 +766,7 @@ void PathFinder::markAvailableAreasWalkable()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PathFinder::markObstaclePolygonsBlocked()
|
void PathFinderOld::markObstaclePolygonsBlocked()
|
||||||
{
|
{
|
||||||
if (obstaclePolygons.empty()) {
|
if (obstaclePolygons.empty()) {
|
||||||
return;
|
return;
|
||||||
@@ -800,7 +803,7 @@ void PathFinder::markObstaclePolygonsBlocked()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::worldToCell(const Eigen::Vector3f& point, Cell& out) const
|
bool PathFinderOld::worldToCell(const Eigen::Vector3f& point, Cell& out) const
|
||||||
{
|
{
|
||||||
if (gridWidth <= 0 || gridDepth <= 0) {
|
if (gridWidth <= 0 || gridDepth <= 0) {
|
||||||
out = {};
|
out = {};
|
||||||
@@ -812,7 +815,7 @@ bool PathFinder::worldToCell(const Eigen::Vector3f& point, Cell& out) const
|
|||||||
return isInsideGrid(out);
|
return isInsideGrid(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
Eigen::Vector3f PathFinder::cellCenter(const Cell& cell) const
|
Eigen::Vector3f PathFinderOld::cellCenter(const Cell& cell) const
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
minX + (static_cast<float>(cell.x) + 0.5f) * cellSize,
|
minX + (static_cast<float>(cell.x) + 0.5f) * cellSize,
|
||||||
@@ -821,17 +824,17 @@ Eigen::Vector3f PathFinder::cellCenter(const Cell& cell) const
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::isInsideGrid(const Cell& cell) const
|
bool PathFinderOld::isInsideGrid(const Cell& cell) const
|
||||||
{
|
{
|
||||||
return cell.x >= 0 && cell.z >= 0 && cell.x < gridWidth && cell.z < gridDepth;
|
return cell.x >= 0 && cell.z >= 0 && cell.x < gridWidth && cell.z < gridDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::isCellWalkable(const Cell& cell) const
|
bool PathFinderOld::isCellWalkable(const Cell& cell) const
|
||||||
{
|
{
|
||||||
return isCellWalkable(cell, walkable);
|
return isCellWalkable(cell, walkable);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::isCellWalkable(const Cell& cell, const std::vector<unsigned char>& walkableGrid) const
|
bool PathFinderOld::isCellWalkable(const Cell& cell, const std::vector<unsigned char>& walkableGrid) const
|
||||||
{
|
{
|
||||||
const int idx = indexOf(cell);
|
const int idx = indexOf(cell);
|
||||||
return isInsideGrid(cell) &&
|
return isInsideGrid(cell) &&
|
||||||
@@ -840,17 +843,17 @@ bool PathFinder::isCellWalkable(const Cell& cell, const std::vector<unsigned cha
|
|||||||
walkableGrid[static_cast<size_t>(idx)] != 0;
|
walkableGrid[static_cast<size_t>(idx)] != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PathFinder::indexOf(const Cell& cell) const
|
int PathFinderOld::indexOf(const Cell& cell) const
|
||||||
{
|
{
|
||||||
return cell.z * gridWidth + cell.x;
|
return cell.z * gridWidth + cell.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out) const
|
bool PathFinderOld::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out) const
|
||||||
{
|
{
|
||||||
return findNearestWalkableCell(point, out, walkable);
|
return findNearestWalkableCell(point, out, walkable);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out, const std::vector<unsigned char>& walkableGrid) const
|
bool PathFinderOld::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out, const std::vector<unsigned char>& walkableGrid) const
|
||||||
{
|
{
|
||||||
Cell origin;
|
Cell origin;
|
||||||
if (!worldToCell(point, origin)) {
|
if (!worldToCell(point, origin)) {
|
||||||
@@ -885,12 +888,12 @@ bool PathFinder::findNearestWalkableCell(const Eigen::Vector3f& point, Cell& out
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::hasLineOfSight(const Cell& from, const Cell& to) const
|
bool PathFinderOld::hasLineOfSight(const Cell& from, const Cell& to) const
|
||||||
{
|
{
|
||||||
return hasLineOfSight(from, to, walkable);
|
return hasLineOfSight(from, to, walkable);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::hasLineOfSight(const Cell& from, const Cell& to, const std::vector<unsigned char>& walkableGrid) const
|
bool PathFinderOld::hasLineOfSight(const Cell& from, const Cell& to, const std::vector<unsigned char>& walkableGrid) const
|
||||||
{
|
{
|
||||||
const Eigen::Vector3f a = cellCenter(from);
|
const Eigen::Vector3f a = cellCenter(from);
|
||||||
const Eigen::Vector3f b = cellCenter(to);
|
const Eigen::Vector3f b = cellCenter(to);
|
||||||
@@ -909,12 +912,12 @@ bool PathFinder::hasLineOfSight(const Cell& from, const Cell& to, const std::vec
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PathFinder::Cell> PathFinder::smoothCells(const std::vector<Cell>& cells) const
|
std::vector<PathFinderOld::Cell> PathFinderOld::smoothCells(const std::vector<Cell>& cells) const
|
||||||
{
|
{
|
||||||
return smoothCells(cells, walkable);
|
return smoothCells(cells, walkable);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PathFinder::Cell> PathFinder::smoothCells(const std::vector<Cell>& cells, const std::vector<unsigned char>& walkableGrid) const
|
std::vector<PathFinderOld::Cell> PathFinderOld::smoothCells(const std::vector<Cell>& cells, const std::vector<unsigned char>& walkableGrid) const
|
||||||
{
|
{
|
||||||
if (cells.size() <= 2) {
|
if (cells.size() <= 2) {
|
||||||
return cells;
|
return cells;
|
||||||
@@ -937,7 +940,7 @@ std::vector<PathFinder::Cell> PathFinder::smoothCells(const std::vector<Cell>& c
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PathFinder::pointInPolygon(float x, float z, const std::vector<Eigen::Vector2f>& polygon)
|
bool PathFinderOld::pointInPolygon(float x, float z, const std::vector<Eigen::Vector2f>& polygon)
|
||||||
{
|
{
|
||||||
bool inside = false;
|
bool inside = false;
|
||||||
size_t previous = polygon.size() - 1;
|
size_t previous = polygon.size() - 1;
|
||||||
|
|||||||
+536
-4
@@ -1,15 +1,547 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Eigen/Core>
|
#include <Eigen/Core>
|
||||||
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef min
|
||||||
|
#undef min
|
||||||
|
#endif
|
||||||
|
#ifdef max
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
#include "../../thirdparty/fpm-1.1.0/include/fpm/fixed.hpp"
|
||||||
|
#include "../../thirdparty/fpm-1.1.0/include/fpm/math.hpp"
|
||||||
|
|
||||||
|
// Сторонние библиотеки для геометрии (Header-only / легко подключаемые):
|
||||||
|
// 1. Clipper2: https://github.com/PolyClipping/Clipper2/releases/tag/Clipper2_2.0.1
|
||||||
|
// 2. CDT (Constrained Delaunay Triangulation): https://github.com/artem-ogre/CDT
|
||||||
|
|
||||||
|
#include "../../thirdparty/Clipper2-Clipper2_2.0.1/CPP/build/Release/include/clipper2/clipper.h"
|
||||||
|
#include "../../thirdparty/CDT-2.0.2/CDT/build/release/include/CDT.h"
|
||||||
|
|
||||||
namespace FRG {
|
namespace FRG {
|
||||||
|
|
||||||
|
|
||||||
|
//using fp = fpm::fixed<std::int32_t, std::int64_t, 16>;
|
||||||
|
inline constexpr unsigned int FP_FRACTION_BITS = 12;
|
||||||
|
using fp = fpm::fixed<std::int32_t, std::int64_t, FP_FRACTION_BITS>;
|
||||||
|
|
||||||
|
struct Vec2fp {
|
||||||
|
fp x{ 0 };
|
||||||
|
fp y{ 0 };
|
||||||
|
|
||||||
|
Vec2fp() = default;
|
||||||
|
Vec2fp(fp _x, fp _y) : x(_x), y(_y) {}
|
||||||
|
|
||||||
|
// Конвертация из float только на этапе загрузки/ввода
|
||||||
|
static Vec2fp fromFloat(float fx, float fy) {
|
||||||
|
return Vec2fp(fp(fx), fp(fy));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2fp operator+(const Vec2fp& o) const { return { x + o.x, y + o.y }; }
|
||||||
|
Vec2fp operator-(const Vec2fp& o) const { return { x - o.x, y - o.y }; }
|
||||||
|
Vec2fp operator*(fp s) const { return { x * s, y * s }; }
|
||||||
|
Vec2fp operator/(fp s) const { return { x / s, y / s }; }
|
||||||
|
bool operator==(const Vec2fp& o) const { return x == o.x && y == o.y; }
|
||||||
|
bool operator!=(const Vec2fp& o) const { return !(*this == o); }
|
||||||
|
fp dot(const Vec2fp& o) const { return x * o.x + y * o.y; }
|
||||||
|
|
||||||
|
// 2D Векторное произведение (Z-компонента): определяет, с какой стороны лежит точка
|
||||||
|
fp cross(const Vec2fp& o) const { return x * o.y - y * o.x; }
|
||||||
|
|
||||||
|
fp lengthSq() const { return x * x + y * y; }
|
||||||
|
fp length() const { return fpm::sqrt(lengthSq()); }
|
||||||
|
|
||||||
|
Eigen::Vector2f toEigen() const {
|
||||||
|
return Eigen::Vector2f(static_cast<float>(x), static_cast<float>(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vec2fp fromEigen(const Eigen::Vector2f& v) {
|
||||||
|
return Vec2fp(fp(v.x()), fp(v.y()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vec3fp {
|
||||||
|
fp x{ 0 }, y{ 0 }, z{ 0 };
|
||||||
|
Vec3fp() = default;
|
||||||
|
Vec3fp(fp _x, fp _y, fp _z) : x(_x), y(_y), z(_z) {}
|
||||||
|
|
||||||
|
static Vec3fp fromEigen(const Eigen::Vector3f& v) {
|
||||||
|
return { fp(v.x()), fp(v.y()), fp(v.z()) };
|
||||||
|
}
|
||||||
|
Eigen::Vector3f toEigen() const {
|
||||||
|
return Eigen::Vector3f(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::vector<Eigen::Vector2f> toEigenVector(const std::vector<FRG::Vec2fp>& src) {
|
||||||
|
std::vector<Eigen::Vector2f> dst;
|
||||||
|
dst.reserve(src.size());
|
||||||
|
for (const auto& pt : src) {
|
||||||
|
dst.emplace_back(static_cast<float>(pt.x), static_cast<float>(pt.y));
|
||||||
|
}
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<FRG::Vec2fp> fromEigenVector(const std::vector<Eigen::Vector2f>& src) {
|
||||||
|
std::vector<FRG::Vec2fp> dst;
|
||||||
|
dst.reserve(src.size());
|
||||||
|
for (const auto& pt : src) {
|
||||||
|
dst.emplace_back(FRG::fp(pt.x()), FRG::fp(pt.y()));
|
||||||
|
}
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
class PathFinder {
|
class PathFinder {
|
||||||
|
public:
|
||||||
|
struct NavigationArea {
|
||||||
|
std::string name;
|
||||||
|
bool available = true;
|
||||||
|
std::vector<Vec2fp> polygon;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ObstaclePolygon {
|
||||||
|
std::string name;
|
||||||
|
std::vector<Vec2fp> polygon;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NavTriangle {
|
||||||
|
int id = -1;
|
||||||
|
Vec2fp vertices[3];
|
||||||
|
Vec2fp center;
|
||||||
|
int neighbors[3] = { -1, -1, -1 }; // Индексы соседних треугольников по ребрам (0-1, 1-2, 2-0)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Portal {
|
||||||
|
Vec2fp left;
|
||||||
|
Vec2fp right;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Масштаб для Clipper2 (так как Clipper работает с целыми int64_t,
|
||||||
|
// сохраняем точность в 10000 единиц на 1 метр)
|
||||||
|
static constexpr int64_t CLIPPER_SCALE = 10000;
|
||||||
|
|
||||||
|
void build(const std::string& configPath = "", const std::string& zipPath = "") {
|
||||||
|
// Здесь может быть парсинг configPath в area и obstaclePolygons.
|
||||||
|
// После заполнения геометрии строим NavMesh:
|
||||||
|
area.name = "main";
|
||||||
|
area.polygon = {
|
||||||
|
Vec2fp::fromFloat(-100.f, -100.f),
|
||||||
|
Vec2fp::fromFloat(-100.f, 100.f),
|
||||||
|
Vec2fp::fromFloat(100.f, 100.f),
|
||||||
|
Vec2fp::fromFloat(100.f, -100.f)
|
||||||
|
};
|
||||||
|
|
||||||
|
area.available = true;
|
||||||
|
|
||||||
|
obstaclePolygons.push_back({
|
||||||
|
"p1", {Vec2fp::fromFloat(-10.f, -10.f),
|
||||||
|
Vec2fp::fromFloat(-10.f, 10.f),
|
||||||
|
Vec2fp::fromFloat(10.f, 10.f),
|
||||||
|
Vec2fp::fromFloat(10.f, -10.f)}
|
||||||
|
});
|
||||||
|
|
||||||
|
//ready = true;
|
||||||
|
|
||||||
|
generateNavMesh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Построение NavMesh: Объединение полигонов (Clipper2) + Триангуляция (CDT)
|
||||||
|
void generateNavMesh() {
|
||||||
|
navMesh.clear();
|
||||||
|
if (area.polygon.size() < 3) return;
|
||||||
|
|
||||||
|
using namespace Clipper2Lib;
|
||||||
|
|
||||||
|
// Вспомогательная лямбда: безопасно переводит fp в координаты Clipper2 (int64_t)
|
||||||
|
auto toClipperCoord = [](fp val) -> int64_t {
|
||||||
|
return (static_cast<int64_t>(val.raw_value()) * CLIPPER_SCALE) / (1LL << FP_FRACTION_BITS);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Переводим контур локации (NavigationArea) в формат Clipper2
|
||||||
|
Paths64 subject;
|
||||||
|
Path64 outerPath;
|
||||||
|
for (const auto& pt : area.polygon) {
|
||||||
|
outerPath.push_back(Point64(toClipperCoord(pt.x), toClipperCoord(pt.y)));
|
||||||
|
}
|
||||||
|
subject.push_back(outerPath);
|
||||||
|
|
||||||
|
// 2. Собираем все препятствия
|
||||||
|
Paths64 clips;
|
||||||
|
for (const auto& obs : obstaclePolygons) {
|
||||||
|
if (obs.polygon.size() < 3) continue;
|
||||||
|
Path64 clipPath;
|
||||||
|
for (const auto& pt : obs.polygon) {
|
||||||
|
clipPath.push_back(Point64(toClipperCoord(pt.x), toClipperCoord(pt.y)));
|
||||||
|
}
|
||||||
|
clips.push_back(clipPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Булево объединение пересекающихся препятствий и вычитание их из локации
|
||||||
|
// Clipper автоматически объединяет пересекающиеся полигоны внутри Difference
|
||||||
|
PolyTree64 solTree;
|
||||||
|
Clipper64 clipper;
|
||||||
|
clipper.AddSubject(subject);
|
||||||
|
clipper.AddClip(clips);
|
||||||
|
//clipper.Execute(ClipType::Difference, FillRule::NonZero, solTree);
|
||||||
|
clipper.Execute(ClipType::Difference, FillRule::EvenOdd, solTree);
|
||||||
|
|
||||||
|
// 4. Подготовка данных для Constrained Delaunay Triangulation (CDT)
|
||||||
|
std::vector<CDT::V2d<double>> cdtVertices;
|
||||||
|
std::vector<CDT::Edge> cdtEdges;
|
||||||
|
|
||||||
|
auto extractPolygons = [&](const PolyPath64* node, auto& selfRef) -> void {
|
||||||
|
if (!node->Polygon().empty()) {
|
||||||
|
const Path64& path = node->Polygon();
|
||||||
|
std::size_t startIdx = cdtVertices.size();
|
||||||
|
std::size_t count = path.size();
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < count; ++i) {
|
||||||
|
double x = static_cast<double>(path[i].x) / CLIPPER_SCALE;
|
||||||
|
double y = static_cast<double>(path[i].y) / CLIPPER_SCALE;
|
||||||
|
cdtVertices.push_back({ x, y });
|
||||||
|
cdtEdges.push_back(CDT::Edge(startIdx + i, startIdx + (i + 1) % count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < node->Count(); ++i) {
|
||||||
|
selfRef(node->Child(i), selfRef);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < solTree.Count(); ++i) {
|
||||||
|
extractPolygons(solTree.Child(i), extractPolygons);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cdtVertices.empty()) return;
|
||||||
|
|
||||||
|
CDT::RemoveDuplicatesAndRemapEdges(cdtVertices, cdtEdges);
|
||||||
|
|
||||||
|
// 5. Выполняем триангуляцию и удаляем треугольники внутри дыр (препятствий)
|
||||||
|
CDT::Triangulation<double> cdt;
|
||||||
|
cdt.insertVertices(cdtVertices);
|
||||||
|
cdt.insertEdges(cdtEdges);
|
||||||
|
cdt.eraseOuterTrianglesAndHoles();
|
||||||
|
|
||||||
|
// 6. Конвертируем результат в детерминированный граф NavTriangle (Q32.32)
|
||||||
|
const auto& triangles = cdt.triangles;
|
||||||
|
const auto& vertices = cdt.vertices;
|
||||||
|
navMesh.resize(triangles.size());
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < triangles.size(); ++i) {
|
||||||
|
NavTriangle& navTri = navMesh[i];
|
||||||
|
navTri.id = static_cast<int>(i);
|
||||||
|
|
||||||
|
for (int j = 0; j < 3; ++j) {
|
||||||
|
const auto& v = vertices[triangles[i].vertices[j]];
|
||||||
|
navTri.vertices[j] = Vec2fp(fp(v.x), fp(v.y));
|
||||||
|
|
||||||
|
// Соседи в CDT хранятся напротив вершин, перепривязываем к ребрам (j, j+1)
|
||||||
|
auto neighborIdx = triangles[i].neighbors[j];
|
||||||
|
navTri.neighbors[j] = (neighborIdx != CDT::noNeighbor) ? static_cast<int>(neighborIdx) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
navTri.center = (navTri.vertices[0] + navTri.vertices[1] + navTri.vertices[2]) / fp(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
ready = !navMesh.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Основной метод поиска пути (плоскость XZ, Y — высота)
|
||||||
|
std::vector<Vec3fp> findPathToNearest(const Vec3fp& start3D, const Vec3fp& end3D) const {
|
||||||
|
if (!ready || !area.available) return { start3D };
|
||||||
|
|
||||||
|
Vec2fp startPt(start3D.x, start3D.z);
|
||||||
|
Vec2fp targetPt(end3D.x, end3D.z);
|
||||||
|
|
||||||
|
// 1. Находим треугольники для старта и финиша.
|
||||||
|
// Если точка попала в препятствие (вне NavMesh), ищем ближайшую точку на ребрах NavMesh
|
||||||
|
int startTri = findTriangleContaining(startPt);
|
||||||
|
if (startTri == -1) {
|
||||||
|
startPt = findClosestPointOnNavMesh(startPt, startTri);
|
||||||
|
}
|
||||||
|
|
||||||
|
int endTri = findTriangleContaining(targetPt);
|
||||||
|
if (endTri == -1) {
|
||||||
|
targetPt = findClosestPointOnNavMesh(targetPt, endTri);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startTri == -1 || endTri == -1) return { start3D };
|
||||||
|
|
||||||
|
// Если старт и конец в одном треугольнике — идем по прямой
|
||||||
|
if (startTri == endTri) {
|
||||||
|
return {
|
||||||
|
start3D,
|
||||||
|
Vec3fp(targetPt.x, floorY, targetPt.y)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Поиск коридора из треугольников через A*
|
||||||
|
std::vector<int> triangleCorridor = runAStar(startTri, endTri, startPt, targetPt);
|
||||||
|
if (triangleCorridor.empty()) return { start3D };
|
||||||
|
|
||||||
|
// 3. Сглаживание пути через Funnel Algorithm (Натянутая нить)
|
||||||
|
std::vector<Vec2fp> smoothPath2D = stringPullFunnel(startPt, targetPt, triangleCorridor);
|
||||||
|
|
||||||
|
// 4. Формируем выходной массив 3D точек
|
||||||
|
std::vector<Vec3fp> result;
|
||||||
|
result.reserve(smoothPath2D.size());
|
||||||
|
for (const auto& pt : smoothPath2D) {
|
||||||
|
result.emplace_back(pt.x, floorY, pt.y);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перегрузка для совместимости со старым Eigen-интерфейсом
|
||||||
|
std::vector<Eigen::Vector3f> findPathToNearest(const Eigen::Vector3f& start, const Eigen::Vector3f& end) const {
|
||||||
|
auto pathFp = findPathToNearest(Vec3fp::fromEigen(start), Vec3fp::fromEigen(end));
|
||||||
|
std::vector<Eigen::Vector3f> out;
|
||||||
|
out.reserve(pathFp.size());
|
||||||
|
for (const auto& p : pathFp) out.push_back(p.toEigen());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isWalkable(const Vec3fp& point) const {
|
||||||
|
return findTriangleContaining(Vec2fp(point.x, point.z)) != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isWalkable(const Eigen::Vector3f& point) const {
|
||||||
|
return isWalkable(Vec3fp::fromEigen(point));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setAreaAvailable(const std::string& areaName, bool available) {
|
||||||
|
if (area.name == areaName) {
|
||||||
|
area.available = available;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isReady() const { return ready; }
|
||||||
|
const NavigationArea& getArea() const { return area; }
|
||||||
|
void setArea(const NavigationArea& a) { area = a; }
|
||||||
|
|
||||||
|
fp getFloorY() const { return floorY; }
|
||||||
|
void setFloorY(fp y) { floorY = y; }
|
||||||
|
|
||||||
|
void addObstaclePolygon(const ObstaclePolygon& polygon) {
|
||||||
|
obstaclePolygons.push_back(polygon);
|
||||||
|
}
|
||||||
|
const std::vector<ObstaclePolygon>& getObstaclePolygons() const { return obstaclePolygons; }
|
||||||
|
|
||||||
|
bool saveConfig(const std::string& path) const { return true; }
|
||||||
|
bool saveGrid(const std::string& path) const { return true; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
NavigationArea area;
|
||||||
|
std::vector<ObstaclePolygon> obstaclePolygons;
|
||||||
|
std::vector<NavTriangle> navMesh;
|
||||||
|
fp floorY{ 0 };
|
||||||
|
bool ready = false;
|
||||||
|
|
||||||
|
// Проверка нахождения точки внутри треугольника (через знаки векторных произведений)
|
||||||
|
static bool isPointInTriangle(const Vec2fp& p, const Vec2fp& a, const Vec2fp& b, const Vec2fp& c) {
|
||||||
|
fp d1 = (b - a).cross(p - a);
|
||||||
|
fp d2 = (c - b).cross(p - b);
|
||||||
|
fp d3 = (a - c).cross(p - c);
|
||||||
|
|
||||||
|
bool hasNeg = (d1 < fp(0)) || (d2 < fp(0)) || (d3 < fp(0));
|
||||||
|
bool hasPos = (d1 > fp(0)) || (d2 > fp(0)) || (d3 > fp(0));
|
||||||
|
return !(hasNeg && hasPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
int findTriangleContaining(const Vec2fp& pt) const {
|
||||||
|
for (const auto& tri : navMesh) {
|
||||||
|
if (isPointInTriangle(pt, tri.vertices[0], tri.vertices[1], tri.vertices[2])) {
|
||||||
|
return tri.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проекция точки на отрезок AB
|
||||||
|
static Vec2fp closestPointOnSegment(const Vec2fp& p, const Vec2fp& a, const Vec2fp& b) {
|
||||||
|
Vec2fp ab = b - a;
|
||||||
|
fp lenSq = ab.lengthSq();
|
||||||
|
if (lenSq == fp(0)) return a;
|
||||||
|
|
||||||
|
fp t = (p - a).dot(ab) / lenSq;
|
||||||
|
t = std::max(fp(0), std::min(fp(1), t));
|
||||||
|
return a + ab * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2fp findClosestPointOnNavMesh(const Vec2fp& pt, int& outTriId) const {
|
||||||
|
fp minDistSq = std::numeric_limits<fp>::max();
|
||||||
|
Vec2fp bestPt = pt;
|
||||||
|
outTriId = -1;
|
||||||
|
|
||||||
|
for (const auto& tri : navMesh) {
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
// В CDT neighbors[i] — это сосед через ребро (vertices[i], vertices[(i + 1) % 3])
|
||||||
|
if (tri.neighbors[i] == -1) {
|
||||||
|
const Vec2fp& a = tri.vertices[i];
|
||||||
|
const Vec2fp& b = tri.vertices[(i + 1) % 3];
|
||||||
|
Vec2fp proj = closestPointOnSegment(pt, a, b);
|
||||||
|
fp distSq = (pt - proj).lengthSq();
|
||||||
|
|
||||||
|
if (distSq < minDistSq) {
|
||||||
|
minDistSq = distSq;
|
||||||
|
bestPt = proj;
|
||||||
|
outTriId = tri.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestPt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Поиск пути A* по центрам треугольников
|
||||||
|
std::vector<int> runAStar(int startTri, int endTri, const Vec2fp& startPt, const Vec2fp& endPt) const {
|
||||||
|
struct NodeRecord {
|
||||||
|
int triId;
|
||||||
|
fp fScore;
|
||||||
|
// В случае равенства fScore сортируем по triId для 100% детерминизма
|
||||||
|
bool operator>(const NodeRecord& o) const {
|
||||||
|
if (fScore != o.fScore) return fScore > o.fScore;
|
||||||
|
return triId > o.triId;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::priority_queue<NodeRecord, std::vector<NodeRecord>, std::greater<NodeRecord>> openSet;
|
||||||
|
std::vector<fp> gScore(navMesh.size(), std::numeric_limits<fp>::max());
|
||||||
|
std::vector<int> cameFrom(navMesh.size(), -1);
|
||||||
|
std::vector<bool> closed(navMesh.size(), false);
|
||||||
|
|
||||||
|
gScore[startTri] = fp(0);
|
||||||
|
openSet.push({ startTri, (navMesh[startTri].center - endPt).length() });
|
||||||
|
|
||||||
|
while (!openSet.empty()) {
|
||||||
|
int current = openSet.top().triId;
|
||||||
|
openSet.pop();
|
||||||
|
|
||||||
|
if (current == endTri) {
|
||||||
|
std::vector<int> path;
|
||||||
|
for (int curr = endTri; curr != -1; curr = cameFrom[curr]) {
|
||||||
|
path.push_back(curr);
|
||||||
|
}
|
||||||
|
std::reverse(path.begin(), path.end());
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closed[current]) continue;
|
||||||
|
closed[current] = true;
|
||||||
|
|
||||||
|
const NavTriangle& currTri = navMesh[current];
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
int neighbor = currTri.neighbors[i];
|
||||||
|
if (neighbor == -1 || closed[neighbor]) continue;
|
||||||
|
|
||||||
|
fp stepCost = (currTri.center - navMesh[neighbor].center).length();
|
||||||
|
fp tentativeG = gScore[current] + stepCost;
|
||||||
|
|
||||||
|
if (tentativeG < gScore[neighbor]) {
|
||||||
|
cameFrom[neighbor] = current;
|
||||||
|
gScore[neighbor] = tentativeG;
|
||||||
|
fp h = (navMesh[neighbor].center - endPt).length();
|
||||||
|
openSet.push({ neighbor, tentativeG + h });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Извлечение общих вершин (портала) между двумя соседними треугольниками
|
||||||
|
// Извлечение общих вершин (портала) между двумя соседними треугольниками
|
||||||
|
Portal getPortal(int fromTriId, int toTriId) const {
|
||||||
|
const NavTriangle& t1 = navMesh[fromTriId];
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
if (t1.neighbors[i] == toTriId) {
|
||||||
|
// При обходе треугольника CCW и движении изнутри наружу через ребро i -> (i + 1) % 3:
|
||||||
|
// вершина i находится СЛЕВА, а вершина (i + 1) % 3 — СПРАВА
|
||||||
|
return Portal{ t1.vertices[i], t1.vertices[(i + 1) % 3] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Portal{ t1.center, t1.center };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Алгоритм натянутой нити (Simple Stupid Funnel Algorithm)
|
||||||
|
std::vector<Vec2fp> stringPullFunnel(const Vec2fp& startPt, const Vec2fp& endPt, const std::vector<int>& corridor) const {
|
||||||
|
std::vector<Portal> portals;
|
||||||
|
portals.reserve(corridor.size() + 2);
|
||||||
|
portals.push_back({ startPt, startPt });
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i + 1 < corridor.size(); ++i) {
|
||||||
|
portals.push_back(getPortal(corridor[i], corridor[i + 1]));
|
||||||
|
}
|
||||||
|
portals.push_back({ endPt, endPt });
|
||||||
|
|
||||||
|
std::vector<Vec2fp> pts;
|
||||||
|
pts.push_back(startPt);
|
||||||
|
|
||||||
|
Vec2fp portalApex = startPt;
|
||||||
|
Vec2fp portalLeft = startPt;
|
||||||
|
Vec2fp portalRight = startPt;
|
||||||
|
int apexIndex = 0, leftIndex = 0, rightIndex = 0;
|
||||||
|
|
||||||
|
for (int i = 1; i < static_cast<int>(portals.size()); ++i) {
|
||||||
|
const Vec2fp& left = portals[i].left;
|
||||||
|
const Vec2fp& right = portals[i].right;
|
||||||
|
|
||||||
|
// Обновляем правую границу воронки
|
||||||
|
if ((portalRight - portalApex).cross(right - portalApex) <= fp(0)) {
|
||||||
|
if (portalApex == portalRight || (portalLeft - portalApex).cross(right - portalApex) > fp(0)) {
|
||||||
|
portalRight = right;
|
||||||
|
rightIndex = i;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pts.push_back(portalLeft);
|
||||||
|
portalApex = portalLeft;
|
||||||
|
apexIndex = leftIndex;
|
||||||
|
portalLeft = portalApex;
|
||||||
|
portalRight = portalApex;
|
||||||
|
leftIndex = apexIndex;
|
||||||
|
rightIndex = apexIndex;
|
||||||
|
i = apexIndex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем левую границу воронки
|
||||||
|
if ((portalLeft - portalApex).cross(left - portalApex) >= fp(0)) {
|
||||||
|
if (portalApex == portalLeft || (portalRight - portalApex).cross(left - portalApex) < fp(0)) {
|
||||||
|
portalLeft = left;
|
||||||
|
leftIndex = i;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pts.push_back(portalRight);
|
||||||
|
portalApex = portalRight;
|
||||||
|
apexIndex = rightIndex;
|
||||||
|
portalLeft = portalApex;
|
||||||
|
portalRight = portalApex;
|
||||||
|
leftIndex = apexIndex;
|
||||||
|
rightIndex = apexIndex;
|
||||||
|
i = apexIndex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pts.back() != endPt) {
|
||||||
|
pts.push_back(endPt);
|
||||||
|
}
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class PathFinderOld {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
struct ObstaclePolygon {
|
struct ObstaclePolygon {
|
||||||
@@ -39,18 +571,18 @@ public:
|
|||||||
std::vector<Eigen::Vector3f> findPath(const Eigen::Vector3f& start,
|
std::vector<Eigen::Vector3f> findPath(const Eigen::Vector3f& start,
|
||||||
const Eigen::Vector3f& end) const;
|
const Eigen::Vector3f& end) const;
|
||||||
|
|
||||||
std::vector<Eigen::Vector3f> findPath(const Eigen::Vector3f& start,
|
/*std::vector<Eigen::Vector3f> findPath(const Eigen::Vector3f& start,
|
||||||
const Eigen::Vector3f& end,
|
const Eigen::Vector3f& end,
|
||||||
const std::vector<DynamicObstacle>& dynamicObstacles) const;
|
const std::vector<DynamicObstacle>& dynamicObstacles) const;
|
||||||
|
*/
|
||||||
// Like findPath, but when the destination is unreachable the character
|
// Like findPath, but when the destination is unreachable the character
|
||||||
// walks as close as possible instead of not moving at all.
|
// walks as close as possible instead of not moving at all.
|
||||||
std::vector<Eigen::Vector3f> findPathToNearest(const Eigen::Vector3f& start,
|
std::vector<Eigen::Vector3f> findPathToNearest(const Eigen::Vector3f& start,
|
||||||
const Eigen::Vector3f& end) const;
|
const Eigen::Vector3f& end) const;
|
||||||
std::vector<Eigen::Vector3f> findPathToNearest(const Eigen::Vector3f& start,
|
/*std::vector<Eigen::Vector3f> findPathToNearest(const Eigen::Vector3f& start,
|
||||||
const Eigen::Vector3f& end,
|
const Eigen::Vector3f& end,
|
||||||
const std::vector<DynamicObstacle>& dynamicObstacles) const;
|
const std::vector<DynamicObstacle>& dynamicObstacles) const;
|
||||||
|
*/
|
||||||
bool setAreaAvailable(const std::string& areaName, bool available);
|
bool setAreaAvailable(const std::string& areaName, bool available);
|
||||||
bool isReady() const { return ready; }
|
bool isReady() const { return ready; }
|
||||||
bool isWalkable(const Eigen::Vector3f& point) const;
|
bool isWalkable(const Eigen::Vector3f& point) const;
|
||||||
|
|||||||
Reference in new issue
Block a user