Skip to content

Commit

Permalink
Merge pull request #579 from openmobilemaps/feature/improved-persisti…
Browse files Browse the repository at this point in the history
…ng-placement

Improved Persisting Placement
  • Loading branch information
maurhofer-ubique authored Jan 24, 2024
2 parents c185fae + 33b4ea9 commit 88d6458
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 83 deletions.
162 changes: 119 additions & 43 deletions shared/public/CollisionGrid.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@ struct IndexRange {

class CollisionGrid {
public:
CollisionGrid(const std::vector<float> &vpMatrix, const Vec2I &size, float gridAngle)
CollisionGrid(const std::vector<float> &vpMatrix, const Vec2I &size, float gridAngle, bool alwaysInsert)
: vpMatrix(vpMatrix), size(size),
sinNegGridAngle(std::sin(-gridAngle * M_PI / 180.0)),
cosNegGridAngle(std::cos(-gridAngle * M_PI / 180.0)) {
cosNegGridAngle(std::cos(-gridAngle * M_PI / 180.0)),
alwaysInsert(alwaysInsert) {
cellSize = std::min(size.x, size.y) / (float) numCellsMinDim;
numCellsX = std::ceil(size.x / cellSize) + 2 * numCellsPadding;
numCellsY = std::ceil(size.y / cellSize) + 2 * numCellsPadding;
Expand Down Expand Up @@ -98,47 +99,26 @@ class CollisionGrid {
}
}
}
for (int16_t y = indexRange.yMin; y <= indexRange.yMax; y++) {
for (int16_t x = indexRange.xMin; x <= indexRange.xMax; x++) {
for (const auto &rect : gridRects[y][x]) {
if (CollisionUtil::checkRectCollision(projectedRectangle, rect)) {
return 1;
}
}
for (const auto &circle : gridCircles[y][x]) {
if (CollisionUtil::checkRectCircleCollision(projectedRectangle, circle)) {
return 1;
}
}

}
if (alwaysInsert) {
return checkRectInsertAlways(rectangle, projectedRectangle, indexRange);
} else {
return checkRectInsertOnCollision(rectangle, projectedRectangle, indexRange);
}

// Only insert, when not colliding
for (int16_t y = indexRange.yMin; y <= indexRange.yMax; y++) {
for (int16_t x = indexRange.xMin; x <= indexRange.xMax; x++) {
gridRects[y][x].push_back(projectedRectangle);
}
}

if (rectangle.contentHash != 0 && rectangle.symbolSpacing > 0) {
spacedRects[rectangle.contentHash].push_back(projectedRectangle);
}
return 0;
}

/**
* Add a vector of circles (which are then projected with the provided vpMatrix) and receive the feedback, if they have collided
* with the previous content of the grid. Assumed to remain circles in the projected space. Only added, when not colliding!
*/
bool addAndCheckCollisionCircles(const std::vector<CollisionCircleF> &circles) {
uint8_t addAndCheckCollisionCircles(const std::vector<CollisionCircleF> &circles) {
if (circles.empty()) {
// No circles -> no collision
return false;
// No circles -> no colliding
return 0;
}

std::vector<std::tuple<CircleF, IndexRange, size_t, int16_t>> projectedCircles;
for (const auto &circle : circles) {
for (const auto &circle: circles) {
auto projectedCircle = getProjectedCircle(circle);
IndexRange indexRange = getIndexRangeForCircle(projectedCircle);
if (indexRange.isValid(numCellsX - 1, numCellsY - 1)) {
Expand All @@ -148,50 +128,116 @@ class CollisionGrid {

if (projectedCircles.empty()) {
// no valid IndexRanges
return true;
return 2;
}

for (const auto &[projectedCircle, indexRange, contentHash, symbolSpacing] : projectedCircles) {
for (const auto &[projectedCircle, indexRange, contentHash, symbolSpacing]: projectedCircles) {

if (contentHash != 0 && symbolSpacing > 0) {
const auto &equalRects = spacedRects.find(contentHash);
if (equalRects != spacedRects.end()) {
for (const auto &other : equalRects->second) {
for (const auto &other: equalRects->second) {
// Assume equal symbol spacing for all primitives with matching content
if (CollisionUtil::checkRectCircleCollision(other, projectedCircle, symbolSpacing)) {
return true;
return 1;

}
}
}
const auto &equalCircles = spacedCircles.find(contentHash);
if (equalCircles != spacedCircles.end()) {
for (const auto &other : equalCircles->second) {
for (const auto &other: equalCircles->second) {
// Assume equal symbol spacing for all primitives with matching content
if (CollisionUtil::checkCircleCollision(projectedCircle, other, symbolSpacing)) {
return true;
return 1;
}
}
}
}
}

if (alwaysInsert) {
return checkCirclesInsertAlways(circles, projectedCircles);
} else {
return checkCirclesInsertOnCollision(circles, projectedCircles);
}
}

private:
uint8_t checkRectInsertOnCollision(const CollisionRectF &rectangle, const RectF &projectedRectangle, const IndexRange &indexRange) {
for (int16_t y = indexRange.yMin; y <= indexRange.yMax; y++) {
for (int16_t x = indexRange.xMin; x <= indexRange.xMax; x++) {
for (const auto &rect: gridRects[y][x]) {
if (CollisionUtil::checkRectCollision(projectedRectangle, rect)) {
return 1;
}
}
for (const auto &circle: gridCircles[y][x]) {
if (CollisionUtil::checkRectCircleCollision(projectedRectangle, circle)) {
return 1;
}
}
}
}

for (int16_t y = indexRange.yMin; y <= indexRange.yMax; y++) {
for (int16_t x = indexRange.xMin; x <= indexRange.xMax; x++) {
gridRects[y][x].push_back(projectedRectangle);
}
}
if (rectangle.contentHash != 0 && rectangle.symbolSpacing > 0) {
spacedRects[rectangle.contentHash].push_back(projectedRectangle);
}

return 0;
}

uint8_t checkRectInsertAlways(const CollisionRectF &rectangle, const RectF &projectedRectangle, const IndexRange &indexRange) {
bool colliding = false;
for (int16_t y = indexRange.yMin; y <= indexRange.yMax; y++) {
for (int16_t x = indexRange.xMin; x <= indexRange.xMax; x++) {
if (!colliding) {
for (const auto &rect: gridRects[y][x]) {
if (CollisionUtil::checkRectCollision(projectedRectangle, rect)) {
colliding = true;
}
}
for (const auto &circle: gridCircles[y][x]) {
if (CollisionUtil::checkRectCircleCollision(projectedRectangle, circle)) {
colliding = true;
}
}
}
gridRects[y][x].push_back(projectedRectangle);
}
}

if (rectangle.contentHash != 0 && rectangle.symbolSpacing > 0) {
spacedRects[rectangle.contentHash].push_back(projectedRectangle);
}

return colliding ? 1 : 0;
}

uint8_t checkCirclesInsertOnCollision(const std::vector<CollisionCircleF> &circles, const std::vector<std::tuple<CircleF, IndexRange, size_t, int16_t>> &projectedCircles) {
for (const auto &[projectedCircle, indexRange, contentHash, symbolSpacing] : projectedCircles) {
for (int16_t y = indexRange.yMin; y <= indexRange.yMax; y++) {
for (int16_t x = indexRange.xMin; x <= indexRange.xMax; x++) {
for (const auto &rect : gridRects[y][x]) {
if (CollisionUtil::checkRectCircleCollision(rect, projectedCircle)) {
return true;
return 1;
}
}
for (const auto &circle : gridCircles[y][x]) {
if (CollisionUtil::checkCircleCollision(projectedCircle, circle)) {
return true;
return 1;
}
}
}
}
}

// Only insert when none colliding
for (const auto &[projectedCircle, indexRange, contentHash, symbolSpacing] : projectedCircles) {
for (const auto &[projectedCircle, indexRange, contentHash, symbolSpacing]: projectedCircles) {
for (int16_t y = indexRange.yMin; y <= indexRange.yMax; y++) {
for (int16_t x = indexRange.xMin; x <= indexRange.xMax; x++) {
gridCircles[y][x].push_back(projectedCircle);
Expand All @@ -201,10 +247,38 @@ class CollisionGrid {
spacedCircles[contentHash].push_back(projectedCircle);
}
}
return false;

return 0;
}

uint8_t checkCirclesInsertAlways(const std::vector<CollisionCircleF> &circles, const std::vector<std::tuple<CircleF, IndexRange, size_t, int16_t>> &projectedCircles) {
bool colliding = false;
for (const auto &[projectedCircle, indexRange, contentHash, symbolSpacing]: projectedCircles) {
for (int16_t y = indexRange.yMin; y <= indexRange.yMax; y++) {
for (int16_t x = indexRange.xMin; x <= indexRange.xMax; x++) {
if (!colliding) {
for (const auto &rect: gridRects[y][x]) {
if (CollisionUtil::checkRectCircleCollision(rect, projectedCircle)) {
colliding = true;
}
}
for (const auto &circle: gridCircles[y][x]) {
if (CollisionUtil::checkCircleCollision(projectedCircle, circle)) {
colliding = true;
}
}
}
gridCircles[y][x].push_back(projectedCircle);
}
}
if (contentHash != 0 && symbolSpacing > 0) {
spacedCircles[contentHash].push_back(projectedCircle);
}
}

return colliding ? 1 : 0;
}

private:
RectF getProjectedRectangle(const CollisionRectF &rectangle) {
temp2[0] = rectangle.x - rectangle.anchorX; // move x to the anchor
temp2[1] = rectangle.y - rectangle.anchorY;
Expand Down Expand Up @@ -304,5 +378,7 @@ class CollisionGrid {
std::unordered_map<size_t, std::vector<RectF>> spacedRects;
std::unordered_map<size_t, std::vector<CircleF>> spacedCircles;

bool alwaysInsert = false;

std::vector<float> temp1 = {0, 0, 0, 0}, temp2 = {0, 0, 0, 0};
};
9 changes: 6 additions & 3 deletions shared/src/map/layers/tiled/vector/Tiled2dMapVectorLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,9 +561,12 @@ void Tiled2dMapVectorLayer::update() {
if (now - lastCollitionCheck > 3000 || tilesChanged) {
lastCollitionCheck = now;
bool enforceUpdate = !prevCollisionStillValid.test_and_set();
collisionManager.syncAccess([&vpMatrix, &viewportSize, viewportRotation, enforceUpdate](const auto &manager) {
manager->collisionDetection(*vpMatrix, viewportSize, viewportRotation, enforceUpdate);
});
collisionManager.syncAccess(
[&vpMatrix, &viewportSize, viewportRotation, enforceUpdate, persistingPlacement = mapDescription->persistingSymbolPlacement](
const auto &manager) {
manager->collisionDetection(*vpMatrix, viewportSize, viewportRotation, enforceUpdate,
persistingPlacement);
});
isAnimating = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,6 @@ class SymbolObjectCollisionWrapper {
}

bool operator<(const SymbolObjectCollisionWrapper &o) const {
if (isColliding != o.isColliding) {
return isColliding;
}
if (symbolObject->smallestVisibleZoom != o.symbolObject->smallestVisibleZoom) {
return symbolObject->smallestVisibleZoom > o.symbolObject->smallestVisibleZoom;
}
if (symbolSortKey == o.symbolSortKey) {
return symbolTileIndex > o.symbolTileIndex;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include "Tiled2dMapVectorSourceSymbolCollisionManager.h"
#include "CollisionGrid.h"

void Tiled2dMapVectorSourceSymbolCollisionManager::collisionDetection(const std::vector<float> &vpMatrix, const Vec2I &viewportSize, float viewportRotation, bool enforceRecomputation) {
void Tiled2dMapVectorSourceSymbolCollisionManager::collisionDetection(const std::vector<float> &vpMatrix, const Vec2I &viewportSize, float viewportRotation, bool enforceRecomputation, bool persistingPlacement) {
std::vector<std::string> layers;
std::string currentSource;

Expand All @@ -21,7 +21,7 @@ void Tiled2dMapVectorSourceSymbolCollisionManager::collisionDetection(const std:
}
lastVpMatrix = vpMatrix;

auto collisionGrid = std::make_shared<CollisionGrid>(vpMatrix, viewportSize, viewportRotation);
auto collisionGrid = std::make_shared<CollisionGrid>(vpMatrix, viewportSize, viewportRotation, persistingPlacement);

const auto lambda = [&collisionGrid, &layers](auto manager){
if (auto strongManager = manager.lock()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Tiled2dMapVectorSourceSymbolCollisionManager: public ActorObject {
public:
Tiled2dMapVectorSourceSymbolCollisionManager(const std::unordered_map<std::string, WeakActor<Tiled2dMapVectorSourceSymbolDataManager>> &symbolSourceDataManagers,
std::shared_ptr<VectorMapDescription> mapDescription): symbolSourceDataManagers(symbolSourceDataManagers), mapDescription(mapDescription) {};
void collisionDetection(const std::vector<float> &vpMatrix, const Vec2I &viewportSize, float viewportRotation, bool enforceRecomputation);
void collisionDetection(const std::vector<float> &vpMatrix, const Vec2I &viewportSize, float viewportRotation, bool enforceRecomputation, bool persistingPlacement);
private:
std::unordered_map<std::string, WeakActor<Tiled2dMapVectorSourceSymbolDataManager>> symbolSourceDataManagers;
std::shared_ptr<VectorMapDescription> mapDescription;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -639,21 +639,9 @@ void Tiled2dMapVectorSourceSymbolDataManager::collisionDetection(std::vector<std
const auto objectsIt = symbolGroupsMap.find(layerIdentifier);
if (objectsIt != symbolGroupsMap.end()) {
for (auto &symbolGroup: std::get<1>(objectsIt->second)) {
symbolGroup.syncAccess([&allObjects, zoomIdentifier, persistingPlacement = persistingSymbolPlacement](auto group){
const auto &objects = group->getSymbolObjectsForCollision();
if (persistingPlacement) {
for (auto &object : objects) {
if (object.symbolObject->largestCollisionZoom == -1 || object.symbolObject->largestCollisionZoom < zoomIdentifier) {
allObjects.push_back(object);
}
else {
object.symbolObject->setHideFromCollision(true);
}
}
} else {
for(auto& o : objects) {
allObjects.push_back(o);
}
symbolGroup.syncAccess([&allObjects](auto group){
for(auto& o : group->getSymbolObjectsForCollision()) {
allObjects.push_back(o);
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -873,18 +873,12 @@ void Tiled2dMapVectorSymbolObject::collisionDetection(const double zoomIdentifie
std::optional<std::vector<CollisionCircleF>> boundingCircles = getMapAlignedBoundingCircles(zoomIdentifier, textSymbolPlacement != TextSymbolPlacement::POINT, true);
// Collide, if no valid boundingCircles
if (boundingCircles.has_value()) {
willCollide = collisionGrid->addAndCheckCollisionCircles(*boundingCircles);
auto check = collisionGrid->addAndCheckCollisionCircles(*boundingCircles);
willCollide = check == 1;
outside = check == 2;
} else {
willCollide = false;
}
outside = false;
}

if (persistingSymbolPlacement) {
if (!willCollide && !outside && zoomIdentifier < smallestVisibleZoom) {
smallestVisibleZoom = zoomIdentifier;
} else if (willCollide && (largestCollisionZoom == -1 || zoomIdentifier > largestCollisionZoom)) {
largestCollisionZoom = zoomIdentifier;
outside = false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,6 @@ class Tiled2dMapVectorSymbolObject {
int customTextureOffset = 0;
std::string stringIdentifier;

double smallestVisibleZoom = 999;
double largestCollisionZoom = -1;

private:
double lastZoomEvaluation = -1;
void evaluateStyleProperties(const double zoomIdentifier);
Expand Down

0 comments on commit 88d6458

Please sign in to comment.