1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181 | 1×
1×
1×
72×
72×
72×
72×
72×
1×
80×
1×
231×
231×
600×
600×
214×
214×
214×
214×
214×
17×
1×
214×
214×
214×
214×
214×
127×
127×
127×
87×
87×
87×
1×
214×
214×
189×
214×
127×
214×
| goog.provide('ol.style.Atlas');
goog.require('ol.dom');
/**
* This class facilitates the creation of image atlases.
*
* Images added to an atlas will be rendered onto a single
* atlas canvas. The distribution of images on the canvas is
* managed with the bin packing algorithm described in:
* http://www.blackpawn.com/texts/lightmaps/
*
* @constructor
* @struct
* @param {number} size The size in pixels of the sprite image.
* @param {number} space The space in pixels between images.
* Because texture coordinates are float values, the edges of
* images might not be completely correct (in a way that the
* edges overlap when being rendered). To avoid this we add a
* padding around each image.
*/
ol.style.Atlas = function(size, space) {
/**
* @private
* @type {number}
*/
this.space_ = space;
/**
* @private
* @type {Array.<ol.AtlasBlock>}
*/
this.emptyBlocks_ = [{x: 0, y: 0, width: size, height: size}];
/**
* @private
* @type {Object.<string, ol.AtlasInfo>}
*/
this.entries_ = {};
/**
* @private
* @type {CanvasRenderingContext2D}
*/
this.context_ = ol.dom.createCanvasContext2D(size, size);
/**
* @private
* @type {HTMLCanvasElement}
*/
this.canvas_ = this.context_.canvas;
};
/**
* @param {string} id The identifier of the entry to check.
* @return {?ol.AtlasInfo} The atlas info.
*/
ol.style.Atlas.prototype.get = function(id) {
return this.entries_[id] || null;
};
/**
* @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`.
* @return {?ol.AtlasInfo} The position and atlas image for the entry.
*/
ol.style.Atlas.prototype.add = function(id, width, height, renderCallback, opt_this) {
var block, i, ii;
for (i = 0, ii = this.emptyBlocks_.length; i < ii; ++i) {
block = this.emptyBlocks_[i];
if (block.width >= width + this.space_ &&
block.height >= height + this.space_) {
// we found a block that is big enough for our entry
var entry = {
offsetX: block.x + this.space_,
offsetY: block.y + this.space_,
image: this.canvas_
};
this.entries_[id] = entry;
// render the image on the atlas image
renderCallback.call(opt_this, this.context_,
block.x + this.space_, block.y + this.space_);
// split the block after the insertion, either horizontally or vertically
this.split_(i, block, width + this.space_, height + this.space_);
return entry;
}
}
// there is no space for the new entry in this atlas
return null;
};
/**
* @private
* @param {number} index The index of the block.
* @param {ol.AtlasBlock} block The block to split.
* @param {number} width The width of the entry to insert.
* @param {number} height The height of the entry to insert.
*/
ol.style.Atlas.prototype.split_ = function(index, block, width, height) {
var deltaWidth = block.width - width;
var deltaHeight = block.height - height;
/** @type {ol.AtlasBlock} */
var newBlock1;
/** @type {ol.AtlasBlock} */
var newBlock2;
if (deltaWidth > deltaHeight) {
// split vertically
// block right of the inserted entry
newBlock1 = {
x: block.x + width,
y: block.y,
width: block.width - width,
height: block.height
};
// block below the inserted entry
newBlock2 = {
x: block.x,
y: block.y + height,
width: width,
height: block.height - height
};
this.updateBlocks_(index, newBlock1, newBlock2);
} else {
// split horizontally
// block right of the inserted entry
newBlock1 = {
x: block.x + width,
y: block.y,
width: block.width - width,
height: height
};
// block below the inserted entry
newBlock2 = {
x: block.x,
y: block.y + height,
width: block.width,
height: block.height - height
};
this.updateBlocks_(index, newBlock1, newBlock2);
}
};
/**
* Remove the old block and insert new blocks at the same array position.
* The new blocks are inserted at the same position, so that splitted
* blocks (that are potentially smaller) are filled first.
* @private
* @param {number} index The index of the block to remove.
* @param {ol.AtlasBlock} newBlock1 The 1st block to add.
* @param {ol.AtlasBlock} newBlock2 The 2nd block to add.
*/
ol.style.Atlas.prototype.updateBlocks_ = function(index, newBlock1, newBlock2) {
var args = [index, 1];
if (newBlock1.width > 0 && newBlock1.height > 0) {
args.push(newBlock1);
}
if (newBlock2.width > 0 && newBlock2.height > 0) {
args.push(newBlock2);
}
this.emptyBlocks_.splice.apply(this.emptyBlocks_, args);
};
|