feat(simulation): add Worker AI polygon runtime and terrain navigation
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
FROM ros:humble-ros-base-jammy
|
||||
LABEL com.nodedc.product="mission-core" \
|
||||
com.nodedc.stack="ai-polygon-route-author" \
|
||||
com.nodedc.role="offline-route-author" \
|
||||
com.nodedc.managed-by="ai-polygon-worker"
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends g++ librecast-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY habitat_route.cpp /src/habitat_route.cpp
|
||||
RUN g++ -O2 -std=c++17 -I/usr/include/recastnavigation /src/habitat_route.cpp -lDetour -o /usr/local/bin/habitat-route
|
||||
ENTRYPOINT ["habitat-route"]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Offline Habitat route authoring
|
||||
|
||||
This tool uses Ubuntu's packaged Recast/Detour library to extract a complete
|
||||
route from a supplied Habitat navmesh. It runs on Worker only. It outputs task
|
||||
waypoints, never runtime collision knowledge for the AI composition.
|
||||
|
||||
Build the `Dockerfile` into `ndc-ai-polygon-route-author:habitat-v1` using the
|
||||
isolated anonymous Docker configuration described in the Worker runbook. Mount
|
||||
only the source asset directory read-only, disable container networking, and run:
|
||||
|
||||
```
|
||||
habitat-route /data/scene.navmesh startX startY startZ goalX goalY goalZ
|
||||
```
|
||||
|
||||
Coordinates remain Habitat Y-up in the output. Mission Core conversion is
|
||||
`(x,y,z) -> (x,-z,y)`. The source navmesh's agent radius and step capability may
|
||||
be incompatible with the 1 x 1 m rover. Extracted points require full-footprint
|
||||
support/collision checks and a real closed-loop run before acceptance. Partial
|
||||
paths, incompatible binary layouts, and disconnected endpoints are rejected.
|
||||
|
||||
Sources: Habitat-Sim `src/esp/nav/PathFinder.cpp` and `PathFinder.h` define the
|
||||
version 1/2 binary container; Recast Navigation's Detour performs the actual
|
||||
path search. The loader supports 32-bit Detour references and the v2 56-byte
|
||||
settings layout. The image build and source/output hashes belong in the
|
||||
private qualification receipt. No asset is modified by route extraction.
|
||||
@@ -0,0 +1,49 @@
|
||||
// Offline mission authoring using the supplied Habitat/Detour navmesh.
|
||||
// Binary layout: facebookresearch/habitat-sim src/esp/nav/PathFinder.cpp.
|
||||
// No navmesh or privileged scene geometry is supplied to runtime inference.
|
||||
#include <DetourNavMesh.h>
|
||||
#include <DetourNavMeshQuery.h>
|
||||
#include <DetourAlloc.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <stdexcept>
|
||||
#include <cmath>
|
||||
struct Header { int magic, version, tiles; dtNavMeshParams params; };
|
||||
struct Tile { dtTileRef ref; int size; };
|
||||
void require(bool ok, const char* message) { if (!ok) throw std::runtime_error(message); }
|
||||
int main(int argc, char** argv) { try {
|
||||
require(argc == 8, "navmesh start-x start-y start-z goal-x goal-y goal-z required");
|
||||
std::ifstream file(argv[1], std::ios::binary); Header h{};
|
||||
require(bool(file.read(reinterpret_cast<char*>(&h), sizeof(h))), "header read failed");
|
||||
require(h.magic == (('M'<<24)|('S'<<16)|('E'<<8)|'T') && (h.version==1 || h.version==2), "unsupported navmesh");
|
||||
require(h.tiles > 0 && h.tiles < 100000, "invalid tile count");
|
||||
// v2 stores thirteen float settings and four boolean flags (56 bytes).
|
||||
if (h.version == 2) file.seekg(56, std::ios::cur);
|
||||
dtNavMesh* mesh = dtAllocNavMesh(); require(mesh, "allocation failed");
|
||||
require(dtStatusSucceed(mesh->init(&h.params)), "init failed");
|
||||
for (int i=0;i<h.tiles;i++) {
|
||||
Tile t{}; require(bool(file.read(reinterpret_cast<char*>(&t),sizeof(t))), "tile header failed");
|
||||
require(t.ref && t.size>0 && t.size<100000000, "invalid tile");
|
||||
auto* data=static_cast<unsigned char*>(dtAlloc(t.size,DT_ALLOC_PERM));
|
||||
require(data && bool(file.read(reinterpret_cast<char*>(data),t.size)), "tile read failed");
|
||||
require(dtStatusSucceed(mesh->addTile(data,t.size,DT_TILE_FREE_DATA,t.ref,nullptr)), "tile version mismatch");
|
||||
}
|
||||
require(file.peek()==std::char_traits<char>::eof(), "unexpected trailing bytes");
|
||||
dtNavMeshQuery query; require(dtStatusSucceed(query.init(mesh,65535)), "query init failed");
|
||||
float start[3],goal[3],nearStart[3],nearGoal[3],extent[3]={2,4,2};
|
||||
for(int i=0;i<3;i++){start[i]=std::stof(argv[i+2]);goal[i]=std::stof(argv[i+5]);}
|
||||
dtQueryFilter filter; dtPolyRef first=0,last=0;
|
||||
require(dtStatusSucceed(query.findNearestPoly(start,extent,&filter,&first,nearStart)) && first,"start outside navmesh");
|
||||
require(dtStatusSucceed(query.findNearestPoly(goal,extent,&filter,&last,nearGoal)) && last,"goal outside navmesh");
|
||||
dtPolyRef path[4096];int count=0;
|
||||
auto status=query.findPath(first,last,nearStart,nearGoal,&filter,path,&count,4096);
|
||||
require(dtStatusSucceed(status) && !dtStatusDetail(status,DT_BUFFER_TOO_SMALL) && count && path[count-1]==last,"no complete route");
|
||||
float points[4096*3];int n=0;
|
||||
status=query.findStraightPath(nearStart,nearGoal,path,count,points,nullptr,nullptr,&n,4096,DT_STRAIGHTPATH_ALL_CROSSINGS);
|
||||
require(dtStatusSucceed(status) && !dtStatusDetail(status,DT_BUFFER_TOO_SMALL) && n>=2,"route extraction failed");
|
||||
double length=0;for(int i=1;i<n;i++){double d=0;for(int j=0;j<3;j++)d+=std::pow(points[i*3+j]-points[(i-1)*3+j],2);length+=std::sqrt(d);}
|
||||
std::cout<<std::setprecision(10)<<"{\"length_m\":"<<length<<",\"points_habitat\":[";
|
||||
for(int i=0;i<n;i++){if(i)std::cout<<',';std::cout<<'['<<points[i*3]<<','<<points[i*3+1]<<','<<points[i*3+2]<<']';}
|
||||
std::cout<<"]}"<<std::endl;dtFreeNavMesh(mesh);return 0;
|
||||
} catch(const std::exception& e){std::cerr<<e.what()<<std::endl;return 1;} }
|
||||
Reference in New Issue
Block a user