Cache hit detect indexes and check closest pixels first.
This commit is contained in:
@@ -219,7 +219,7 @@ class ExecutorGroup {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mask = getCircleArray(hitTolerance);
|
const indexes = getPixelIndexArray(hitTolerance);
|
||||||
|
|
||||||
let builderType;
|
let builderType;
|
||||||
|
|
||||||
@@ -231,31 +231,24 @@ class ExecutorGroup {
|
|||||||
function featureCallback(feature, geometry) {
|
function featureCallback(feature, geometry) {
|
||||||
const imageData = context.getImageData(0, 0, contextSize, contextSize)
|
const imageData = context.getImageData(0, 0, contextSize, contextSize)
|
||||||
.data;
|
.data;
|
||||||
for (let i = 0; i < contextSize; i++) {
|
for (let i = 0, ii = indexes.length; i < ii; i++) {
|
||||||
for (let j = 0; j < contextSize; j++) {
|
if (imageData[indexes[i]] > 0) {
|
||||||
if (mask[i][j]) {
|
if (
|
||||||
if (imageData[(j * contextSize + i) * 4 + 3] > 0) {
|
!declutteredFeatures ||
|
||||||
let result;
|
(builderType !== BuilderType.IMAGE &&
|
||||||
if (
|
builderType !== BuilderType.TEXT) ||
|
||||||
!(
|
declutteredFeatures.indexOf(feature) !== -1
|
||||||
declutteredFeatures &&
|
) {
|
||||||
(builderType == BuilderType.IMAGE ||
|
const result = callback(feature, geometry);
|
||||||
builderType == BuilderType.TEXT)
|
if (result) {
|
||||||
) ||
|
return result;
|
||||||
declutteredFeatures.indexOf(feature) !== -1
|
|
||||||
) {
|
|
||||||
result = callback(feature, geometry);
|
|
||||||
}
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
context.clearRect(0, 0, contextSize, contextSize);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
context.clearRect(0, 0, contextSize, contextSize);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {Array<number>} */
|
/** @type {Array<number>} */
|
||||||
@@ -372,78 +365,61 @@ class ExecutorGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This cache is used for storing calculated pixel circles for increasing performance.
|
* This cache is used to store arrays of indexes for calculated pixel circles
|
||||||
|
* to increase performance.
|
||||||
* It is a static property to allow each Replaygroup to access it.
|
* It is a static property to allow each Replaygroup to access it.
|
||||||
* @type {Object<number, Array<Array<(boolean|undefined)>>>}
|
* @type {Object<number, Array<number>>}
|
||||||
*/
|
*/
|
||||||
const circleArrayCache = {
|
const circlePixelIndexArrayCache = {};
|
||||||
0: [[true]],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method fills a row in the array from the given coordinate to the
|
* This methods creates an array with indexes of all pixels within a circle,
|
||||||
* middle with `true`.
|
* ordered by how close they are to the center.
|
||||||
* @param {Array<Array<(boolean|undefined)>>} array The array that will be altered.
|
|
||||||
* @param {number} x X coordinate.
|
|
||||||
* @param {number} y Y coordinate.
|
|
||||||
*/
|
|
||||||
function fillCircleArrayRowToMiddle(array, x, y) {
|
|
||||||
let i;
|
|
||||||
const radius = Math.floor(array.length / 2);
|
|
||||||
if (x >= radius) {
|
|
||||||
for (i = radius; i < x; i++) {
|
|
||||||
array[i][y] = true;
|
|
||||||
}
|
|
||||||
} else if (x < radius) {
|
|
||||||
for (i = x + 1; i < radius; i++) {
|
|
||||||
array[i][y] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This methods creates a circle inside a fitting array. Points inside the
|
|
||||||
* circle are marked by true, points on the outside are undefined.
|
|
||||||
* It uses the midpoint circle algorithm.
|
|
||||||
* A cache is used to increase performance.
|
* A cache is used to increase performance.
|
||||||
* @param {number} radius Radius.
|
* @param {number} radius Radius.
|
||||||
* @returns {Array<Array<(boolean|undefined)>>} An array with marked circle points.
|
* @returns {Array<number>} An array with indexes within a circle.
|
||||||
*/
|
*/
|
||||||
export function getCircleArray(radius) {
|
export function getPixelIndexArray(radius) {
|
||||||
if (circleArrayCache[radius] !== undefined) {
|
if (circlePixelIndexArrayCache[radius] !== undefined) {
|
||||||
return circleArrayCache[radius];
|
return circlePixelIndexArrayCache[radius];
|
||||||
}
|
}
|
||||||
|
|
||||||
const arraySize = radius * 2 + 1;
|
const size = radius * 2 + 1;
|
||||||
const arr = new Array(arraySize);
|
const maxDistanceSq = radius * radius;
|
||||||
for (let i = 0; i < arraySize; i++) {
|
const distances = new Array(maxDistanceSq + 1);
|
||||||
arr[i] = new Array(arraySize);
|
for (let i = 0; i <= radius; ++i) {
|
||||||
}
|
for (let j = 0; j <= radius; ++j) {
|
||||||
|
const distanceSq = i * i + j * j;
|
||||||
let x = radius;
|
if (distanceSq > maxDistanceSq) {
|
||||||
let y = 0;
|
break;
|
||||||
let error = 0;
|
}
|
||||||
|
let distance = distances[distanceSq];
|
||||||
while (x >= y) {
|
if (!distance) {
|
||||||
fillCircleArrayRowToMiddle(arr, radius + x, radius + y);
|
distance = [];
|
||||||
fillCircleArrayRowToMiddle(arr, radius + y, radius + x);
|
distances[distanceSq] = distance;
|
||||||
fillCircleArrayRowToMiddle(arr, radius - y, radius + x);
|
}
|
||||||
fillCircleArrayRowToMiddle(arr, radius - x, radius + y);
|
distance.push(((radius + i) * size + (radius + j)) * 4 + 3);
|
||||||
fillCircleArrayRowToMiddle(arr, radius - x, radius - y);
|
if (i > 0) {
|
||||||
fillCircleArrayRowToMiddle(arr, radius - y, radius - x);
|
distance.push(((radius - i) * size + (radius + j)) * 4 + 3);
|
||||||
fillCircleArrayRowToMiddle(arr, radius + y, radius - x);
|
}
|
||||||
fillCircleArrayRowToMiddle(arr, radius + x, radius - y);
|
if (j > 0) {
|
||||||
|
distance.push(((radius + i) * size + (radius - j)) * 4 + 3);
|
||||||
y++;
|
if (i > 0) {
|
||||||
error += 1 + 2 * y;
|
distance.push(((radius - i) * size + (radius - j)) * 4 + 3);
|
||||||
if (2 * (error - x) + 1 > 0) {
|
}
|
||||||
x -= 1;
|
}
|
||||||
error += 1 - 2 * x;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
circleArrayCache[radius] = arr;
|
const pixelIndex = [];
|
||||||
return arr;
|
for (let i = 0, ii = distances.length; i < ii; ++i) {
|
||||||
|
if (distances[i]) {
|
||||||
|
pixelIndex.push(...distances[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
circlePixelIndexArrayCache[radius] = pixelIndex;
|
||||||
|
return pixelIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ExecutorGroup;
|
export default ExecutorGroup;
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
import {getCircleArray} from '../../../../../src/ol/render/canvas/ExecutorGroup.js';
|
import {getPixelIndexArray} from '../../../../../src/ol/render/canvas/ExecutorGroup.js';
|
||||||
|
|
||||||
describe('ol.render.canvas.ExecutorGroup', function () {
|
describe('ol.render.canvas.ExecutorGroup', function () {
|
||||||
describe('#getCircleArray_', function () {
|
describe('#getPixelIndexArray', function () {
|
||||||
it('creates an array with a pixelated circle marked with true', function () {
|
it('creates an array with every index within distance', function () {
|
||||||
const radius = 10;
|
const radius = 10;
|
||||||
|
const size = radius * 2 + 1;
|
||||||
|
const hitIndexes = getPixelIndexArray(radius);
|
||||||
|
|
||||||
|
const circleArray = new Array(size);
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
circleArray[i] = new Array(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
hitIndexes.forEach(function (d) {
|
||||||
|
const x = ((d - 3) / 4) % size;
|
||||||
|
const y = ((d - 3) / 4 / size) | 0;
|
||||||
|
circleArray[x][y] = true;
|
||||||
|
});
|
||||||
|
|
||||||
const minRadiusSq = Math.pow(radius - Math.SQRT2, 2);
|
const minRadiusSq = Math.pow(radius - Math.SQRT2, 2);
|
||||||
const maxRadiusSq = Math.pow(radius + Math.SQRT2, 2);
|
const maxRadiusSq = Math.pow(radius + Math.SQRT2, 2);
|
||||||
const circleArray = getCircleArray(radius);
|
|
||||||
const size = radius * 2 + 1;
|
|
||||||
expect(circleArray.length).to.be(size);
|
expect(circleArray.length).to.be(size);
|
||||||
|
|
||||||
for (let i = 0; i < size; i++) {
|
for (let i = 0; i < size; i++) {
|
||||||
@@ -24,5 +36,33 @@ describe('ol.render.canvas.ExecutorGroup', function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
it('orders the indexes correctly from closest to farthest away', function () {
|
||||||
|
const radius = 10;
|
||||||
|
const size = radius * 2 + 1;
|
||||||
|
const hitIndexes = getPixelIndexArray(radius);
|
||||||
|
|
||||||
|
// Center first
|
||||||
|
expect(hitIndexes[0]).to.be((size * radius + radius) * 4 + 3);
|
||||||
|
|
||||||
|
// 4 Pixels above/below/left/right of center next
|
||||||
|
const begin = hitIndexes.slice(1, 5);
|
||||||
|
expect(begin).to.contain((radius * size + radius + 1) * 4 + 3);
|
||||||
|
expect(begin).to.contain(((radius + 1) * size + radius) * 4 + 3);
|
||||||
|
expect(begin).to.contain(((radius - 1) * size + radius) * 4 + 3);
|
||||||
|
expect(begin).to.contain((radius * size + radius - 1) * 4 + 3);
|
||||||
|
|
||||||
|
// 4 Pixels in the middle of each side in the last 12 elements (at radius 10)
|
||||||
|
const last = hitIndexes.slice(hitIndexes.length - 12);
|
||||||
|
expect(last).to.contain((0 * size + radius) * 4 + 3);
|
||||||
|
expect(last).to.contain((radius * size + 0) * 4 + 3);
|
||||||
|
expect(last).to.contain((radius * size + size - 1) * 4 + 3);
|
||||||
|
expect(last).to.contain(((size - 1) * size + radius) * 4 + 3);
|
||||||
|
});
|
||||||
|
it('has no duplicate indexes', function () {
|
||||||
|
const radius = 10;
|
||||||
|
const hitIndexes = getPixelIndexArray(radius);
|
||||||
|
|
||||||
|
expect(new Set(hitIndexes).size).to.be(hitIndexes.length);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user