6#include "navmeshbuilder.h"
10#include "navmeshtransform.h"
11#include "recastnav_p.h"
13#include "recastnavsettings_p.h"
14#include "routingarea.h"
17#include <KOSMIndoorMap/MapData>
18#include <KOSMIndoorMap/MapCSSParser>
19#include <KOSMIndoorMap/MapCSSResult>
20#include <KOSMIndoorMap/MapCSSStyle>
21#include <KOSMIndoorMap/OverlaySource>
23#include <loader/levelparser_p.h>
24#include <qloggingcategory.h>
25#include <scene/penwidthutil_p.h>
26#include <scene/scenegraphitem.h>
27#include <style/mapcssdeclaration_p.h>
28#include <style/mapcssstate_p.h>
32#include <QPainterPath>
35#include <private/qtriangulator_p.h>
36#include <private/qtriangulatingstroker_p.h>
39#include <DetourNavMeshBuilder.h>
43#include <unordered_map>
44#include <unordered_set>
48namespace KOSMIndoorRouting {
50enum class LinkDirection {
Forward, Backward, Bidirectional };
56}
constexpr inline const routing_area_map[] = {
57 {
"escalator", AreaType::Escalator },
58 {
"movingWalkway", AreaType::MovingWalkway },
59 {
"stairs", AreaType::Stairs },
60 {
"elevator", AreaType::Elevator },
61 {
"tactilePaving", AreaType::TactilePaving },
62 {
"streetCrossing", AreaType::StreetCrossing },
63 {
"ramp", AreaType::Ramp },
64 {
"room", AreaType::Room },
67class NavMeshBuilderPrivate
70 [[nodiscard]] std::optional<LinkDirection> linkDirection(KOSMIndoorMap::LayerSelectorKey layerKey)
const;
74 [[nodiscard]]
int levelForNode(
OSM::Id nodeId)
const;
75 void addNodeToLevelIndex(
OSM::Id nodeId,
int level);
76 void indexNodeLevels();
83 [[nodiscard]]
double doorWidth(
const OSM::Node *node);
84 void extrudeWall(
const std::vector<const OSM::Node*> &way,
int floorLevel);
86 void addVertex(
float x,
float y,
float z);
87 void addFace(std::size_t i, std::size_t j, std::size_t k, AreaType areaType);
88 void addOffMeshConnection(
float x1,
float y1,
float z1,
float x2,
float y2,
float z2, LinkDirection linkDir, AreaType areaType);
91 [[nodiscard]]
bool buildNavMeshTile(rcContext *ctx,
int tx,
int ty, NavMeshPrivate *result);
101 std::array<KOSMIndoorMap::LayerSelectorKey, 3> m_linkKeys;
102 std::array<KOSMIndoorMap::ClassSelectorKey, std::size(routing_area_map)> m_areaClassKeys;
104 NavMeshTransform m_transform;
106 std::unordered_map<OSM::Id, int> m_nodeLevelMap;
109 std::unordered_set<OSM::Element> m_processedLinks;
112 std::vector<float> m_verts;
113 inline int numVerts()
const {
return (
int)m_verts.size() / 3; }
114 std::vector<int> m_tris;
115 inline int numTris()
const {
return (
int)m_tris.size() / 3; }
116 std::vector<uint8_t> m_triAreaIds;
120 std::vector<float> verts;
121 std::vector<float> rads;
122 std::vector<uint16_t> flags;
123 std::vector<uint8_t> areas;
124 std::vector<uint8_t> dir;
125 std::vector<uint32_t> userId;
127 inline int offMeshCount()
const {
return (
int) m_offMeshCon.rads.size(); }
143 qsizetype m_vertexOffset = 0;
147using namespace KOSMIndoorRouting;
167 for (; subIt !=
path.
end(); ++subIt) {
168 subPoly.
push_back(
QPointF((*subIt)->coordinate.lonF(), (*subIt)->coordinate.latF()));
169 if ((*subIt)->id == pathBegin && subIt != it && subIt != std::prev(
path.
end())) {
175 poly = poly.
isEmpty() ? std::move(subPoly) : poly.united(subPoly);
183 assert(e.type() == OSM::Type::Relation);
184 QPolygonF outerPath = createPolygon(dataSet, e);
188 for (
const auto &mem : e.relation()->members) {
189 const bool isInner = std::strcmp(mem.role().name(),
"inner") == 0;
190 const bool isOuter = std::strcmp(mem.role().name(),
"outer") == 0;
191 if (mem.type() != OSM::Type::Way || (!isInner && !isOuter)) {
194 if (
auto way = dataSet.
way(mem.id)) {
195 const auto subPoly = createPolygon(dataSet,
OSM::Element(way));
199 path.addPolygon(subPoly);
208NavMeshBuilder::NavMeshBuilder(
QObject *parent)
210 , d(std::make_unique<NavMeshBuilderPrivate>())
214NavMeshBuilder::~NavMeshBuilder() =
default;
220 if (d->m_style.isEmpty()) {
222 d->m_style = p.parse(QStringLiteral(
":/org.kde.kosmindoorrouting/navmesh-filter.mapcss"));
224 qCWarning(
Log) << p.errorMessage();
229 constexpr const char *link_direction_keys[] = {
"link_forward",
"link_backward",
"link" };
230 for (
auto dir : {LinkDirection::Forward, LinkDirection::Backward, LinkDirection::Bidirectional}) {
231 d->m_linkKeys[qToUnderlying(dir)] = d->m_style.layerKey(link_direction_keys[qToUnderlying(dir)]);
234 for (std::size_t i = 0; i < std::size(routing_area_map); ++i) {
235 d->m_areaClassKeys[i] = d->m_style.classKey(routing_area_map[i].name);
236 if (d->m_areaClassKeys[i].isNull()) {
237 qCWarning(
Log) <<
"area class key not found:" << routing_area_map[i].name;
242 if (!d->m_data.isEmpty()) {
243 d->m_style.compile(d->m_data.dataSet());
245 d->m_tagKeys.door = d->m_data.dataSet().tagKey(
"door");
246 d->m_tagKeys.entrance = d->m_data.dataSet().tagKey(
"entrance");
247 d->m_tagKeys.indoor = d->m_data.dataSet().tagKey(
"indoor");
248 d->m_tagKeys.highway = d->m_data.dataSet().tagKey(
"highway");
249 d->m_tagKeys.room = d->m_data.dataSet().tagKey(
"room");
250 d->m_tagKeys.stairs = d->m_data.dataSet().tagKey(
"stairs");
256 d->m_equipmentModel = equipmentModel;
260void NavMeshBuilder::writeDebugNavMesh(
const QString &gsetFile,
const QString &objFile)
262 d->m_gsetFileName = gsetFile;
263 d->m_objFileName = objFile;
266std::optional<LinkDirection> NavMeshBuilderPrivate::linkDirection(KOSMIndoorMap::LayerSelectorKey layerKey)
const
268 for (
auto dir : {LinkDirection::Forward, LinkDirection::Backward, LinkDirection::Bidirectional}) {
269 if (!m_linkKeys[qToUnderlying(dir)].isNull() && m_linkKeys[qToUnderlying(dir)] == layerKey) {
278 for (std::size_t i = 0; i < m_areaClassKeys.size(); ++i) {
279 if (!m_areaClassKeys[i].isNull() && res.
hasClass(m_areaClassKeys[i])) {
280 return routing_area_map[i].area;
284 return AreaType::Walkable;
287int NavMeshBuilderPrivate::levelForNode(
OSM::Id nodeId)
const
289 const auto it = m_nodeLevelMap.find(nodeId);
290 return it != m_nodeLevelMap.
end() ? (*it).second : 0;
293void NavMeshBuilderPrivate::addNodeToLevelIndex(
OSM::Id nodeId,
int level)
295 auto it = m_nodeLevelMap.find(nodeId);
296 if (it == m_nodeLevelMap.end()) {
297 m_nodeLevelMap[nodeId] =
level;
300 if ((*it).second != level) {
301 (*it).second = std::numeric_limits<int>::min();
305void NavMeshBuilderPrivate::indexNodeLevels()
307 for (
const auto &level : m_data.levelMap()) {
311 for (
const auto elem :
level.second) {
312 switch (elem.type()) {
313 case OSM::Type::Null:
315 case OSM::Type::Node:
320 const auto lvl = elem.tagValue(
"level");
321 if (lvl.isEmpty() || lvl.contains(
';')) {
324 for (
OSM::Id nodeId : elem.way()->nodes) {
325 addNodeToLevelIndex(nodeId,
level.
first.numericLevel());
329 case OSM::Type::Relation:
337void NavMeshBuilder::start()
342 d->m_transform.initialize(d->m_data.boundingBox());
343 d->indexNodeLevels();
345 std::vector<OSM::Element> hiddenElements;
346 if (d->m_equipmentModel) {
347 d->m_equipmentModel->hiddenElements(hiddenElements);
349 std::sort(hiddenElements.begin(), hiddenElements.end());
351 for (
const auto &level : d->m_data.levelMap()) {
352 for (
const auto &elem :
level.second) {
353 if (std::binary_search(hiddenElements.begin(), hiddenElements.end(), elem)) {
356 d->processElement(elem,
level.
first.numericLevel());
359 if (
level.
first.numericLevel() % 10 || !d->m_equipmentModel) {
363 d->processElement(elem, floorLevel);
367 [[unlikely]]
if (!d->m_gsetFileName.isEmpty()) {
372 qCDebug(
Log) <<
"Vertex data size:" << d->m_verts.size() *
sizeof(float);
373 qCDebug(
Log) <<
"Triangle index size:" << d->m_tris.size() *
sizeof(int);
374 qCDebug(
Log) <<
"Triangle area size:" << d->m_triAreaIds.size();
375 qCDebug(
Log) <<
"Off-mesh data size:" << d->offMeshCount() * 16;
384void NavMeshBuilderPrivate::processElement(
OSM::Element elem,
int floorLevel)
390 KOSMIndoorMap::MapCSSState filterState;
391 filterState.element = elem;
393 m_style.
evaluate(filterState, m_filterResult);
395 for (
const auto &res : m_filterResult.
results()) {
397 processGeometry(elem, floorLevel, res);
398 }
else if (
const auto linkDir = linkDirection(res.
layerSelector()); linkDir) {
399 processLink(elem, floorLevel, *linkDir, res);
408 if (prop && prop->doubleValue() > 0.0) {
410 if (elem.type() == OSM::Type::Relation) {
411 path = createPath(m_data.dataSet(), elem);
413 path.addPolygon(createPolygon(m_data.dataSet(), elem));
417 const auto triSet = qTriangulate(m_transform.mapGeoToNav(path));
418 qCDebug(
Log) <<
"A" << elem.url() << m_transform.mapGeoToNav(path).boundingRect() <<
path.elementCount() << triSet.indices.
size() << triSet.vertices.size() << m_vertexOffset << floorLevel;
420 for (qsizetype i = 0; i < triSet.vertices.size(); i += 2) {
421 addVertex(triSet.vertices[i], m_transform.mapHeightToNav(floorLevel), triSet.vertices[i + 1]);
423 if (triSet.indices.type() == QVertexIndexVector::UnsignedShort) {
424 for (qsizetype i = 0; i <triSet.indices.size(); i += 3) {
425 addFace(*(
reinterpret_cast<const uint16_t*
>(triSet.indices.data()) + i) + m_vertexOffset,
426 *(
reinterpret_cast<const uint16_t*
>(triSet.indices.data()) + i + 1) + m_vertexOffset,
427 *(
reinterpret_cast<const uint16_t*
>(triSet.indices.data()) + i + 2) + m_vertexOffset,
430 }
else if (triSet.indices.type() == QVertexIndexVector::UnsignedInt) {
431 for (qsizetype i = 0; i <triSet.indices.size(); i += 3) {
432 addFace(*(
reinterpret_cast<const uint32_t*
>(triSet.indices.data()) + i) + m_vertexOffset,
433 *(
reinterpret_cast<const uint32_t*
>(triSet.indices.data()) + i + 1) + m_vertexOffset,
434 *(
reinterpret_cast<const uint32_t*
>(triSet.indices.data()) + i + 2) + m_vertexOffset,
438 m_vertexOffset += triSet.vertices.size() / 2;
445 if (
const auto penWidth = prop ? KOSMIndoorMap::PenWidthUtil::penWidth(elem, prop, dummyUnit) : 0.0; penWidth > 0.0) {
446 QPolygonF poly = m_transform.mapGeoToNav(createPolygon(m_data.dataSet(), elem));
448 path.addPolygon(poly);
454 QTriangulatingStroker stroker;
455 stroker.process(qtVectorPathForPath(path), pen, {}, {});
456 qCDebug(
Log) <<
"L" << elem.url() << stroker.vertexCount() << pen.
widthF();
458 for (
int i = 0; i < stroker.vertexCount(); i += 2) {
460 if (poly.
size() == 2 && elem.type() == OSM::Type::Way) {
461 const auto way = elem.way();
462 const auto l1 = levelForNode(way->nodes.at(0));
463 const auto l2 = levelForNode(way->nodes.at(1));
464 qCDebug(
Log) <<
" S" << elem.url() << floorLevel << l1 << l2;
465 if (l1 != l2 && l1 != std::numeric_limits<int>::min() && l2 != std::numeric_limits<int>::min()) {
466 QPointF p(*(stroker.vertices() + i), *(stroker.vertices() + i + 1));
470 addVertex(*(stroker.vertices() + i), m_transform.mapHeightToNav(l), *(stroker.vertices() + i + 1));
472 for (
int i = 0; i < stroker.vertexCount() / 2 - 2; ++i) {
475 addFace(m_vertexOffset + i, m_vertexOffset + i + 1, m_vertexOffset + i + 2, areaType(res));
477 addFace(m_vertexOffset + i + 1, m_vertexOffset + i, m_vertexOffset + i + 2, areaType(res));
480 m_vertexOffset += stroker.vertexCount() / 2;
486 if (prop && prop->doubleValue() > 0.0) {
488 const auto way = elem.
outerPath(m_data.dataSet());
492 const auto highway = elem.tagValue(m_tagKeys.highway);
493 const auto indoor = elem.tagValue(m_tagKeys.indoor);
494 const auto room = elem.tagValue(m_tagKeys.room);
495 if (highway ==
"elevator" || room ==
"stairs" || room ==
"steps" ||
496 (indoor ==
"room" && elem.tagValue(m_tagKeys.stairs) ==
"yes") ||
497 (indoor ==
"yes" && highway ==
"steps")) {
498 const auto isDoor = [
this](
const OSM::Node *node) {
501 if (std::none_of(way.begin(), way.end(), isDoor)) {
502 qCDebug(
Log) <<
"skipping walls for door-less room" << elem.url();
506 extrudeWall(way, floorLevel);
513 if (m_processedLinks.contains(elem)) {
519 if (prop && prop->doubleValue() <= 0.0) {
523 std::vector<int> levels;
524 KOSMIndoorMap::LevelParser::parse(elem.tagValue(
"level"), elem, [&levels](
int level,
auto) { levels.push_back(level); });
525 if (levels.size() > 1) {
526 qDebug() <<
"E" << elem.url() << levels;
528 const QPointF p = m_transform.mapGeoToNav(elem.center());
529 for (std::size_t i = 0; i < levels.size() - 1; ++i) {
530 addOffMeshConnection(p.
x(), m_transform.mapHeightToNav(levels[i]), p.
y(), p.
x(), m_transform.mapHeightToNav(levels[i + 1]), p.
y(), LinkDirection::Bidirectional, areaType(res));
532 m_processedLinks.insert(elem);
538 if (
const auto penWidth = prop ? KOSMIndoorMap::PenWidthUtil::penWidth(elem, prop, dummyUnit) : 0.0; penWidth <= 0.0) {
542 const auto way = elem.way();
543 if (way->nodes.size() == 2) {
544 auto l1 = levelForNode(way->nodes.at(0));
545 auto l2 = levelForNode(way->nodes.at(1));
547 std::vector<int> levels;
548 KOSMIndoorMap::LevelParser::parse(elem.tagValue(
"level"), elem, [&levels](
int level,
auto) { levels.push_back(level); });
549 if (levels.size() == 2) {
550 auto l1b = levels[0];
551 auto l2b = levels[1];
555 if (l1 == l1b && l2 != l2b && (l2 == 0 || l2 == std::numeric_limits<int>::min())) {
557 }
else if (l2 == l2b && l1 != l1b && (l1 == 0 || l1 == std::numeric_limits<int>::min())) {
562 if (l1 != l2 && l1 != std::numeric_limits<int>::min() && l2 != std::numeric_limits<int>::min()) {
563 qCDebug(
Log) <<
" LINK" << elem.url() << floorLevel << l1 << l2 << levels;
564 const auto poly = createPolygon(m_data.dataSet(), elem);
565 const auto p1 = m_transform.mapGeoToNav(poly.
at(0));
566 const auto p2 = m_transform.mapGeoToNav(poly.
at(1));
567 addOffMeshConnection(p1.x(), m_transform.mapHeightToNav(l1), p1.y(), p2.x(), m_transform.mapHeightToNav(l2), p2.y(), linkDir, areaType(res));
569 qCDebug(
Log) <<
" failed to determin levels for link" << elem.url() <<floorLevel << l1 << l2 << levels;
571 m_processedLinks.insert(elem);
576double NavMeshBuilderPrivate::doorWidth(
const OSM::Node *node)
578 if (node->tags.empty()) {
582 KOSMIndoorMap::MapCSSState state;
583 state.element = node;
584 m_filterResultL2.
clear();
586 m_style.
evaluate(state, m_filterResultL2);
588 const auto &layer = m_filterResultL2[{}];
590 if (!prop || prop->doubleValue() <= 0.0) {
594 return prop ? std::max(prop->doubleValue(), 0.0) : 1.0;
597void NavMeshBuilderPrivate::extrudeWall(
const std::vector<const OSM::Node*> &way,
int floorLevel)
599 bool reuseEdge =
false;
600 for (std::size_t i = 0; i < way.size() - 1; ++i) {
601 auto p1 = m_transform.mapGeoToNav(way[i]->coordinate);
602 auto p2 = m_transform.mapGeoToNav(way[i + 1]->coordinate);
605 if (
const auto w = doorWidth(way[i]); w >= 0.0) {
607 if (line.length() < w) {
610 QLineF reverseLine(p2, p1);
611 reverseLine.setLength(line.length() - w);
612 p1 = reverseLine.p2();
615 if (
auto w = doorWidth(way[i + 1]); w >= 0.0) {
617 if (line.length() < w) {
620 line.setLength(line.length() - w);
624 qsizetype offset = m_vertexOffset;
626 addVertex((
float)p1.x(), m_transform.mapHeightToNav(floorLevel), (
float)p1.y());
627 addVertex((
float)p1.x(), m_transform.mapHeightToNav(floorLevel + 10), (
float)p1.y());
631 assert(m_vertexOffset >= 2);
635 addVertex((
float)p2.x(), m_transform.mapHeightToNav(floorLevel), (
float)p2.y());
636 addVertex((
float)p2.x(), m_transform.mapHeightToNav(floorLevel + 10), (
float)p2.y());
639 addFace(offset, offset + 2, offset + 1, AreaType::Unwalkable);
640 addFace(offset + 2, offset + 3, offset + 1, AreaType::Unwalkable);
644void NavMeshBuilderPrivate::addVertex(
float x,
float y,
float z)
646 for (
const auto v : {x, y, z}) {
647 m_verts.push_back(v);
651void NavMeshBuilderPrivate::addFace(std::size_t i, std::size_t j, std::size_t k, AreaType areaType)
653 for (
const auto v : {i, j, k}) {
654 m_tris.push_back((
int)v);
656 m_triAreaIds.push_back(qToUnderlying(areaType));
659void NavMeshBuilderPrivate::addOffMeshConnection(
float x1,
float y1,
float z1,
float x2,
float y2,
float z2, LinkDirection linkDir, AreaType areaType)
661 if (linkDir == LinkDirection::Backward) {
665 linkDir = LinkDirection::Forward;
668 for (
const auto v : { x1, y1, z1, x2, y2, z2 }) {
669 m_offMeshCon.verts.push_back(v);
671 m_offMeshCon.rads.push_back(0.6);
672 m_offMeshCon.flags.push_back(flagsForAreaType(areaType));
673 m_offMeshCon.areas.push_back(qToUnderlying(areaType));
674 m_offMeshCon.dir.push_back(linkDir == LinkDirection::Bidirectional ? 1 : 0);
675 m_offMeshCon.userId.push_back(0);
678void NavMeshBuilderPrivate::writeGsetFile()
680 QFile f(m_gsetFileName);
683 f.write(m_objFileName.
toUtf8());
717 auto p = m_transform.mapGeoToNav(m_data.boundingBox().min);
720 f.write(
QByteArray::number(std::prev(m_data.levelMap().end())->first.numericLevel()));
726 p = m_transform.mapGeoToNav(m_data.boundingBox().max);
736 for (
int i = 0; i < offMeshCount(); ++i) {
738 for (
int j = 0; j < 6; ++j) {
753void NavMeshBuilderPrivate::writeObjFile()
755 QFile f(m_objFileName);
758 for (std::size_t i = 0; i < m_verts.size(); i += 3) {
768 for (std::size_t i = 0; i < m_tris.size(); i += 3) {
779void NavMeshBuilderPrivate::buildNavMesh()
783 const auto bmin = m_transform.mapGeoHeightToNav(m_data.boundingBox().min, std::prev(m_data.levelMap().end())->first.numericLevel());
784 const auto bmax = m_transform.mapGeoHeightToNav(m_data.boundingBox().max, m_data.levelMap().begin()->first.numericLevel());
787 const auto result = NavMeshPrivate::create(resultData);
788 result->m_transform = m_transform;
790 result->m_dirty =
true;
791 qCDebug(
Log) <<
"nav mesh invalidated";
800 rcCalcGridSize(bmin, bmax, RECAST_CELL_SIZE, &width, &height);
801 qCDebug(
Log) << width <<
"x" << height <<
"cells";
803 const auto tileWidth = (width + RECAST_TILE_SIZE - 1) / RECAST_TILE_SIZE;
804 const auto tileHeight = (height + RECAST_TILE_SIZE - 1) / RECAST_TILE_SIZE;
805 qCDebug(
Log) << tileWidth <<
"x" << tileHeight <<
"tiles";
810 const auto tileBits = std::min<int>(std::ceil(std::log2(tileWidth * tileHeight)), 14);
811 const auto polyBits = 22 - tileBits;
812 qCDebug(
Log) <<
"using" << tileBits <<
"bits for tiles and" << polyBits <<
"bits for polygons";
814 result->m_navMesh.reset(dtAllocNavMesh());
816 dtNavMeshParams params;
817 rcVcopy(params.orig, bmin);
818 params.tileWidth = RECAST_TILE_SIZE * RECAST_CELL_SIZE;
819 params.tileHeight = RECAST_TILE_SIZE * RECAST_CELL_SIZE;
820 params.maxTiles = (1 << tileBits);
821 params.maxPolys = (1 << polyBits);
823 result->m_navMesh->init(¶ms);
826 for (
int tx = 0; tx < tileWidth; ++tx) {
827 for (
int ty = 0; ty < tileHeight; ++ty) {
828 if (!buildNavMeshTile(&ctx, tx, ty, result)) {
834 result->m_navMeshQuery.reset(dtAllocNavMeshQuery());
835 const auto status = result->m_navMeshQuery->init(result->m_navMesh.get(), RECAST_NAV_QUERY_MAX_NODES);
836 if (dtStatusFailed(
status)) {
837 qCWarning(
Log) <<
"Failed to init dtNavMeshQuery";
841 m_navMesh = std::move(resultData);
842 qCDebug(
Log) <<
"done";
846bool NavMeshBuilderPrivate::buildNavMeshTile(rcContext *ctx,
int tx,
int ty, NavMeshPrivate *result)
848 qCDebug(
Log) <<
" building tile" << tx << ty;
850 const auto walkableHeight = (int)std::ceil(RECAST_AGENT_HEIGHT / RECAST_CELL_HEIGHT);
851 const auto walkableClimb = (int)std::floor(RECAST_AGENT_MAX_CLIMB/ RECAST_CELL_HEIGHT);
852 const auto walkableRadius = (int)std::ceil(RECAST_AGENT_RADIUS / RECAST_CELL_SIZE);
853 const auto borderSize = (float)walkableRadius + 3.0f;
856 auto bmin = m_transform.mapGeoHeightToNav(m_data.boundingBox().min, std::prev(m_data.levelMap().end())->first.numericLevel());
857 bmin.x += (float)tx * RECAST_TILE_SIZE * RECAST_CELL_SIZE;
858 bmin.z += (float)ty * RECAST_TILE_SIZE * RECAST_CELL_SIZE;
860 auto bmax = m_transform.mapGeoHeightToNav(m_data.boundingBox().max, m_data.levelMap().begin()->first.numericLevel());
861 bmax.x = std::min(bmax.x, bmin.x + RECAST_TILE_SIZE * RECAST_CELL_SIZE);
862 bmax.z = std::min(bmax.z, bmin.z + RECAST_TILE_SIZE * RECAST_CELL_SIZE);
865 bmin.x -= borderSize * RECAST_CELL_SIZE;
866 bmin.z -= borderSize * RECAST_CELL_SIZE;
867 bmax.x += borderSize * RECAST_CELL_SIZE;
868 bmax.z += borderSize * RECAST_CELL_SIZE;
874 rcCalcGridSize(bmin, bmax, RECAST_CELL_SIZE, &width, &height);
877 rcHeightfieldPtr solid(rcAllocHeightfield());
878 if (!rcCreateHeightfield(ctx, *solid, width, height, bmin, bmax, RECAST_CELL_SIZE, RECAST_CELL_HEIGHT)) {
879 qCWarning(
Log) <<
"Failed to create solid heightfield.";
883 if (!rcRasterizeTriangles(ctx, m_verts.data(), numVerts(), m_tris.data(), m_triAreaIds.data(), numTris(), *solid, walkableClimb)) {
884 qCWarning(
Log) <<
"Failed to rasterize triangles";
890 rcFilterLowHangingWalkableObstacles(ctx, walkableClimb, *solid);
891 rcFilterLedgeSpans(ctx, walkableHeight, walkableClimb, *solid);
892 rcFilterWalkableLowHeightSpans(ctx, walkableHeight, *solid);
895 rcCompactHeightfieldPtr chf(rcAllocCompactHeightfield());
896 if (!rcBuildCompactHeightfield(ctx, walkableHeight, walkableClimb, *solid, *chf)) {
897 qCWarning(
Log) <<
"Failed to build compact height field.";
902 if (!rcErodeWalkableArea(ctx, walkableRadius, *chf)) {
903 qCWarning(
Log) <<
"Failed to erode walkable area";
907 if constexpr (RECAST_PARTITION_TYPE == RecastPartitionType::Monotone) {
908 if (!rcBuildRegionsMonotone(ctx, *chf, (
int)borderSize, (
int)std::pow(RECAST_REGION_MIN_AREA, 2.0), (
int)std::pow(RECAST_REGION_MERGE_AREA, 2.0))) {
909 qCWarning(
Log) <<
"Failed to build monotone regions";
912 }
else if constexpr (RECAST_PARTITION_TYPE == RecastPartitionType::Watershed) {
913 if (!rcBuildDistanceField(ctx, *chf)) {
914 qCWarning(
Log) <<
"Failed to build distance field.";
917 if (!rcBuildRegions(ctx, *chf, (
int)borderSize, (
int)std::pow(RECAST_REGION_MIN_AREA, 2.0), (
int)std::pow(RECAST_REGION_MERGE_AREA, 2.0))) {
918 qCWarning(
Log) <<
"Failed to build watershed regions.";
922 static_assert(
"partition type not yet implemented");
926 rcContourSetPtr cset(rcAllocContourSet());
927 if (!rcBuildContours(ctx, *chf, RECAST_MAX_SIMPLIFICATION_ERROR, RECAST_MAX_EDGE_LEN / RECAST_CELL_SIZE, *cset)) {
928 qCWarning(
Log) <<
"Failed to create contours.";
933 rcPolyMeshPtr pmesh(rcAllocPolyMesh());
934 if (!rcBuildPolyMesh(ctx, *cset, DT_VERTS_PER_POLYGON, *pmesh)) {
935 qCWarning(
Log) <<
"Failed to triangulate contours";
938 if (pmesh->npolys == 0) {
939 qCDebug(
Log) <<
"skipping empty tile";
944 QFile f(u
"pmesh.obj"_s);
946 RecastDebugIoAdapter adapter(&f);
947 duDumpPolyMeshToObj(*pmesh, &adapter);
951 rcPolyMeshDetailPtr dmesh(rcAllocPolyMeshDetail());
952 if (!rcBuildPolyMeshDetail(ctx, *pmesh, *chf, RECAST_DETAIL_SAMPLE_DIST * RECAST_CELL_SIZE, RECAST_DETAIL_SAMPLE_MAX_ERROR * RECAST_CELL_HEIGHT, *dmesh)) {
953 qCWarning(
Log) <<
"Failed to build detail mesh";
960 uint8_t *navData =
nullptr;
963 for (
int i = 0; i < pmesh->npolys; ++i) {
964 pmesh->flags[i] = flagsForAreaType(
static_cast<AreaType
>(pmesh->areas[i]));
967 dtNavMeshCreateParams params;
968 std::memset(¶ms, 0,
sizeof(params));
969 params.verts = pmesh->verts;
970 params.vertCount = pmesh->nverts;
971 params.polys = pmesh->polys;
972 params.polyAreas = pmesh->areas;
973 params.polyFlags = pmesh->flags;
974 params.polyCount = pmesh->npolys;
975 params.nvp = pmesh->nvp;
976 params.detailMeshes = dmesh->meshes;
977 params.detailVerts = dmesh->verts;
978 params.detailVertsCount = dmesh->nverts;
979 params.detailTris = dmesh->tris;
980 params.detailTriCount = dmesh->ntris;
981 params.offMeshConVerts = m_offMeshCon.verts.data();
982 params.offMeshConRad = m_offMeshCon.rads.data();
983 params.offMeshConDir = m_offMeshCon.dir.data();
984 params.offMeshConAreas = m_offMeshCon.areas.data();
985 params.offMeshConFlags = m_offMeshCon.flags.data();
986 params.offMeshConUserID = m_offMeshCon.userId.data();
987 params.offMeshConCount = offMeshCount();
988 params.walkableHeight = RECAST_AGENT_HEIGHT;
989 params.walkableRadius = RECAST_AGENT_RADIUS;
990 params.walkableClimb = RECAST_AGENT_MAX_CLIMB;
993 params.tileLayer = 0;
994 rcVcopy(params.bmin, pmesh->bmin);
995 rcVcopy(params.bmax, pmesh->bmax);
996 params.cs = RECAST_CELL_SIZE;
997 params.ch = RECAST_CELL_HEIGHT;
998 params.buildBvTree =
true;
1000 if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) {
1001 qCWarning(
Log) <<
"dtCreateNavMeshData failed";
1004 std::unique_ptr<uint8_t> navDataPtr(navData);
1006 result->m_navMesh->removeTile(result->m_navMesh->getTileRefAt(tx, ty, 0), {},
nullptr);
1007 auto status = result->m_navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, {},
nullptr);
1008 if (dtStatusFailed(
status)) {
1009 qCWarning(
Log) <<
"Failed to add tile to dtNavMesh";
1013 (void)navDataPtr.release();
1018NavMesh NavMeshBuilder::navMesh()
const
1020 return d->m_navMesh;
A source for overlay elements, drawn on top of the static map data.
void update()
Trigger map re-rendering when the source changes.
bool hasError() const
Returns true if an error occured during parsing and the returned style is invalid.
Result of MapCSS stylesheet evaluation for a single layer selector.
bool hasClass(ClassSelectorKey cls) const
Check whether this result layer has class cls set.
LayerSelectorKey layerSelector() const
The layer selector for this result.
bool hasLineProperties() const
Returns true if a way/line needs to be drawn.
const MapCSSDeclaration * declaration(MapCSSProperty prop) const
Returns the declaration for property @prop, or nullptr is this property isn't set.
bool hasExtrudeProperties() const
Returns true if a 3D extrusion is requested.
bool hasAreaProperties() const
Returns true if an area/polygon needs to be drawn.
Result of MapCSS stylesheet evaluation for all layer selectors.
void clear()
Reset result state from a previous evaluation, while retaining previously allocated resource for reus...
const std::vector< MapCSSResultLayer > & results() const
Results for all layer selectors.
A parsed MapCSS style sheet.
void evaluate(const MapCSSState &state, MapCSSResult &result) const
Evaluates the style sheet for a given state state (OSM element, view state, element state,...
void initializeState(MapCSSState &state) const
Initializes the evaluation state.
Raw OSM map data, separated by levels.
Compiled nav mesh for routing.
A set of nodes, ways and relations.
const Way * way(Id id) const
Find a way by its id.
A reference to any of OSM::Node/OSM::Way/OSM::Relation.
bool hasTags() const
Returns whether or not this element has any tags set.
std::vector< const Node * > outerPath(const DataSet &dataSet) const
Returns all nodes belonging to the outer path of this element.
Q_SCRIPTABLE CaptureState status()
QString path(const QString &relativePath)
Unit
Unit for geometry sizes.
@ FillOpacity
area fill color
@ Extrude
rounded or rectangular
QStringView level(QStringView ifopt)
int64_t Id
OSM element identifier.
QByteArray tagValue(const Elem &elem, TagKey key)
Returns the tag value for key of elem.
bool isEmpty() const const
QByteArray number(double n, char format, int precision)
qreal length() const const
const_reference at(qsizetype i) const const
bool isEmpty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void setCapStyle(Qt::PenCapStyle style)
void setWidthF(qreal width)
qreal widthF() const const
qsizetype size() const const
QByteArray toUtf8() const const
QChar first() const const
QThread * currentThread()
QThreadPool * globalInstance()
void start(Callable &&callableToRun, int priority)