Keep separate atlas for hit-detection images
This commit is contained in:
@@ -8,12 +8,16 @@ goog.require('goog.object');
|
||||
|
||||
|
||||
/**
|
||||
* Provides information for an image inside an atlas.
|
||||
* `offsetX` and `offsetY` are the position of the image inside
|
||||
* Provides information for an image inside an atlas manager.
|
||||
* `offsetX` and `offsetY` is the position of the image inside
|
||||
* the atlas image `image`.
|
||||
* @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement}}
|
||||
* `hitOffsetX` and `hitOffsetY` ist the position of the hit-detection image
|
||||
* inside the hit-detection atlas image `hitImage` (only when a hit-detection
|
||||
* image was created for this image).
|
||||
* @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement,
|
||||
* hitOffsetX: number, hitOffsetY: number, hitImage: HTMLCanvasElement}}
|
||||
*/
|
||||
ol.style.AtlasInfo;
|
||||
ol.style.AtlasManagerInfo;
|
||||
|
||||
|
||||
|
||||
@@ -66,18 +70,52 @@ ol.style.AtlasManager = function(opt_options) {
|
||||
* @type {Array.<ol.style.Atlas>}
|
||||
*/
|
||||
this.atlases_ = [new ol.style.Atlas(this.currentSize_, this.space_)];
|
||||
|
||||
/**
|
||||
* The size in pixels of the latest atlas image for hit-detection images.
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
this.currentHitSize_ = this.currentSize_;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array.<ol.style.Atlas>}
|
||||
*/
|
||||
this.hitAtlases_ = [new ol.style.Atlas(this.currentHitSize_, this.space_)];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} id The identifier of the entry to check.
|
||||
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
|
||||
* or `null` if the entry is not part of the atlas manager.
|
||||
* @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
|
||||
* entry, or `null` if the entry is not part of the atlas manager.
|
||||
*/
|
||||
ol.style.AtlasManager.prototype.getInfo = function(id) {
|
||||
/** @type {?ol.style.AtlasInfo} */
|
||||
var info = this.getInfo_(this.atlases_, id);
|
||||
|
||||
if (info === null) {
|
||||
return null;
|
||||
}
|
||||
/** @type {?ol.style.AtlasInfo} */
|
||||
var hitInfo = this.getInfo_(this.hitAtlases_, id);
|
||||
|
||||
return this.mergeInfos_(info, hitInfo);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Array.<ol.style.Atlas>} atlases The atlases to search.
|
||||
* @param {string} id The identifier of the entry to check.
|
||||
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
|
||||
* or `null` if the entry is not part of the atlases.
|
||||
*/
|
||||
ol.style.AtlasManager.prototype.getInfo_ = function(atlases, id) {
|
||||
var atlas, info, i, ii;
|
||||
for (i = 0, ii = this.atlases_.length; i < ii; ++i) {
|
||||
atlas = this.atlases_[i];
|
||||
for (i = 0, ii = atlases.length; i < ii; ++i) {
|
||||
atlas = atlases[i];
|
||||
info = atlas.get(id);
|
||||
if (info !== null) {
|
||||
return info;
|
||||
@@ -87,41 +125,109 @@ ol.style.AtlasManager.prototype.getInfo = function(id) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {ol.style.AtlasInfo} info The info for the real image.
|
||||
* @param {?ol.style.AtlasInfo} hitInfo The info for the hit-detection
|
||||
* image.
|
||||
* @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
|
||||
* entry, or `null` if the entry is not part of the atlases.
|
||||
*/
|
||||
ol.style.AtlasManager.prototype.mergeInfos_ = function(info, hitInfo) {
|
||||
return /** @type {ol.style.AtlasManagerInfo} */ ({
|
||||
offsetX: info.offsetX,
|
||||
offsetY: info.offsetY,
|
||||
image: info.image,
|
||||
hitOffsetX: (hitInfo === null) ? undefined : hitInfo.offsetX,
|
||||
hitOffsetY: (hitInfo === null) ? undefined : hitInfo.offsetY,
|
||||
hitImage: (hitInfo === null) ? undefined : hitInfo.image
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add an image to the atlas manager.
|
||||
*
|
||||
* If an entry for the given id already exists, the entry will
|
||||
* be overridden (but the space on the atlas graphic will not be freed).
|
||||
*
|
||||
* If `renderHitCallback` is provided, the image (or the hit-detection version
|
||||
* of the image) will be rendered into a separate hit-detection atlas image.
|
||||
*
|
||||
* @param {string} id The identifier of the entry to add.
|
||||
* @param {number} width The width.
|
||||
* @param {number} height The height.
|
||||
* @param {function(CanvasRenderingContext2D, number, number)} renderCallback
|
||||
* Called to render the new image onto an atlas image.
|
||||
* @param {function(CanvasRenderingContext2D, number, number)=}
|
||||
* opt_renderHitCallback Called to render a hit-detection image onto a hit
|
||||
* detection atlas image.
|
||||
* @param {Object=} opt_this Value to use as `this` when executing
|
||||
* `renderCallback`.
|
||||
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
|
||||
* or `null` if the image is too big.
|
||||
* `renderCallback` and `renderHitCallback`.
|
||||
* @return {?ol.style.AtlasManagerInfo} The position and atlas image for the
|
||||
* entry, or `null` if the image is too big.
|
||||
*/
|
||||
ol.style.AtlasManager.prototype.add =
|
||||
function(id, width, height, renderCallback, opt_this) {
|
||||
function(id, width, height,
|
||||
renderCallback, opt_renderHitCallback, opt_this) {
|
||||
if (width + this.space_ > this.maxSize_ ||
|
||||
height + this.space_ > this.maxSize_) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @type {?ol.style.AtlasInfo} */
|
||||
var info = this.add_(false,
|
||||
id, width, height, renderCallback, opt_this);
|
||||
if (info === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @type {?ol.style.AtlasInfo} */
|
||||
var hitInfo = null;
|
||||
if (opt_renderHitCallback !== undefined) {
|
||||
hitInfo = this.add_(true,
|
||||
id, width, height, opt_renderHitCallback, opt_this);
|
||||
}
|
||||
return this.mergeInfos_(info, hitInfo);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {boolean} isHitAtlas If the hit-detection atlases are used.
|
||||
* @param {string} id The identifier of the entry to add.
|
||||
* @param {number} width The width.
|
||||
* @param {number} height The height.
|
||||
* @param {function(CanvasRenderingContext2D, number, number)} renderCallback
|
||||
* Called to render the new image onto an atlas image.
|
||||
* @param {Object=} opt_this Value to use as `this` when executing
|
||||
* `renderCallback` and `renderHitCallback`.
|
||||
* @return {?ol.style.AtlasInfo} The position and atlas image for the entry,
|
||||
* or `null` if the image is too big.
|
||||
*/
|
||||
ol.style.AtlasManager.prototype.add_ =
|
||||
function(isHitAtlas, id, width, height,
|
||||
renderCallback, opt_this) {
|
||||
var atlases = (isHitAtlas) ? this.hitAtlases_ : this.atlases_;
|
||||
var atlas, info, i, ii;
|
||||
for (i = 0, ii = this.atlases_.length; i < ii; ++i) {
|
||||
atlas = this.atlases_[i];
|
||||
for (i = 0, ii = atlases.length; i < ii; ++i) {
|
||||
atlas = atlases[i];
|
||||
info = atlas.add(id, width, height, renderCallback, opt_this);
|
||||
if (info !== null) {
|
||||
return info;
|
||||
} else if (info === null && i === ii - 1) {
|
||||
// the entry could not be added to one of the existing atlases,
|
||||
// create a new atlas that is twice as big and try to add to this one.
|
||||
this.currentSize_ = Math.min(this.currentSize_ * 2, this.maxSize_);
|
||||
atlas = new ol.style.Atlas(this.currentSize_, this.space_);
|
||||
this.atlases_.push(atlas);
|
||||
var size;
|
||||
if (isHitAtlas) {
|
||||
size = Math.min(this.currentHitSize_ * 2, this.maxSize_);
|
||||
this.currentHitSize_ = size;
|
||||
} else {
|
||||
size = Math.min(this.currentSize_ * 2, this.maxSize_);
|
||||
this.currentSize_ = size;
|
||||
}
|
||||
atlas = new ol.style.Atlas(size, this.space_);
|
||||
atlases.push(atlas);
|
||||
// run the loop another time
|
||||
++ii;
|
||||
}
|
||||
@@ -130,6 +236,15 @@ ol.style.AtlasManager.prototype.add =
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Provides information for an image inside an atlas.
|
||||
* `offsetX` and `offsetY` are the position of the image inside
|
||||
* the atlas image `image`.
|
||||
* @typedef {{offsetX: number, offsetY: number, image: HTMLCanvasElement}}
|
||||
*/
|
||||
ol.style.AtlasInfo;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This class facilitates the creation of image atlases.
|
||||
|
||||
@@ -189,46 +189,73 @@ describe('ol.style.AtlasManager', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
var info = manager.add('1', 32, 32, defaultRender);
|
||||
|
||||
expect(info).to.eql(
|
||||
{offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_});
|
||||
expect(info).to.eql({
|
||||
offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_,
|
||||
hitOffsetX: undefined, hitOffsetY: undefined, hitImage: undefined});
|
||||
|
||||
expect(manager.getInfo('1')).to.eql(info);
|
||||
});
|
||||
|
||||
it('adds one entry (also to the hit detection atlas)', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
var info = manager.add('1', 32, 32, defaultRender, defaultRender);
|
||||
|
||||
expect(info).to.eql({
|
||||
offsetX: 1, offsetY: 1, image: manager.atlases_[0].canvas_,
|
||||
hitOffsetX: 1, hitOffsetY: 1,
|
||||
hitImage: manager.hitAtlases_[0].canvas_});
|
||||
|
||||
expect(manager.getInfo('1')).to.eql(info);
|
||||
});
|
||||
|
||||
it('creates a new atlas if needed', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
expect(manager.add('1', 100, 100, defaultRender)).to.be.ok();
|
||||
var info = manager.add('2', 100, 100, defaultRender);
|
||||
expect(manager.add('1', 100, 100, defaultRender, defaultRender))
|
||||
.to.be.ok();
|
||||
var info = manager.add('2', 100, 100, defaultRender, defaultRender);
|
||||
expect(info).to.be.ok();
|
||||
expect(info.image.width).to.eql(256);
|
||||
expect(manager.atlases_).to.have.length(2);
|
||||
expect(info.hitImage.width).to.eql(256);
|
||||
expect(manager.hitAtlases_).to.have.length(2);
|
||||
});
|
||||
|
||||
it('creates new atlases until one is large enough', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
expect(manager.add('1', 100, 100, defaultRender)).to.be.ok();
|
||||
expect(manager.add('1', 100, 100, defaultRender, defaultRender))
|
||||
.to.be.ok();
|
||||
expect(manager.atlases_).to.have.length(1);
|
||||
var info = manager.add('2', 500, 500, defaultRender);
|
||||
expect(manager.hitAtlases_).to.have.length(1);
|
||||
var info = manager.add('2', 500, 500, defaultRender, defaultRender);
|
||||
expect(info).to.be.ok();
|
||||
expect(info.image.width).to.eql(512);
|
||||
expect(manager.atlases_).to.have.length(3);
|
||||
expect(info.hitImage.width).to.eql(512);
|
||||
expect(manager.hitAtlases_).to.have.length(3);
|
||||
});
|
||||
|
||||
it('checks all existing atlases and create a new if needed', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
expect(manager.add('1', 100, 100, defaultRender)).to.be.ok();
|
||||
expect(manager.add('2', 100, 100, defaultRender)).to.be.ok();
|
||||
expect(manager.add('1', 100, 100, defaultRender, defaultRender))
|
||||
.to.be.ok();
|
||||
expect(manager.add('2', 100, 100, defaultRender, defaultRender))
|
||||
.to.be.ok();
|
||||
expect(manager.atlases_).to.have.length(2);
|
||||
var info = manager.add(3, 500, 500, defaultRender);
|
||||
expect(manager.hitAtlases_).to.have.length(2);
|
||||
var info = manager.add(3, 500, 500, defaultRender, defaultRender);
|
||||
expect(info).to.be.ok();
|
||||
expect(info.image.width).to.eql(512);
|
||||
expect(manager.atlases_).to.have.length(3);
|
||||
expect(info.hitImage.width).to.eql(512);
|
||||
expect(manager.hitAtlases_).to.have.length(3);
|
||||
});
|
||||
|
||||
it('returns null if the size exceeds the maximum size', function() {
|
||||
var manager = new ol.style.AtlasManager({size: 128});
|
||||
expect(manager.add('1', 100, 100, defaultRender)).to.be.ok();
|
||||
expect(manager.add('2', 2048, 2048, defaultRender)).to.eql(null);
|
||||
expect(manager.add('1', 100, 100, defaultRender, defaultRender))
|
||||
.to.be.ok();
|
||||
expect(manager.add('2', 2048, 2048, defaultRender, defaultRender))
|
||||
.to.eql(null);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user