From b1cb5a076b96583309c705c6eb7cc9c275b394e3 Mon Sep 17 00:00:00 2001 From: Bruno de Oliveira Abinader Date: Tue, 25 Oct 2016 16:28:28 +0300 Subject: [PATCH] Reuse last placement data in CollisionTile.queryRenderedSymbols() Port of https://github.com/mapbox/mapbox-gl-native/pull/6773. --- js/data/feature_index.js | 2 +- js/symbol/collision_tile.js | 112 +++++++++++++++++++++++------------- 2 files changed, 74 insertions(+), 40 deletions(-) diff --git a/js/data/feature_index.js b/js/data/feature_index.js index aef06fb6bf1..74787ac4abf 100644 --- a/js/data/feature_index.js +++ b/js/data/feature_index.js @@ -146,7 +146,7 @@ class FeatureIndex { matching.sort(topDownFeatureComparator); this.filterMatching(result, matching, this.featureIndexArray, queryGeometry, filter, params.layers, styleLayers, args.bearing, pixelsToTileUnits); - const matchingSymbols = this.collisionTile.queryRenderedSymbols(minX, minY, maxX, maxY, args.scale); + const matchingSymbols = this.collisionTile.queryRenderedSymbols(queryGeometry, args.scale); matchingSymbols.sort(); this.filterMatching(result, matchingSymbols, this.collisionTile.collisionBoxArray, queryGeometry, filter, params.layers, styleLayers, args.bearing, pixelsToTileUnits); diff --git a/js/symbol/collision_tile.js b/js/symbol/collision_tile.js index fd97a753144..1741c035a61 100644 --- a/js/symbol/collision_tile.js +++ b/js/symbol/collision_tile.js @@ -4,6 +4,40 @@ const Point = require('point-geometry'); const EXTENT = require('../data/extent'); const Grid = require('grid-index'); +const intersectionTests = require('../symbol/intersection_tests'); + +// Predicate for ruling out already seen features. +function seenFeature(sourceLayerFeatures, blocking) { + const sourceLayer = blocking.sourceLayerIndex; + const featureIndex = blocking.featureIndex; + if (sourceLayerFeatures[sourceLayer] === undefined) { + sourceLayerFeatures[sourceLayer] = {}; + } + return sourceLayerFeatures[sourceLayer][featureIndex] === true; +} + +// Check if feature is rendered (collision free) at current scale. +function visibleAtScale(blocking, roundedScale) { + return roundedScale >= blocking.placementScale && roundedScale <= blocking.maxScale; +} + +// Check if query polygon intersects with the feature box at current +// scale. +function intersectsAtScale(rotationMatrix, rotatedQuery, blocking, scale, yStretch) { + const anchor = blocking.anchorPoint.matMult(rotationMatrix); + const x1 = anchor.x + blocking.x1 / scale; + const y1 = anchor.y + blocking.y1 / scale * yStretch; + const x2 = anchor.x + blocking.x2 / scale; + const y2 = anchor.y + blocking.y2 / scale * yStretch; + const bbox = [ + new Point(x1, y1), + new Point(x2, y1), + new Point(x2, y2), + new Point(x1, y2) + ]; + return intersectionTests.polygonIntersectsPolygon(rotatedQuery, bbox); +} + /** * A collision tile used to prevent symbols from overlapping. It keep tracks of * where previous symbols have been placed and is used to check if a new @@ -25,7 +59,7 @@ class CollisionTile { this.ignoredGrid = new Grid(EXTENT, 12, 0); } - this.minScale = 0.25; + this.minScale = 0.5; this.maxScale = 2; this.angle = angle; @@ -173,56 +207,56 @@ class CollisionTile { return minPlacementScale; } - queryRenderedSymbols(minX, minY, maxX, maxY, scale) { + queryRenderedSymbols(queryGeometry, scale) { const sourceLayerFeatures = {}; const result = []; + if (queryGeometry.length === 0 || (this.grid.length === 0 && this.ignoredGrid.length === 0)) { + return result; + } + const collisionBoxArray = this.collisionBoxArray; const rotationMatrix = this.rotationMatrix; - const anchorPoint = new Point(minX, minY)._matMult(rotationMatrix); - - const queryBox = this.tempCollisionBox; - queryBox.anchorX = anchorPoint.x; - queryBox.anchorY = anchorPoint.y; - queryBox.x1 = 0; - queryBox.y1 = 0; - queryBox.x2 = maxX - minX; - queryBox.y2 = maxY - minY; - queryBox.maxScale = scale; - - // maxScale is stored using a Float32. Convert `scale` to the stored Float32 value. - scale = queryBox.maxScale; - - const searchBox = [ - anchorPoint.x + queryBox.x1 / scale, - anchorPoint.y + queryBox.y1 / scale * this.yStretch, - anchorPoint.x + queryBox.x2 / scale, - anchorPoint.y + queryBox.y2 / scale * this.yStretch - ]; - const blockingBoxKeys = this.grid.query(searchBox[0], searchBox[1], searchBox[2], searchBox[3]); - const blockingBoxKeys2 = this.ignoredGrid.query(searchBox[0], searchBox[1], searchBox[2], searchBox[3]); - for (let k = 0; k < blockingBoxKeys2.length; k++) { - blockingBoxKeys.push(blockingBoxKeys2[k]); + // Generate a rotated geometry out of the original query geometry. + // Scale has already been handled by the prior conversions. + const rotatedQuery = []; + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + for (let i = 0; i < queryGeometry.length; i++) { + const ring = queryGeometry[i]; + for (let k = 0; k < ring.length; k++) { + const p = ring[k].matMult(rotationMatrix); + minX = Math.min(minX, p.x); + minY = Math.min(minY, p.y); + maxX = Math.max(maxX, p.x); + maxY = Math.max(maxY, p.y); + rotatedQuery.push(p); + } + } + + const features = this.grid.query(minX, minY, maxX, maxY); + const ignoredFeatures = this.ignoredGrid.query(minX, minY, maxX, maxY); + for (let i = 0; i < ignoredFeatures.length; i++) { + features.push(ignoredFeatures[i]); } - for (let i = 0; i < blockingBoxKeys.length; i++) { - const blocking = collisionBoxArray.get(blockingBoxKeys[i]); + // Account for the rounding done when updating symbol shader variables. + const roundedScale = Math.pow(2, Math.ceil(Math.log(scale) / Math.LN2 * 10) / 10); + for (let i = 0; i < features.length; i++) { + const blocking = collisionBoxArray.get(features[i]); const sourceLayer = blocking.sourceLayerIndex; const featureIndex = blocking.featureIndex; - if (sourceLayerFeatures[sourceLayer] === undefined) { - sourceLayerFeatures[sourceLayer] = {}; - } - if (!sourceLayerFeatures[sourceLayer][featureIndex]) { - const blockingAnchorPoint = blocking.anchorPoint.matMult(rotationMatrix); - const minPlacementScale = this.getPlacementScale(this.minScale, anchorPoint, queryBox, blockingAnchorPoint, blocking); - if (minPlacementScale >= scale) { - sourceLayerFeatures[sourceLayer][featureIndex] = true; - result.push(blockingBoxKeys[i]); - } - } + if (seenFeature(sourceLayerFeatures, blocking)) continue; + if (!visibleAtScale(blocking, roundedScale)) continue; + if (!intersectsAtScale(rotatedQuery, blocking, scale, this.yStretch)) continue; + + sourceLayerFeatures[sourceLayer][featureIndex] = true; + result.push(features[i]); } return result;