autumnplot-gl 3.1.0 → 4.0.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +6 -11
  2. package/dist/110.autumnplot-gl.js +1 -1
  3. package/dist/110.autumnplot-gl.js.map +1 -1
  4. package/dist/autumnplot-gl.js +1 -1
  5. package/dist/autumnplot-gl.js.map +1 -1
  6. package/dist/marchingsquares.wasm +0 -0
  7. package/lib/AutumnTypes.d.ts +53 -5
  8. package/lib/AutumnTypes.js +25 -1
  9. package/lib/Barbs.d.ts +23 -6
  10. package/lib/Barbs.js +20 -18
  11. package/lib/BillboardCollection.d.ts +16 -8
  12. package/lib/BillboardCollection.js +107 -59
  13. package/lib/Color.d.ts +57 -0
  14. package/lib/Color.js +163 -0
  15. package/lib/ColorBar.d.ts +12 -1
  16. package/lib/ColorBar.js +9 -7
  17. package/lib/Colormap.d.ts +19 -18
  18. package/lib/Colormap.js +84 -23
  19. package/lib/Contour.d.ts +42 -11
  20. package/lib/Contour.js +67 -58
  21. package/lib/ContourCreator.d.ts +4 -0
  22. package/lib/ContourCreator.js +2 -1
  23. package/lib/Fill.d.ts +27 -16
  24. package/lib/Fill.js +105 -83
  25. package/lib/Grid.d.ts +125 -29
  26. package/lib/Grid.js +303 -95
  27. package/lib/Hodographs.d.ts +24 -6
  28. package/lib/Hodographs.js +28 -24
  29. package/lib/Map.js +1 -1
  30. package/lib/Paintball.d.ts +6 -5
  31. package/lib/Paintball.js +38 -32
  32. package/lib/ParticleTracer.d.ts +19 -0
  33. package/lib/ParticleTracer.js +37 -0
  34. package/lib/PlotComponent.d.ts +6 -7
  35. package/lib/PlotComponent.js +17 -7
  36. package/lib/PlotLayer.d.ts +4 -4
  37. package/lib/PlotLayer.worker.d.ts +1 -2
  38. package/lib/PlotLayer.worker.js +22 -57
  39. package/lib/PolylineCollection.d.ts +18 -9
  40. package/lib/PolylineCollection.js +124 -89
  41. package/lib/RawField.d.ts +76 -23
  42. package/lib/RawField.js +138 -29
  43. package/lib/ShaderManager.d.ts +12 -0
  44. package/lib/ShaderManager.js +58 -0
  45. package/lib/StationPlot.d.ts +145 -0
  46. package/lib/StationPlot.js +205 -0
  47. package/lib/TextCollection.d.ts +12 -8
  48. package/lib/TextCollection.js +113 -71
  49. package/lib/cpp/marchingsquares.js +483 -585
  50. package/lib/cpp/marchingsquares.wasm +0 -0
  51. package/lib/cpp/marchingsquares_embind.d.ts +23 -3
  52. package/lib/index.d.ts +7 -4
  53. package/lib/index.js +5 -3
  54. package/lib/utils.d.ts +5 -8
  55. package/lib/utils.js +12 -83
  56. package/package.json +2 -2
@@ -1,5 +1,7 @@
1
- import { WebGLAnyRenderingContext } from "./AutumnTypes";
2
- import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
1
+ import { RenderMethodArg, WebGLAnyRenderingContext } from "./AutumnTypes";
2
+ import { Color } from "./Color";
3
+ import { ShaderProgramManager } from "./ShaderManager";
4
+ import { WGLBuffer, WGLTexture } from "autumn-wgl";
3
5
  interface TextSpec {
4
6
  lat: number;
5
7
  lon: number;
@@ -12,20 +14,22 @@ interface TextCollectionOptions {
12
14
  horizontal_align?: HorizontalAlign;
13
15
  vertical_align?: VerticalAlign;
14
16
  font_size?: number;
15
- text_color?: [number, number, number];
16
- halo_color?: [number, number, number];
17
+ text_color?: Color;
18
+ halo_color?: Color;
17
19
  halo?: boolean;
20
+ offset_x?: number;
21
+ offset_y?: number;
18
22
  }
19
23
  declare class TextCollection {
20
- readonly program: WGLProgram;
24
+ readonly shader_manager: ShaderProgramManager;
21
25
  readonly anchors: WGLBuffer;
22
26
  readonly offsets: WGLBuffer;
23
27
  readonly texcoords: WGLBuffer;
24
28
  readonly texture: WGLTexture;
25
29
  readonly opts: Required<TextCollectionOptions>;
26
30
  private constructor();
27
- static make(gl: WebGLAnyRenderingContext, text_locs: TextSpec[], fontstack_url: string, opts?: TextCollectionOptions): Promise<TextCollection>;
28
- render(gl: WebGLAnyRenderingContext, matrix: number[], [map_width, map_height]: [number, number], map_zoom: number): void;
31
+ static make(gl: WebGLAnyRenderingContext, text_locs: TextSpec[], fontstack_url_template: string, opts?: TextCollectionOptions): Promise<TextCollection>;
32
+ render(gl: WebGLAnyRenderingContext, arg: RenderMethodArg, [map_width, map_height]: [number, number], map_zoom: number): void;
29
33
  }
30
34
  export { TextCollection };
31
- export type { TextSpec, TextCollectionOptions };
35
+ export type { TextSpec, TextCollectionOptions, HorizontalAlign, VerticalAlign };
@@ -1,22 +1,24 @@
1
- import { isWebGL2Ctx } from "./AutumnTypes";
1
+ import { getRendererData, isWebGL2Ctx } from "./AutumnTypes";
2
+ import { Color } from "./Color";
2
3
  import { LngLat } from "./Map";
4
+ import { ShaderProgramManager } from "./ShaderManager";
3
5
  import { Cache, normalizeOptions } from "./utils";
4
- import { WGLBuffer, WGLProgram, WGLTexture } from "autumn-wgl";
6
+ import { WGLBuffer, WGLTexture } from "autumn-wgl";
5
7
  import Protobuf from 'pbf';
6
8
  import potpack from "potpack";
7
- const text_vertex_shader_src = `
8
- uniform mat4 u_matrix;
9
+ const text_vertex_shader_src = `#version 300 es
10
+
9
11
  uniform int u_offset;
10
12
  uniform highp float u_map_width;
11
13
  uniform highp float u_map_height;
12
14
  uniform highp float u_map_zoom;
13
15
  uniform highp float u_font_size;
14
16
 
15
- attribute vec3 a_pos;
16
- attribute vec2 a_offset;
17
- attribute vec2 a_tex_coord;
17
+ in vec3 a_pos;
18
+ in vec2 a_offset;
19
+ in vec2 a_tex_coord;
18
20
 
19
- varying highp vec2 v_tex_coord;
21
+ out highp vec2 v_tex_coord;
20
22
 
21
23
  mat4 scalingMatrix(float x_scale, float y_scale, float z_scale) {
22
24
  return mat4(x_scale, 0.0, 0.0, 0.0,
@@ -37,38 +39,43 @@ void main() {
37
39
 
38
40
  mat4 map_stretch_matrix = scalingMatrix(u_map_height / u_map_width, 1., 1.);
39
41
 
40
- gl_Position = u_matrix * vec4(a_pos.xy + globe_offset, 0.0, 1.0) + u_font_size / 12. * 1.5 * map_stretch_matrix * vec4(offset, 0., 0.);
42
+ gl_Position = projectTile(a_pos.xy + globe_offset) + u_font_size / 12. * 1.5 * map_stretch_matrix * vec4(offset, 0., 0.);
41
43
  v_tex_coord = a_tex_coord;
42
44
  }`
43
- const text_fragment_shader_src = `
44
- varying highp vec2 v_tex_coord;
45
+ const text_fragment_shader_src = `#version 300 es
46
+
47
+ in highp vec2 v_tex_coord;
45
48
  uniform sampler2D u_sdf_sampler;
46
49
  uniform int u_is_halo;
47
50
 
48
- uniform lowp vec3 u_text_color;
49
- uniform lowp vec3 u_halo_color;
51
+ uniform lowp vec4 u_text_color;
52
+ uniform lowp vec4 u_halo_color;
50
53
 
51
54
  #define SDF_FILL 0.75
52
55
  #define SDF_HALO 0.45
53
56
 
57
+ out highp vec4 fragColor;
58
+
54
59
  void main() {
55
- highp float sdf_val = texture2D(u_sdf_sampler, v_tex_coord).r;
60
+ highp float sdf_val = texture(u_sdf_sampler, v_tex_coord).r;
56
61
 
57
62
  lowp float step_width = 0.08;
58
63
  lowp float alpha = smoothstep(SDF_FILL - step_width, SDF_FILL + step_width, sdf_val);
59
64
 
60
- lowp vec3 color = u_is_halo == 1 ? u_halo_color : u_text_color;
65
+ lowp vec4 color = u_is_halo == 1 ? u_halo_color : u_text_color;
61
66
 
62
67
  if (u_is_halo == 1) {
63
68
  alpha = min(smoothstep(SDF_HALO - step_width, SDF_HALO + step_width, sdf_val), 1.0 - alpha);
64
69
  }
65
70
 
66
- gl_FragColor = vec4(color, alpha);
71
+ color.a *= alpha;
72
+ fragColor = color;
67
73
  }`
68
- const program_cache = new Cache((gl) => new WGLProgram(gl, text_vertex_shader_src, text_fragment_shader_src));
69
74
  const PADDING = 3;
70
75
  function parseFontPBF(data) {
71
76
  const readGlyph = (tag, glyph, pbf) => {
77
+ if (pbf === undefined)
78
+ return;
72
79
  switch (tag) {
73
80
  case 1:
74
81
  glyph.id = pbf.readVarint();
@@ -94,12 +101,16 @@ function parseFontPBF(data) {
94
101
  }
95
102
  };
96
103
  const readFontStack = (tag, glyphs, pbf) => {
104
+ if (glyphs === undefined || pbf === undefined)
105
+ return;
97
106
  if (tag == 3) {
98
107
  const glyph = pbf.readMessage(readGlyph, {});
99
108
  glyphs.push(glyph);
100
109
  }
101
110
  };
102
111
  const readFontStacks = (tag, glyphs, pbf) => {
112
+ if (pbf === undefined)
113
+ return;
103
114
  if (tag == 1) {
104
115
  pbf.readMessage(readFontStack, glyphs);
105
116
  }
@@ -117,12 +128,16 @@ function createAtlas(pbf_glyphs) {
117
128
  const { w: img_width, h: img_height } = potpack(bins);
118
129
  const atlas_data = new Uint8Array(img_width * img_height);
119
130
  const glyphs = {};
131
+ let max_glyph_height = 0;
120
132
  glyph_bins.forEach(glyph_bin => {
121
133
  const { bin, glyph } = glyph_bin;
134
+ if (bin.x === undefined || bin.y === undefined)
135
+ throw `Potpack couldn't pack this pot, I guess?`;
122
136
  glyphs[glyph.id] = {
123
137
  id: glyph.id, width: glyph.width, height: glyph.height, left: glyph.left, top: glyph.top,
124
138
  atlas_i: bin.x, atlas_j: bin.y, advance: glyph.advance
125
139
  };
140
+ max_glyph_height = Math.max(max_glyph_height, glyph.height);
126
141
  for (let i = 0; i < glyph.width; i++) {
127
142
  for (let j = 0; j < glyph.height; j++) {
128
143
  const glyph_idx = i + glyph.width * j;
@@ -132,38 +147,46 @@ function createAtlas(pbf_glyphs) {
132
147
  }
133
148
  });
134
149
  const glyph_M = glyphs['M'.charCodeAt(0)];
135
- const baseline = glyph_M.height - glyph_M.top;
136
- const top = -glyph_M.top;
150
+ const baseline = glyph_M === undefined ? max_glyph_height : glyph_M.height - glyph_M.top;
151
+ const top = glyph_M === undefined ? 0 : -glyph_M.top;
137
152
  return { atlas: atlas_data, atlas_width: img_width, atlas_height: img_height, baseline: baseline, top: top, glyph_info: glyphs };
138
153
  }
139
- async function getFontAtlas(url) {
140
- const resp = await fetch(url);
141
- const blob = await resp.blob();
142
- const data_buffer = await blob.arrayBuffer();
143
- // Parse the PBF and get the glyph data
144
- const glyphs = parseFontPBF(new Uint8Array(data_buffer));
154
+ const FONT_ATLAS_CACHE = new Cache(async (urls) => {
155
+ const promises = urls.map(async (url) => {
156
+ const resp = await fetch(url);
157
+ const blob = await resp.blob();
158
+ const data_buffer = await blob.arrayBuffer();
159
+ // Parse the PBF and get the glyph data
160
+ return parseFontPBF(new Uint8Array(data_buffer));
161
+ });
162
+ const glyphs = (await Promise.all(promises)).flat();
145
163
  // Create an atlas for the glyphs
146
164
  return createAtlas(glyphs);
147
- }
165
+ });
148
166
  const text_collection_opt_defaults = {
149
167
  horizontal_align: 'left',
150
168
  vertical_align: 'baseline',
151
169
  font_size: 12,
152
- text_color: [0, 0, 0],
153
- halo_color: [0, 0, 0],
154
- halo: false
170
+ text_color: new Color([0, 0, 0, 1]),
171
+ halo_color: new Color([0, 0, 0, 1]),
172
+ halo: false,
173
+ offset_x: 0,
174
+ offset_y: 0
155
175
  };
156
176
  class TextCollection {
157
177
  constructor(gl, text_locs, font_atlas, opts) {
158
- this.program = program_cache.getValue(gl);
159
178
  this.opts = normalizeOptions(opts, text_collection_opt_defaults);
160
179
  const is_webgl2 = isWebGL2Ctx(gl);
161
180
  const format = is_webgl2 ? gl.R8 : gl.LUMINANCE;
162
181
  const type = gl.UNSIGNED_BYTE;
163
182
  const row_alignment = 1;
183
+ const empty_atlas = font_atlas.atlas_width == 0 || font_atlas.atlas_height == 0 || font_atlas.atlas.length == 0;
184
+ const atlas_width = empty_atlas ? 1 : font_atlas.atlas_width;
185
+ const atlas_height = empty_atlas ? 1 : font_atlas.atlas_height;
186
+ const atlas_data = empty_atlas ? new Uint8Array([0, 0, 0, 0]) : font_atlas.atlas;
164
187
  const image = {
165
- 'format': format, 'type': type, 'width': font_atlas.atlas_width, 'height': font_atlas.atlas_height,
166
- 'image': font_atlas.atlas, 'row_alignment': row_alignment, 'mag_filter': gl.LINEAR
188
+ 'format': format, 'type': type, 'width': atlas_width, 'height': atlas_height,
189
+ 'image': atlas_data, 'row_alignment': row_alignment, 'mag_filter': gl.LINEAR
167
190
  };
168
191
  this.texture = new WGLTexture(gl, image);
169
192
  const n_verts = text_locs.map(tl => tl.text.length).reduce((a, b) => a + b, 0) * 6;
@@ -175,7 +198,8 @@ class TextCollection {
175
198
  const { lat, lon, text } = loc;
176
199
  const min_zoom = loc.min_zoom === undefined ? 0 : loc.min_zoom;
177
200
  const { x: anchor_x, y: anchor_y } = new LngLat(lon, lat).toMercatorCoord();
178
- let x_offset = 0;
201
+ let x_offset = this.opts.offset_x;
202
+ let y_offset = this.opts.offset_y;
179
203
  const init_i_off = i_off;
180
204
  for (let i = 0; i < text.length; i++) {
181
205
  const glyph_code = text.charCodeAt(i);
@@ -204,17 +228,17 @@ class TextCollection {
204
228
  anchor_data[i_anch++] = anchor_y;
205
229
  anchor_data[i_anch++] = min_zoom;
206
230
  offset_data[i_off++] = x_offset;
207
- offset_data[i_off++] = font_atlas.baseline + glyph_info.top - glyph_info.height;
231
+ offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top - glyph_info.height;
208
232
  offset_data[i_off++] = x_offset;
209
- offset_data[i_off++] = font_atlas.baseline + glyph_info.top - glyph_info.height;
233
+ offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top - glyph_info.height;
210
234
  offset_data[i_off++] = x_offset + glyph_info.width;
211
- offset_data[i_off++] = font_atlas.baseline + glyph_info.top - glyph_info.height;
235
+ offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top - glyph_info.height;
212
236
  offset_data[i_off++] = x_offset;
213
- offset_data[i_off++] = font_atlas.baseline + glyph_info.top;
237
+ offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top;
214
238
  offset_data[i_off++] = x_offset + glyph_info.width;
215
- offset_data[i_off++] = font_atlas.baseline + glyph_info.top;
239
+ offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top;
216
240
  offset_data[i_off++] = x_offset + glyph_info.width;
217
- offset_data[i_off++] = font_atlas.baseline + glyph_info.top;
241
+ offset_data[i_off++] = y_offset + font_atlas.baseline + glyph_info.top;
218
242
  tc_data[i_tc++] = glyph_info.atlas_i / font_atlas.atlas_width;
219
243
  tc_data[i_tc++] = (glyph_info.atlas_j + glyph_info.height) / font_atlas.atlas_height;
220
244
  tc_data[i_tc++] = glyph_info.atlas_i / font_atlas.atlas_width;
@@ -229,66 +253,84 @@ class TextCollection {
229
253
  tc_data[i_tc++] = glyph_info.atlas_j / font_atlas.atlas_height;
230
254
  x_offset += glyph_info.advance - glyph_info.left;
231
255
  }
232
- if (opts.horizontal_align == 'center') {
256
+ if (this.opts.horizontal_align == 'center') {
233
257
  for (let i = init_i_off; i < init_i_off + text.length * 12; i += 2) {
234
- offset_data[i] -= x_offset / 2;
258
+ offset_data[i] -= (x_offset - this.opts.offset_x) / 2;
235
259
  }
236
260
  }
237
- else if (opts.horizontal_align == 'right') {
261
+ else if (this.opts.horizontal_align == 'right') {
238
262
  for (let i = init_i_off; i < init_i_off + text.length * 12; i += 2) {
239
- offset_data[i] -= x_offset;
263
+ offset_data[i] -= (x_offset - this.opts.offset_x);
240
264
  }
241
265
  }
242
- if (opts.vertical_align == 'top') {
266
+ if (this.opts.vertical_align == 'top') {
243
267
  for (let i = init_i_off + 1; i < init_i_off + text.length * 12; i += 2) {
244
268
  offset_data[i] -= (font_atlas.baseline - font_atlas.top);
245
269
  }
246
270
  }
247
- else if (opts.vertical_align == 'middle') {
271
+ else if (this.opts.vertical_align == 'middle') {
248
272
  for (let i = init_i_off + 1; i < init_i_off + text.length * 12; i += 2) {
249
273
  offset_data[i] -= (font_atlas.baseline - font_atlas.top) / 2;
250
274
  }
251
275
  }
252
276
  });
277
+ this.shader_manager = new ShaderProgramManager(text_vertex_shader_src, text_fragment_shader_src, []);
253
278
  this.anchors = new WGLBuffer(gl, anchor_data, 3, gl.TRIANGLE_STRIP);
254
279
  this.offsets = new WGLBuffer(gl, offset_data, 2, gl.TRIANGLE_STRIP);
255
280
  this.texcoords = new WGLBuffer(gl, tc_data, 2, gl.TRIANGLE_STRIP);
256
281
  }
257
- static async make(gl, text_locs, fontstack_url, opts) {
258
- const atlas = await getFontAtlas(fontstack_url);
282
+ static async make(gl, text_locs, fontstack_url_template, opts) {
283
+ const FONT_GROUP_SIZE = 256;
284
+ const characters = text_locs.map(tl => [...tl.text]).flat().map(c => c.charCodeAt(0)).filter((c, i, ary) => ary.indexOf(c) == i);
285
+ const char_code_min = Math.min(...characters);
286
+ const char_code_max = Math.max(...characters);
287
+ const stack_start = Math.floor(char_code_min / FONT_GROUP_SIZE) * FONT_GROUP_SIZE;
288
+ const stack_end = Math.floor(char_code_max / FONT_GROUP_SIZE) * FONT_GROUP_SIZE;
289
+ const fontstack_urls = [];
290
+ for (let istack = stack_start; istack <= stack_end; istack += FONT_GROUP_SIZE) {
291
+ fontstack_urls.push(fontstack_url_template.replace('{range}', `${istack}-${istack + FONT_GROUP_SIZE - 1}`));
292
+ }
293
+ const atlas = await FONT_ATLAS_CACHE.getValue(fontstack_urls);
294
+ if (atlas.atlas_height == 0 || atlas.atlas_width == 0 || atlas.atlas.length == 0)
295
+ console.warn(`No font data from '${fontstack_url_template}'`);
259
296
  return new TextCollection(gl, text_locs, atlas, opts);
260
297
  }
261
- render(gl, matrix, [map_width, map_height], map_zoom) {
298
+ render(gl, arg, [map_width, map_height], map_zoom) {
299
+ const render_data = getRendererData(arg);
300
+ const program = this.shader_manager.getShaderProgram(gl, render_data.shaderData);
262
301
  const uniforms = {
263
- 'u_matrix': matrix, 'u_map_width': map_width, 'u_map_height': map_height, 'u_map_zoom': map_zoom, 'u_font_size': this.opts.font_size,
264
- 'u_text_color': this.opts.text_color, 'u_halo_color': this.opts.halo_color, 'u_offset': 0
302
+ 'u_map_width': map_width, 'u_map_height': map_height, 'u_map_zoom': map_zoom, 'u_font_size': this.opts.font_size,
303
+ 'u_text_color': this.opts.text_color.toRGBATuple(), 'u_halo_color': this.opts.halo_color.toRGBATuple(), 'u_offset': 0,
304
+ ...this.shader_manager.getShaderUniforms(render_data)
265
305
  };
266
306
  uniforms['u_is_halo'] = this.opts.halo ? 1 : 0;
267
- this.program.use({ 'a_pos': this.anchors, 'a_offset': this.offsets, 'a_tex_coord': this.texcoords }, uniforms, { 'u_sdf_sampler': this.texture });
307
+ program.use({ 'a_pos': this.anchors, 'a_offset': this.offsets, 'a_tex_coord': this.texcoords }, uniforms, { 'u_sdf_sampler': this.texture });
268
308
  gl.enable(gl.BLEND);
269
309
  gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
270
- this.program.draw();
271
- if (this.opts.halo) {
272
- this.program.setUniforms({ 'u_is_halo': 0 });
273
- this.program.draw();
274
- }
275
- this.program.setUniforms({ 'u_offset': -2, 'u_is_halo': this.opts.halo ? 1 : 0 });
276
- this.program.draw();
277
- if (this.opts.halo) {
278
- this.program.setUniforms({ 'u_is_halo': 0 });
279
- this.program.draw();
280
- }
281
- this.program.setUniforms({ 'u_offset': -1, 'u_is_halo': this.opts.halo ? 1 : 0 });
282
- this.program.draw();
310
+ program.draw();
283
311
  if (this.opts.halo) {
284
- this.program.setUniforms({ 'u_is_halo': 0 });
285
- this.program.draw();
312
+ program.setUniforms({ 'u_is_halo': 0 });
313
+ program.draw();
286
314
  }
287
- this.program.setUniforms({ 'u_offset': 1, 'u_is_halo': this.opts.halo ? 1 : 0 });
288
- this.program.draw();
289
- if (this.opts.halo) {
290
- this.program.setUniforms({ 'u_is_halo': 0 });
291
- this.program.draw();
315
+ if (render_data.type != 'maplibre' || !render_data.shaderData.define.includes('GLOBE')) {
316
+ program.setUniforms({ 'u_offset': -2, 'u_is_halo': this.opts.halo ? 1 : 0 });
317
+ program.draw();
318
+ if (this.opts.halo) {
319
+ program.setUniforms({ 'u_is_halo': 0 });
320
+ program.draw();
321
+ }
322
+ program.setUniforms({ 'u_offset': -1, 'u_is_halo': this.opts.halo ? 1 : 0 });
323
+ program.draw();
324
+ if (this.opts.halo) {
325
+ program.setUniforms({ 'u_is_halo': 0 });
326
+ program.draw();
327
+ }
328
+ program.setUniforms({ 'u_offset': 1, 'u_is_halo': this.opts.halo ? 1 : 0 });
329
+ program.draw();
330
+ if (this.opts.halo) {
331
+ program.setUniforms({ 'u_is_halo': 0 });
332
+ program.draw();
333
+ }
292
334
  }
293
335
  }
294
336
  }