Files
2025-09-18 17:55:52 -04:00

1081 lines
35 KiB
C++

#include "stdafx.h"
#include "V8DataModel/DataStoreService.h"
#include "V8DataModel/DataModel.h"
#include "V8DataModel/Workspace.h"
#include "V8DataModel/MegaCluster.h"
#include "V8DataModel/PartCookie.h"
#include "v8datamodel/PathfindingService.h"
#include "Util/RobloxGoogleAnalytics.h"
#include "Network/Players.h"
#include "v8world/World.h"
#include "v8world/ContactManagerSpatialHash.h"
#include "Voxel/Grid.Chunk.h"
#include "voxel2/Grid.h"
#include "reflection/EnumConverter.h"
#include "rbx/Profiler.h"
LOGVARIABLE(PathfindingDetail, 0);
LOGVARIABLE(PathfindingPerf, 0);
DYNAMIC_FASTINTVARIABLE(PathfindingMaxDistance, 512);
DYNAMIC_FASTINTVARIABLE(PathfindingJobRunsPerSecond, 10);
DYNAMIC_FASTINTVARIABLE(PathfindingChunksPerInvokation, 10);
DYNAMIC_FASTINTVARIABLE(PathfindingAgeToCollectChunks, 1000);
DYNAMIC_FASTINTVARIABLE(PathfindingCollectPeriod, 100);
DYNAMIC_FASTINTVARIABLE(PathfindingDefaultBucketNum, 2048);
DYNAMIC_FASTINTVARIABLE(PathfindingVerticalChunkClamp, 1);
DYNAMIC_FASTINTVARIABLE(PathfindingSmoothIterations, 5);
DYNAMIC_FASTINTVARIABLE(PathfindingAverageWindow, 7);
RBX_REGISTER_ENUM(Path::PathStatus);
namespace RBX {
const char* const sPathfindingService = "PathfindingService";
const float PATHFINDING_FALL_COST = 1.0f;
const float PATHFINDING_WALK_COST = 1.0f;
const float PATHFINDING_WALK_DIAGLONAL_COST = sqrt(2);
float kMaxWorldCoordinate = (float)((std::numeric_limits<G3D::int16>::max()-2*kVoxelChunkSizeXZ)*Voxel::kCELL_SIZE);
REFLECTION_BEGIN();
static Reflection::PropDescriptor<PathfindingService, float> prop_EmptyCutoff
("EmptyCutoff", category_Data, &PathfindingService::getEmptyCutoff, &PathfindingService::setEmptyCutoff);
static Reflection::BoundYieldFuncDesc<PathfindingService, shared_ptr<Instance>(Vector3, Vector3, float)>
func_computeRawPathAsync(&PathfindingService::computeRawPathAsync, "ComputeRawPathAsync", "start", "finish", "maxDistance", Security::None);
static Reflection::BoundYieldFuncDesc<PathfindingService, shared_ptr<Instance>(Vector3, Vector3, float)>
func_computeSmoothPathAsync(&PathfindingService::computeSmoothPathAsync, "ComputeSmoothPathAsync", "start", "finish", "maxDistance", Security::None);
static Reflection::BoundYieldFuncDesc<Path, int(int)>
func_checkOcclusionAsync(&Path::checkOcclusionAsync, "CheckOcclusionAsync", "start", Security::None);
static Reflection::BoundFuncDesc<Path, shared_ptr<const Reflection::ValueArray>(void) > prop_Points
(&Path::getPointCoordinates, "GetPointCoordinates", Security::None);
REFLECTION_END();
namespace Reflection
{
template<> Reflection::EnumDesc<Path::PathStatus>::EnumDesc()
:RBX::Reflection::EnumDescriptor("PathStatus")
{
addPair(Path::Success, "Success");
addPair(Path::ClosestNoPath, "ClosestNoPath");
addPair(Path::ClosestOutOfRange, "ClosestOutOfRange");
addPair(Path::FailStartNotEmpty, "FailStartNotEmpty");
addPair(Path::FailFinishNotEmpty, "FailFinishNotEmpty");
}
}
REFLECTION_BEGIN();
static Reflection::EnumPropDescriptor<Path, Path::PathStatus> prop_PathStatus
("Status", category_Data, &Path::getStatus, NULL);
REFLECTION_END();
class PathfindingJob : public DataModelJob
{
public:
shared_ptr<PathfindingService> pathfindingService;
double desiredHz;
PathfindingJob(PathfindingService* owner)
: DataModelJob("PathfindingJob", DataModelJob::Write, false, shared_from(DataModel::get(owner)), Time::Interval(0.01)),
pathfindingService(shared_from(owner))
{
desiredHz = DFInt::PathfindingJobRunsPerSecond;
cyclicExecutive = true;
}
/*override*/ Time::Interval sleepTime(const Stats& stats)
{
return computeStandardSleepTime(stats, desiredHz);
}
/*override*/ Job::Error error(const Stats& stats)
{
return computeStandardErrorCyclicExecutiveSleeping(stats, desiredHz);
}
/*override*/ TaskScheduler::StepResult stepDataModelJob(const Stats& stats)
{
pathfindingService->executeThrottledRequests();
return TaskScheduler::Stepped;
}
};
Vector3int16 getDiagonalOffsetByIndex(int dir)
{
switch (dir)
{
case 0: return Vector3int16(1,0,-1);
case 1: return Vector3int16(-1,0,-1);
case 2: return Vector3int16(-1,0,1);
case 3: return Vector3int16(1,0,1);
default:
RBXASSERT(false);
return Vector3int16(0,0,0);
}
}
Vector3int16 getOffsetByIndex(int dir)
{
switch (dir)
{
case 0: return Vector3int16(1,0,0);
case 1: return Vector3int16(0,0,-1);
case 2: return Vector3int16(-1,0,0);
case 3: return Vector3int16(0,0,1);
default:
RBXASSERT(false);
return Vector3int16(0,0,0);
}
}
PathfindingService::PathfindingService()
: lastChunkId(SpatialRegion::Id(std::numeric_limits<G3D::int16>::max(), std::numeric_limits<G3D::int16>::max(), std::numeric_limits<G3D::int16>::max())),
lastChunk(NULL),
currentFrameNum(0),
voxelizer(true /* collisionTransparency */)
{
emptyCutoff = 40/255.0f;
}
void PathfindingService::setEmptyCutoff(float value)
{
if(value < 0.0f || value > 1.0f)
{
throw std::runtime_error("Value is outside expected range");
}
emptyCutoff = value;
}
inline float diffDistance(const Vector3int16& start, const Vector3int16& finish)
{
Vector3int16 delta = finish - start;
return sqrt(delta.x * delta.x + delta.y * delta.y + delta.z * delta.z);
}
void PathfindingService::PathfindingState::addOpenNode(const Node& node)
{
FASTLOG3(FLog::PathfindingDetail, "Adding open node: %i %i %i", node.pos.x, node.pos.y, node.pos.z);
NodesByPos::iterator it = nodesByPos.find(node.pos);
if(it != nodesByPos.end())
{
FASTLOG1F(FLog::PathfindingDetail, "Open node already exist with cost %f", it->second.totalPath);
if(node.totalPath < it->second.totalPath)
{
FASTLOG1F(FLog::PathfindingDetail, "New node is better, opening node with distance %f", it->second.itWeight->first);
if(!it->second.closed)
{
openNodesByWeight.erase(it->second.itWeight);
it->second.itWeight = openNodesByWeight.end();
}
it->second.closed = false;
it->second.parent = node.parent;
it->second.totalPath = node.totalPath;
}
else
{
return;
}
}
else
{
std::pair<NodesByPos::iterator, bool> result = nodesByPos.insert(NodesByPos::value_type(node.pos, node));
RBXASSERT(result.second);
it = result.first;
}
float rawDistance = diffDistance(finish, node.pos);
float distance = rawDistance + node.totalPath;
FASTLOG3(FLog::PathfindingDetail, "Creating open node: %i %i %i", node.pos.x, node.pos.y, node.pos.z);
FASTLOG2F(FLog::PathfindingDetail, "Distance: %f, raw distance: %f", distance, rawDistance);
FASTLOG3(FLog::PathfindingDetail, "New open node parent: %i %i %i", node.parent.x, node.parent.y, node.parent.z);
OpenNodesByWeight::iterator itWeight = openNodesByWeight.insert(std::make_pair(distance, it));
it->second.itWeight = itWeight;
RBXASSERT_SLOW(checkOpenNodesCounts());
if(rawDistance < closestDistance)
{
FASTLOG1F(FLog::PathfindingDetail, "Distance is less than previous closest distance %f", closestDistance);
closestDistance = rawDistance;
closestCell = node.pos;
closestCost = node.totalPath;
}
}
Vector3 getPoint(const std::vector<Vector3>& points, int index, int size)
{
if (index < 0) {
RBXASSERT((-index) < size);
// Inverse around point zero, so result = point_0 + (point_0 - point_i) = 2*point_0 - point_i
return 2*points[0] - points[-index];
} else if (index >= size)
{
RBXASSERT((2*(size-1) - index) >= 0);
// Inverse around last point (size-1), index is inversed the same way
return 2*points[size-1] - points[2*(size-1) - index];
}
return points[index];
}
void PathfindingService::smoothPath(std::vector<Vector3>& points, unsigned char emptyCutoff, int smoothWindow)
{
int size = points.size();
if(smoothWindow >= size)
{
// Find largest odd number less or equal to size
smoothWindow = (size - 1) | 1;
}
if(smoothWindow == 1)
return;
float aveCoeff = 1.0f / smoothWindow;
int halfAve = smoothWindow / 2;
Vector3 ave = Vector3(0,0,0);
for(int i = -halfAve; i <= halfAve; i++)
{
ave += getPoint(points, i, size) * aveCoeff;
}
std::vector<Vector3> newPoints(points.size());
newPoints[0] = points[0];
newPoints[size-1] = points[size-1];
for(int i = 1; i < size-1; i++)
{
newPoints[i] = points[i];
ave += getPoint(points, i+halfAve, size) * aveCoeff;
ave -= getPoint(points, i-halfAve-1, size) * aveCoeff;
ave.y = points[i].y;
FASTLOG1(FLog::PathfindingDetail, "Processing point %i", i);
FASTLOG3F(FLog::PathfindingDetail, "Average: %f %f %f", ave.x, ave.y, ave.z);
Vector3int16 floor = Vector3int16(fastFloorInt(ave.x-0.5f), ave.y, fastFloorInt(ave.z-0.5f));
Vector3int16 ceil = Vector3int16(fastCeilInt(ave.x-0.5f), ave.y, fastCeilInt(ave.z-0.5f));
FASTLOG4(FLog::PathfindingDetail, "Floor: %i %i, ceil: %i %i", floor.x, floor.z, ceil.x, ceil.z);
if(floor == ceil)
{
if(isSolid(floor, emptyCutoff) != EMPTY)
{
FASTLOG3(FLog::PathfindingDetail, "Floor is same as ceil, but %i %i %i is not empty", floor.x, floor.y, floor.z);
continue;
}
}
else
{
if(isSolid(Vector3int16(ceil.x, ceil.y, ceil.z), emptyCutoff) != EMPTY)
{
FASTLOG3(FLog::PathfindingDetail, "Skipping, %i %i %i is not empty", ceil.x, ceil.y, ceil.z);
continue;
}
if(isSolid(Vector3int16(floor.x, ceil.y, ceil.z), emptyCutoff) != EMPTY)
{
FASTLOG3(FLog::PathfindingDetail, "Skipping, %i %i %i is not empty", floor.x, ceil.y, ceil.z);
continue;
}
if(isSolid(Vector3int16(floor.x, ceil.y, floor.z), emptyCutoff) != EMPTY)
{
FASTLOG3(FLog::PathfindingDetail, "Skipping, %i %i %i is not empty", floor.x, ceil.y, floor.z);
continue;
}
if(isSolid(Vector3int16(ceil.x, ceil.y, floor.z), emptyCutoff) != EMPTY)
{
FASTLOG3(FLog::PathfindingDetail, "Skipping, %i %i %i is not empty", ceil.x, ceil.y, floor.z);
continue;
}
}
Vector3int16 cellBelow = Vector3int16(fastFloorInt(ave.x), fastFloorInt(ave.y-1), fastFloorInt(ave.z));
if(isSolid(cellBelow, emptyCutoff) != SOLID)
{
FASTLOG3(FLog::PathfindingDetail, "Skipping, nottom %i %i %i is not solid", cellBelow.x, cellBelow.y, cellBelow.z);
continue;
}
newPoints[i] = ave;
}
swap(points, newPoints);
}
bool PathfindingService::PathfindingState::checkOpenNodesCounts()
{
int countOpen = 0;
for(NodesByPos::iterator it = nodesByPos.begin(); it != nodesByPos.end(); ++it)
{
if(!it->second.closed)
countOpen++;
}
return countOpen == openNodesByWeight.size();
}
PathfindingService::Node PathfindingService::PathfindingState::popTopOpenNode()
{
RBXASSERT(openNodesByWeight.size() != 0);
OpenNodesByWeight::iterator top = openNodesByWeight.begin();
Node* result = &(top->second->second);
RBXASSERT(result->closed == false);
FASTLOG1F(FLog::PathfindingDetail, "---- Popping top open node, weight: %f", top->first);
FASTLOG3(FLog::PathfindingDetail, "Popping top open node, coordinate: %i %i %i", result->pos.x, result->pos.y, result->pos.z);
openNodesByWeight.erase(top);
result->itWeight = openNodesByWeight.end();
result->closed = true;
RBXASSERT_SLOW(checkOpenNodesCounts());
return *result;
}
bool PathfindingService::adjustCellSideways(Vector3int16& cellVoxel, Vector3& cellWorld, const Vector3int16& start, unsigned char emptyCutoff)
{
int bestDotResult = 0;
int bestDir = -1;
Vector3int16 direction = start - cellVoxel;
FASTLOG3(FLog::PathfindingDetail, "Adjusting cell sideways, cell: %i %i %i", cellVoxel.x, cellVoxel.y, cellVoxel.z);
FASTLOG3(FLog::PathfindingDetail, "Direction to start: %i %i %i", direction.x, direction.y, direction.z);
for(int dir = 0; dir < 4; dir++)
{
Vector3int16 offset = getOffsetByIndex(dir);
Vector3int16 side = cellVoxel + offset;
if(isSolid(side, emptyCutoff) == EMPTY)
{
FASTLOG4(FLog::PathfindingDetail, "Empty for dir %i, side cell: %i %i %i", dir, side.x, side.y, side.z);
int dotResult = direction.dot(offset);
if(bestDir == -1 || dotResult > bestDotResult)
{
FASTLOG1(FLog::PathfindingDetail, "Dot is better than old result: %i, storing as best", dotResult);
bestDir = dir;
bestDotResult = dotResult;
}
}
}
if(bestDir == -1)
return false;
cellVoxel = cellVoxel + getOffsetByIndex(bestDir);
cellWorld = cellWorld + Vector3(getOffsetByIndex(bestDir))*Voxel::kCELL_SIZE;
FASTLOG3(FLog::PathfindingDetail, "Final cell: %i %i %i", cellVoxel.x, cellVoxel.y, cellVoxel.z);
return true;
}
bool PathfindingService::adjustCell(Vector3int16& cellVoxel, Vector3& cellWorld, const Vector3int16& start, unsigned char emptyCutoff)
{
if(isSolid(cellVoxel, emptyCutoff) == EMPTY)
return true;
FASTLOG(FLog::PathfindingDetail, "Trying sides");
if(adjustCellSideways(cellVoxel, cellWorld, start, emptyCutoff))
return true;
FASTLOG(FLog::PathfindingDetail, "Trying up");
Vector3int16 cellVoxelUp = cellVoxel + Vector3int16(0,1,0);
Vector3 cellWorldUp = cellWorld + Vector3(0,Voxel::kCELL_SIZE,0);
if(isSolid(cellVoxelUp, emptyCutoff) == EMPTY)
{
cellVoxel = cellVoxelUp;
cellWorld = cellWorldUp;
return true;
}
FASTLOG(FLog::PathfindingDetail, "Trying up & sides");
if(adjustCellSideways(cellVoxelUp, cellWorldUp, start, emptyCutoff))
{
cellVoxel = cellVoxelUp;
cellWorld = cellWorldUp;
return true;
}
return false;
}
void PathfindingService::collectOldChunks()
{
FASTLOG(FLog::PathfindingPerf, "Starting collecting old chunks");
std::vector<SpatialRegion::Id> chunkIds = chunkMap.getChunks();
FASTLOG1(FLog::PathfindingDetail, "Collecting chunks, size: %u", chunkIds.size());
for(std::vector<SpatialRegion::Id>::iterator it = chunkIds.begin(); it != chunkIds.end(); ++it)
{
Voxel::OccupancyChunk* chunk = chunkMap.find(*it);
if((chunk->age + DFInt::PathfindingAgeToCollectChunks) < currentFrameNum)
{
if(*it == lastChunkId)
{
lastChunkId = SpatialRegion::Id(std::numeric_limits<G3D::int16>::max(), std::numeric_limits<G3D::int16>::max(), std::numeric_limits<G3D::int16>::max());
lastChunk = NULL;
}
FASTLOG3(FLog::PathfindingDetail, "Erasing chunk: %i %i %i", it->value().x, it->value().y, it->value().z);
chunkMap.erase(*it);
}
}
FASTLOG(FLog::PathfindingPerf, "Finish collecting chunks");
}
int manhattanDistance(const Vector3int16& start, const Vector3int16& finish)
{
Vector3int16 diff = finish - start;
return abs(diff.x) + abs(diff.y) + abs(diff.z);
}
int PathfindingService::countOrComputeDirtyChunks(const Vector3int16& startCell, const Vector3int16& finishCell, float maxDistance, bool compute)
{
RBXPROFILER_SCOPE("Voxel", "countOrComputeDirtyChunks");
int minPathDistance = manhattanDistance(startCell, finishCell);
int offset = std::max(0,(int)ceil((maxDistance - minPathDistance*Voxel::kCELL_SIZE) / (2*Voxel::kCELL_SIZE * Voxel::kXZ_CHUNK_SIZE)));
int offsetY = std::max(offset, DFInt::PathfindingVerticalChunkClamp);
DataModel* dataModel = DataModel::get(this);
ContactManager* contactManager = dataModel->getWorkspace()->getWorld()->getContactManager();
MegaClusterInstance* terrain = boost::polymorphic_downcast<MegaClusterInstance*>(dataModel->getWorkspace()->getTerrain());
Vector3int16 startId = SpatialRegion::regionContainingVoxel(startCell).value();
Vector3int16 finishId = SpatialRegion::regionContainingVoxel(finishCell).value();
Vector3int16 minId = startId.min(finishId) + Vector3int16(-offset,-offsetY,-offset);
Vector3int16 maxId = startId.max(finishId) + Vector3int16(offset,offsetY,offset);
FASTLOG3(FLog::PathfindingDetail, "Min voxel chunk: %i %i %i", minId.x, minId.y, minId.z);
FASTLOG3(FLog::PathfindingDetail, "Max voxel chunk: %i %i %i", maxId.x, maxId.y, maxId.z);
int count = 0;
// Voxelize
for(int x = minId.x; x <= maxId.x; x++)
for(int y = minId.y; y <= maxId.y; y++)
for(int z = minId.z; z <= maxId.z; z++)
{
FASTLOG3(FLog::PathfindingDetail, "Voxelizing: %i %i %i", x, y, z);
Voxel::OccupancyChunk* chunk = chunkMap.find(SpatialRegion::Id(x,y,z));
if(!chunk)
{
count++;
if(compute)
{
Voxel::OccupancyChunk& newChunk = chunkMap.insert(SpatialRegion::Id(x,y,z));
newChunk.index = Vector3int32(x,y,z);
newChunk.dirty = false;
newChunk.age = currentFrameNum;
voxelizer.occupancyUpdateChunk(newChunk, terrain, contactManager);
}
}
else if(chunk->dirty)
{
count++;
if(compute)
{
chunk->dirty = false;
chunk->age = currentFrameNum;
voxelizer.occupancyUpdateChunk(*chunk, terrain, contactManager);
}
}
}
FASTLOG1(FLog::PathfindingPerf, "Found %u dirty chunks", count);
return count;
}
PathfindingService::PathfindingState::PathfindingState() : nodesByPos(DFInt::PathfindingDefaultBucketNum)
{
}
void PathfindingService::computePath(CreatePathRequest& request)
{
RBXPROFILER_SCOPE("Voxel", "computePath");
Vector3int16 startCell = request.startCell;
Vector3int16 finishCell = request.finishCell;
unsigned char emptyCutoff = request.emptyCutoff;
int maxDistance = request.maxDistance;
if(!adjustCell(finishCell, request.start, startCell, emptyCutoff)) {
shared_ptr<Path> path = Creatable<Instance>::create<Path>(weak_from(this), Path::FailFinishNotEmpty);
request.resumeFunction(path);
return;
}
if(!adjustCell(startCell, request.finish, finishCell, emptyCutoff)) {
shared_ptr<Path> path = Creatable<Instance>::create<Path>(weak_from(this), Path::FailStartNotEmpty);
request.resumeFunction(path);
return;
}
PathfindingState state;
state.start = startCell;
state.finish = finishCell;
state.maxDistance = ((int)ceil(maxDistance/Voxel::kCELL_SIZE)) * PATHFINDING_WALK_COST;
state.closestCell = startCell;
state.closestDistance = INT_MAX;
Vector3int16 parentCell;
state.addOpenNode(Node(startCell, startCell, 0));
Path::PathStatus status = Path::ClosestNoPath;
while(state.openNodesByWeight.size() > 0)
{
Node node = state.popTopOpenNode();
if(node.pos == finishCell)
{
// Success!!
parentCell = node.parent;
FASTLOG3(FLog::PathfindingDetail, "Found a path from node %i %i %i", parentCell.x, parentCell.y, parentCell.z);
FASTLOG1F(FLog::PathfindingDetail, "Total path: %f", node.totalPath);
status = Path::Success;
break;
}
if(node.totalPath > state.maxDistance)
{
FASTLOG3(FLog::PathfindingDetail, "Found an outside node %i %i %i", node.pos.x, node.pos.y, node.pos.z);
// There's open space outside the range, take closest item
parentCell = state.closestCell;
FASTLOG3(FLog::PathfindingDetail, "Returning closest node %i %i %i", parentCell.x, parentCell.y, parentCell.z);
status = Path::ClosestOutOfRange;
break;
}
SolidState solidUnder = isSolid(node.pos + Vector3int16(0,-1,0), emptyCutoff);
FASTLOG1(FLog::PathfindingDetail, "Solid under: %i", solidUnder);
// If underneath is empty, the only thing to do is fall
if(solidUnder == EMPTY)
{
Vector3int16 newNodePos = node.pos + Vector3int16(0,-1,0);
state.addOpenNode(Node(newNodePos, node.pos, node.totalPath + PATHFINDING_FALL_COST));
}
else if(solidUnder == SOLID)
{
SideState sideState[4] = { BLOCKED, BLOCKED, BLOCKED, BLOCKED };
for(unsigned dir = 0; dir < 4; dir++)
{
Vector3int16 side = node.pos + getOffsetByIndex(dir);
Vector3int16 sideAbove = side + Vector3int16(0,1,0);
FASTLOG4(FLog::PathfindingDetail, "Dir: %u, cell to check: %i %i %i", dir, side.x, side.y, side.z);
SolidState solidAtSameLevel = isSolid(side, emptyCutoff);
SolidState solidAbove = isSolid(sideAbove, emptyCutoff);
FASTLOG2(FLog::PathfindingDetail, "Solid at same level: %i, solid above: %i", solidUnder, solidAbove);
if(solidAtSameLevel == EMPTY)
{
state.addOpenNode(Node(side, node.pos, node.totalPath + PATHFINDING_WALK_COST));
sideState[dir] = NOTBLOCKED;
}
else if(solidAtSameLevel == SOLID && solidAbove == EMPTY)
{
state.addOpenNode(Node(sideAbove, node.pos, node.totalPath + PATHFINDING_WALK_COST));
sideState[dir] = NOTBLOCKEDABOVE;
}
}
// Check diagonal states
for(unsigned dir = 0; dir < 4; dir++)
{
if(sideState[dir] != BLOCKED && sideState[dir] == sideState[(dir+1) % 4] )
{
Vector3int16 diagSide = node.pos + getDiagonalOffsetByIndex(dir);
Vector3int16 diagSideAbove = diagSide + Vector3int16(0,1,0);
FASTLOG4(FLog::PathfindingDetail, "Diag dir: %u, cell to check: %i %i %i", dir, diagSide.x, diagSide.y, diagSide.z);
SolidState solidAtSameLevel = isSolid(diagSide, emptyCutoff);
SolidState solidAbove = isSolid(diagSideAbove, emptyCutoff);
FASTLOG2(FLog::PathfindingDetail, "Solid at same level: %i, solid above: %i", solidUnder, solidAbove);
if(solidAtSameLevel == EMPTY && sideState[dir] == NOTBLOCKED)
{
state.addOpenNode(Node(diagSide, node.pos, node.totalPath + PATHFINDING_WALK_DIAGLONAL_COST));
}
else if(solidAtSameLevel == SOLID && solidAbove == EMPTY && sideState[dir] == NOTBLOCKEDABOVE)
{
state.addOpenNode(Node(diagSideAbove, node.pos, node.totalPath + PATHFINDING_WALK_DIAGLONAL_COST));
}
}
}
}
FASTLOG3(FLog::PathfindingDetail, "Adding closed node at %i %i %i", node.pos.x, node.pos.y, node.pos.z);
RBXASSERT(node.closed);
}
std::vector<Vector3> pathPoints;
shared_ptr<Path> path = Creatable<Instance>::create<Path>(weak_from(this), status);
if(status != Path::Success)
{
FASTLOG3(FLog::PathfindingDetail, "After checking everything, returning closest node %i %i %i", parentCell.x, parentCell.y, parentCell.z);
parentCell = state.closestCell;
}
else
{
pathPoints.push_back(request.isSmooth ? Voxel::worldSpaceToCellSpace(request.finish) : Vector3(request.finishCell)+Vector3(0.5f, 0.5f,0.5f));
}
while(parentCell != startCell)
{
pathPoints.push_back(Vector3(parentCell)+Vector3(0.5f,0.5f,0.5f));
NodesByPos::iterator itOpen = state.nodesByPos.find(parentCell);
RBXASSERT(itOpen != state.nodesByPos.end());
parentCell = itOpen->second.parent;
FASTLOG3(FLog::PathfindingDetail, "Steping back to %i %i %i", parentCell.x, parentCell.y, parentCell.z);
}
pathPoints.push_back(request.isSmooth ? Voxel::worldSpaceToCellSpace(request.start) : Vector3(request.startCell)+Vector3(0.5f, 0.5f, 0.5f));
std::reverse(pathPoints.begin(), pathPoints.end());
if(request.isSmooth)
{
for(int i = 0; i < DFInt::PathfindingSmoothIterations; i++)
smoothPath(pathPoints, emptyCutoff, DFInt::PathfindingAverageWindow);
}
for(std::vector<Vector3>::iterator it = pathPoints.begin(); it != pathPoints.end(); ++it)
{
path->addPoint(*it);
}
FASTLOG1(FLog::PathfindingPerf, "Result: ", state.openNodesByWeight.size() > 0);
request.resumeFunction(path);
}
void PathfindingService::computeRawPathAsync(Vector3 start, Vector3 finish, float maxDistance, boost::function<void(shared_ptr<Instance>) > resumeFunction, boost::function<void(std::string)> errorFunction)
{
computePathAsync(start, finish, maxDistance, false, resumeFunction, errorFunction);
}
void PathfindingService::computeSmoothPathAsync(Vector3 start, Vector3 finish, float maxDistance, boost::function<void(shared_ptr<Instance>) > resumeFunction, boost::function<void(std::string)> errorFunction)
{
computePathAsync(start, finish, maxDistance, true, resumeFunction, errorFunction);
}
void PathfindingService::computePathAsync(Vector3 start, Vector3 finish, float maxDistance, bool isSmooth, boost::function<void(shared_ptr<Instance>) > resumeFunction, boost::function<void(std::string)> errorFunction)
{
{
static boost::once_flag flag = BOOST_ONCE_INIT;
DataModel* dm = DataModel::get(this);
boost::call_once(flag, boost::bind(&RobloxGoogleAnalytics::trackEvent,GA_CATEGORY_GAME, "PathfindingService",
RBX::StringConverter<int>::convertToString(dm->getPlaceID()).c_str(), 0,false));
}
if(maxDistance > DFInt::PathfindingMaxDistance)
{
errorFunction("MaxDistance is too large");
return;
}
if(start.max(finish).max() > kMaxWorldCoordinate)
{
errorFunction("Start or Finish are outside of supported range");
return;
}
if(start.min(finish).min() < -kMaxWorldCoordinate)
{
errorFunction("Start or Finish are outside of supported range");
return;
}
if (!job)
{
job.reset(new PathfindingJob(this));
TaskScheduler::singleton().add(job);
DataModel* dataModel = DataModel::get(this);
ContactManagerSpatialHash* cmSpatialHash = dataModel->getWorkspace()->getWorld()->getContactManager()->getSpatialHash();
cmSpatialHash->registerCoarseMovementCallback(this);
MegaClusterInstance* terrain = static_cast<MegaClusterInstance*>(dataModel->getWorkspace()->getTerrain());
if (terrain)
{
if (terrain->isSmooth())
terrain->getSmoothGrid()->connectListener(this);
else
terrain->getVoxelGrid()->connectListener(this);
}
}
FASTLOG1F(FLog::PathfindingPerf, "Pathfinding request, distance: %f", maxDistance);
FASTLOG4F(FLog::PathfindingDetail, "Pathfinding request, start: %f %f %f, max distance: %f", start.x, start.y, start.z, maxDistance);
FASTLOG3F(FLog::PathfindingDetail, "Pathfinding request, finish: %f %f %f", finish.x, finish.y, finish.z);
Vector3int16 startCell = Voxel::worldToCell_floor(start);
Vector3int16 finishCell = Voxel::worldToCell_floor(finish);
FASTLOG3(FLog::PathfindingDetail, "Starting cell: %i %i %i", startCell.x, startCell.y, startCell.z);
FASTLOG3(FLog::PathfindingDetail, "Finish cell: %i %i %i", finishCell.x, finishCell.y, finishCell.z);
CreatePathRequest request;
request.start = start;
request.finish = finish;
request.startCell = startCell;
request.finishCell = finishCell;
request.maxDistance = maxDistance;
request.resumeFunction = resumeFunction;
request.errorFunction = errorFunction;
request.isSmooth = isSmooth;
request.emptyCutoff = (unsigned char)(emptyCutoff*255.0f);
if(countOrComputeDirtyChunks(startCell, finishCell, maxDistance, false) == 0)
request.execute(this);
else
{
scheduleRequest(shared_ptr<PathfindingRequest>(new CreatePathRequest(request)));
}
FASTLOG(FLog::PathfindingPerf, "Finished computeRawPathAsync");
}
void PathfindingService::scheduleRequest(const shared_ptr<PathfindingRequest>& request)
{
throttledRequests.push_back(request);
}
void PathfindingService::CreatePathRequest::execute(PathfindingService* service)
{
service->computePath(*this);
}
PathfindingService::SolidState PathfindingService::isSolid(const Vector3int16& pos, unsigned char emptyCutoff)
{
SpatialRegion::Id id = SpatialRegion::regionContainingVoxel(pos);
Vector3int16 offset = SpatialRegion::voxelCoordinateRelativeToEnclosingRegion(pos);
Voxel::OccupancyChunk* chunk = getChunkById(id);
if(!chunk)
return OUTSIDE;
unsigned char occupancy = chunk->occupancy[offset.y][offset.z][offset.x];
return occupancy > emptyCutoff ? SOLID : EMPTY;
}
void PathfindingService::executeThrottledRequests()
{
currentFrameNum++;
if((currentFrameNum % DFInt::PathfindingCollectPeriod) == 0)
{
collectOldChunks();
}
if(throttledRequests.size() == 0)
return;
int budget = DFInt::PathfindingChunksPerInvokation;
FASTLOG(FLog::PathfindingPerf, "Started executing throttled requests");
FASTLOG1(FLog::PathfindingDetail, "Number of pending paths: %u, current frame: %u", throttledRequests.size());
do
{
RBXPROFILER_SCOPE("Voxel", "executeRequest");
shared_ptr<PathfindingRequest> request;
throttledRequests.pop_front(&request);
FASTLOG3(FLog::PathfindingDetail, "Popped request, start cell: %i %i %i", request->startCell.x, request->startCell.y, request->startCell.z);
FASTLOG3(FLog::PathfindingDetail, "Popped request, finish cell: %i %i %i", request->finishCell.x, request->finishCell.y, request->finishCell.z);
RBXPROFILER_LABELF("Voxel", "(%d %d %d) -> (%d %d %d)", request->startCell.x, request->startCell.y, request->startCell.z, request->finishCell.x, request->finishCell.y, request->finishCell.z);
FASTLOG(FLog::PathfindingPerf, "Started computing occupancy");
int chunksToCompute = countOrComputeDirtyChunks(request->startCell, request->finishCell, request->maxDistance, true);
budget -= chunksToCompute;
FASTLOG1(FLog::PathfindingPerf, "Finished computing occupancy, %u chunks", chunksToCompute);
FASTLOG2(FLog::PathfindingDetail, "Computed %u chunks, current budget: %i", chunksToCompute, budget);
FASTLOG(FLog::PathfindingPerf, "Started computing path");
request->execute(this);
FASTLOG(FLog::PathfindingPerf, "Finished computing path");
}
while (budget > 0 && throttledRequests.size() > 0);
FASTLOG(FLog::PathfindingPerf, "Finished executing throttled requests");
}
ExtentsInt32 hashToVoxelChunk(const ExtentsInt32& extents, int level)
{
ExtentsInt32 voxelCellCoords = extents.shiftLeft(level + 1);
Vector3int32 voxelShift = Vector3int32(5,4,5);
RBXASSERT((1 << voxelShift.x) == kVoxelChunkSizeXZ);
RBXASSERT((1 << voxelShift.y) == kVoxelChunkSizeY);
RBXASSERT((1 << voxelShift.z) == kVoxelChunkSizeXZ);
ExtentsInt32 voxelChunkCoords = voxelCellCoords.shiftRight(voxelShift);
return voxelChunkCoords;
}
void PathfindingService::markChunksDirty(const ExtentsInt32& extents)
{
for(int x = extents.low.x; x <= extents.high.x; x++)
for(int y = extents.low.y; y <= extents.high.y; y++)
for(int z = extents.low.z; z <= extents.high.z; z++)
{
Voxel::OccupancyChunk* chunk = getChunkById(SpatialRegion::Id(x,y,z));
if(chunk)
chunk->dirty = true;
}
}
void PathfindingService::coarsePrimitiveMovement(Primitive* p, const UpdateInfo& info)
{
PartInstance* part = PartInstance::fromPrimitive(p);
if(part->getCookie() & PartCookie::IS_HUMANOID_PART)
{
return;
}
ExtentsInt32 newVoxelExtents = hashToVoxelChunk(info.newSpatialExtents, info.newLevel);
markChunksDirty(newVoxelExtents);
if(info.updateType == UpdateInfo::UPDATE_TYPE_Change && info.newLevel != info.oldLevel && info.newSpatialExtents != info.oldSpatialExtents)
{
ExtentsInt32 oldVoxelExtents = hashToVoxelChunk(info.oldSpatialExtents, info.oldLevel);
markChunksDirty(oldVoxelExtents);
}
}
void PathfindingService::terrainCellChanged(const Voxel::CellChangeInfo& info)
{
if(info.afterCell.solid.getBlock() == info.beforeCell.solid.getBlock())
return;
SpatialRegion::Id chunkId = SpatialRegion::regionContainingVoxel(info.position);
Voxel::OccupancyChunk* chunk = getChunkById(chunkId);
if(chunk)
chunk->dirty = true;
}
void PathfindingService::onTerrainRegionChanged(const Voxel2::Region& region)
{
if (region.empty())
return;
SpatialRegion::Id regionIdMin = SpatialRegion::regionContainingVoxel(region.begin().toVector3int16());
SpatialRegion::Id regionIdMax = SpatialRegion::regionContainingVoxel(region.end().toVector3int16() - Vector3int16(1, 1, 1));
for (int y = regionIdMin.value().y; y <= regionIdMax.value().y; ++y)
for (int z = regionIdMin.value().z; z <= regionIdMax.value().z; ++z)
for (int x = regionIdMin.value().x; x <= regionIdMax.value().x; ++x)
if (Voxel::OccupancyChunk* chunk = getChunkById(SpatialRegion::Id(x, y, z)))
chunk->dirty = true;
}
void PathfindingService::onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider)
{
if(oldProvider)
{
DataModel* dataModel = DataModel::get(oldProvider);
if(job)
{
if(dataModel->getWorkspace())
{
MegaClusterInstance* terrain = static_cast<MegaClusterInstance*>(dataModel->getWorkspace()->getTerrain());
if (terrain)
{
if (terrain->isSmooth())
terrain->getSmoothGrid()->disconnectListener(this);
else
terrain->getVoxelGrid()->disconnectListener(this);
}
}
ContactManagerSpatialHash* cmSpatialHash = dataModel->getWorkspace()->getWorld()->getContactManager()->getSpatialHash();
cmSpatialHash->unregisterCoarseMovementCallback(this);
TaskScheduler::singleton().removeBlocking(job);
job.reset();
}
}
}
int PathfindingService::checkPoints(int startPoint, const std::vector<Vector3>& points, unsigned char emptyCutoff)
{
for(std::vector<Vector3>::const_iterator it = points.begin() + startPoint; it != points.end(); ++it)
{
Vector3int16 cell = fastFloorInt16(*it);
if(isSolid(cell, emptyCutoff) != EMPTY)
return it - (points.begin() + startPoint);
}
return -1;
}
Voxel::OccupancyChunk* PathfindingService::getChunkById(const SpatialRegion::Id& id)
{
if(id == lastChunkId)
return lastChunk;
else
{
lastChunk = chunkMap.find(id);
lastChunkId = id;
return lastChunk;
}
}
const char* const sPath = "Path";
Path::Path(weak_ptr<PathfindingService> service, PathStatus status) : service(service), status(status)
{
}
void Path::addPoint(const Vector3& point)
{
points.push_back(point);
}
void Path::reverse()
{
std::reverse(points.begin(), points.end());
}
void Path::OcclusionRequest::execute(PathfindingService* service)
{
int result = service->checkPoints(startPoint, path->getPoints(), emptyCutoff);
resumeFunction(result == -1 ? -1 : (result + startPoint));
}
void Path::checkOcclusionAsync(int startPoint, boost::function<void(int)> resumeFunction, boost::function<void(std::string)> errorFunction)
{
shared_ptr<PathfindingService> pathfindingService = service.lock();
if(!pathfindingService)
{
errorFunction("PathfindingService no longer exists");
return;
}
if(startPoint >= (int)points.size() || startPoint < 0)
{
errorFunction("Start point value is invalid");
return;
}
Vector3 minPoint,maxPoint;
minPoint = points[startPoint];
maxPoint = points[startPoint];
for(int i = startPoint+1; i < (int)points.size(); i++)
{
Vector3 point = points[i];
minPoint = minPoint.min(point);
maxPoint = maxPoint.max(point);
}
Vector3int16 startCell = fastFloorInt16(minPoint);
Vector3int16 finishCell = fastFloorInt16(maxPoint);
int dirtyChunks = pathfindingService->countOrComputeDirtyChunks(startCell, finishCell, 0, false);
OcclusionRequest request;
request.startCell = startCell;
request.finishCell = finishCell;
request.startPoint = startPoint;
request.path = shared_from(this);
request.maxDistance = 0;
request.resumeFunction = resumeFunction;
request.errorFunction = errorFunction;
request.emptyCutoff = (unsigned char)(pathfindingService->getEmptyCutoff() * 255.0f);
if(dirtyChunks == 0)
{
request.execute(pathfindingService.get());
}
else
{
pathfindingService->scheduleRequest(shared_ptr<OcclusionRequest>(new OcclusionRequest(request)));
}
}
shared_ptr<const Reflection::ValueArray> Path::getPointCoordinates()
{
shared_ptr<Reflection::ValueArray> result(new Reflection::ValueArray);
for(std::vector<Vector3>::iterator it = points.begin(); it != points.end(); ++it)
{
Vector3 cellPos = *it;
result->push_back(Voxel::cellSpaceToWorldSpace(cellPos));
}
return result;
}
}