geojs 1.6.5 → 1.7.2

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.
@@ -0,0 +1,122 @@
1
+ var inherit = require('../inherit');
2
+ var timestamp = require('../timestamp');
3
+ var vgl = require('vgl');
4
+
5
+ /**
6
+ * Switch to a specific texture unit.
7
+ *
8
+ * @param {vgl.renderState} renderState An object that contains the context
9
+ * used for drawing.
10
+ * @param {number} textureUnit The number of the texture unit [0-15].
11
+ */
12
+ function activateTextureUnit(renderState, textureUnit) {
13
+ if (textureUnit >= 0 && textureUnit <= 31) {
14
+ renderState.m_context.activeTexture(vgl.GL.TEXTURE0 + textureUnit);
15
+ } else {
16
+ throw Error('[error] Texture unit ' + textureUnit + ' is not supported');
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Create a new instance of class webgl_lookupTable2D.
22
+ *
23
+ * @class
24
+ * @alias geo.webgl.lookupTable2D
25
+ * @param {object} arg Options object.
26
+ * @param {number} [arg.maxWidth] Maximum width to use for the texture. If the
27
+ * number of colors set is less than this, the texture is 1D. If greater, it
28
+ * will be a rectangle of maxWidth x whatever height is necessary.
29
+ * @param {number[]} [arg.colorTable] Initial color table for the texture.
30
+ * This is of the form RGBARGBA... where each value is an integer on the
31
+ * scale [0,255].
32
+ * @extends vgl.texture
33
+ * @returns {geo.webgl.lookupTable2D}
34
+ */
35
+ var webgl_lookupTable2D = function (arg) {
36
+ 'use strict';
37
+
38
+ if (!(this instanceof webgl_lookupTable2D)) {
39
+ return new webgl_lookupTable2D(arg);
40
+ }
41
+ arg = arg || {};
42
+ vgl.texture.call(this);
43
+
44
+ var m_setupTimestamp = timestamp(),
45
+ m_maxWidth = arg.maxWidth || 4096,
46
+ m_colorTable = new Uint8Array([0, 0, 0, 0]),
47
+ m_colorTableOrig,
48
+ m_this = this;
49
+
50
+ /**
51
+ * Create lookup table, initialize parameters, and bind data to it.
52
+ *
53
+ * @param {vgl.renderState} renderState An object that contains the context
54
+ * used for drawing.
55
+ */
56
+ this.setup = function (renderState) {
57
+ activateTextureUnit(renderState, m_this.textureUnit());
58
+
59
+ renderState.m_context.deleteTexture(m_this.m_textureHandle);
60
+ m_this.m_textureHandle = renderState.m_context.createTexture();
61
+ renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, m_this.m_textureHandle);
62
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D, vgl.GL.TEXTURE_MIN_FILTER, vgl.GL.NEAREST);
63
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D, vgl.GL.TEXTURE_MAG_FILTER, vgl.GL.NEAREST);
64
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D, vgl.GL.TEXTURE_WRAP_S, vgl.GL.CLAMP_TO_EDGE);
65
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D, vgl.GL.TEXTURE_WRAP_T, vgl.GL.CLAMP_TO_EDGE);
66
+ renderState.m_context.pixelStorei(vgl.GL.UNPACK_ALIGNMENT, 1);
67
+ renderState.m_context.pixelStorei(vgl.GL.UNPACK_FLIP_Y_WEBGL, true);
68
+
69
+ renderState.m_context.texImage2D(
70
+ vgl.GL.TEXTURE_2D, 0, vgl.GL.RGBA, m_this.width, m_this.height, 0,
71
+ vgl.GL.RGBA, vgl.GL.UNSIGNED_BYTE, m_colorTable);
72
+
73
+ renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, null);
74
+ m_setupTimestamp.modified();
75
+ };
76
+
77
+ /**
78
+ * Get/set color table.
79
+ *
80
+ * @param {number[]} [val] An array of RGBARGBA... integers on a scale
81
+ * of [0, 255]. `undefined` to get the current value.
82
+ * @returns {number[]|this}
83
+ */
84
+ this.colorTable = function (val) {
85
+ if (val === undefined) {
86
+ return m_colorTableOrig;
87
+ }
88
+ m_colorTableOrig = val;
89
+ if (val.length < 4) {
90
+ val = [0, 0, 0, 0];
91
+ }
92
+ m_this.width = Math.min(m_maxWidth, val.length / 4);
93
+ m_this.height = Math.ceil(val.length / 4 / m_maxWidth);
94
+ if (!(val instanceof Uint8Array) || val.length !== m_this.width * m_this.height * 4) {
95
+ if (val.length < m_this.width * m_this.height * 4) {
96
+ val = val.concat(new Array(m_this.width * m_this.height * 4 - val.length).fill(0));
97
+ }
98
+ m_colorTable = new Uint8Array(val);
99
+ } else {
100
+ m_colorTable = val;
101
+ }
102
+ m_this.modified();
103
+ return m_this;
104
+ };
105
+
106
+ /**
107
+ * Get maxWidth value.
108
+ *
109
+ * @returns {number} The maxWidth of the texture used.
110
+ */
111
+ this.maxWidth = function () {
112
+ return m_maxWidth;
113
+ };
114
+
115
+ this.colorTable(arg.colorTable || []);
116
+
117
+ return this;
118
+ };
119
+
120
+ inherit(webgl_lookupTable2D, vgl.texture);
121
+
122
+ module.exports = webgl_lookupTable2D;
@@ -0,0 +1,47 @@
1
+ /* pixelmapFeature fragment shader */
2
+
3
+ varying highp vec2 iTextureCoord;
4
+ uniform sampler2D sampler2d;
5
+ uniform sampler2D lutSampler;
6
+ uniform int lutWidth;
7
+ uniform int lutHeight;
8
+ uniform mediump float opacity;
9
+ uniform highp vec2 crop;
10
+
11
+ void main(void) {
12
+ if ((crop.s < 1.0 && iTextureCoord.s > crop.s) || (crop.t < 1.0 && 1.0 - iTextureCoord.t > crop.t)) {
13
+ discard;
14
+ }
15
+ // to add anti-aliasing, we would need to know the current pixel size
16
+ // (probably computed in the vertex shader) and then sample the base image at
17
+ // multiple points, then average the output color.
18
+ highp vec4 lutValue = texture2D(sampler2d, iTextureCoord);
19
+ highp vec2 lutCoord;
20
+ lutCoord.s = (
21
+ mod(
22
+ // add 0.5 to handle float imprecision
23
+ floor(lutValue.r * 255.0 + 0.5) +
24
+ floor(lutValue.g * 255.0 + 0.5) * 256.0,
25
+ float(lutWidth)
26
+ // center in pixel
27
+ ) + 0.5) / float(lutWidth);
28
+ // Our image is top-down, so invert the coordinate
29
+ lutCoord.t = 1.0 - (
30
+ floor(
31
+ (
32
+ // add 0.5 to handle float imprecision
33
+ floor(lutValue.r * 255.0 + 0.5) +
34
+ floor(lutValue.g * 255.0 + 0.5) * 256.0 +
35
+ floor(lutValue.b * 255.0 + 0.5) * 256.0 * 256.0
36
+ // We may want an option to use the alpha channel to allow more indices
37
+ ) / float(lutWidth)
38
+ // center in pixel
39
+ ) + 0.5) / float(lutHeight);
40
+ if (lutCoord.t < 0.0) {
41
+ discard;
42
+ }
43
+ mediump vec4 color = texture2D(lutSampler, lutCoord);
44
+
45
+ color.a *= opacity;
46
+ gl_FragColor = color;
47
+ }
@@ -0,0 +1,203 @@
1
+ var inherit = require('../inherit');
2
+ var registerFeature = require('../registry').registerFeature;
3
+ var pixelmapFeature = require('../pixelmapFeature');
4
+ var lookupTable2D = require('./lookupTable2D');
5
+ var util = require('../util');
6
+
7
+ /**
8
+ * Create a new instance of class webgl.pixelmapFeature.
9
+ *
10
+ * @class
11
+ * @alias geo.webgl.pixelmapFeature
12
+ * @extends geo.pixelmapFeature
13
+ * @param {geo.pixelmapFeature.spec} arg
14
+ * @returns {geo.webgl.pixelmapFeature}
15
+ */
16
+ var webgl_pixelmapFeature = function (arg) {
17
+ 'use strict';
18
+
19
+ if (!(this instanceof webgl_pixelmapFeature)) {
20
+ return new webgl_pixelmapFeature(arg);
21
+ }
22
+ pixelmapFeature.call(this, arg);
23
+
24
+ var object = require('./object');
25
+ object.call(this);
26
+
27
+ const vgl = require('vgl');
28
+ const fragmentShader = require('./pixelmapFeature.frag');
29
+
30
+ var m_quadFeature,
31
+ m_quadFeatureInit,
32
+ s_exit = this._exit,
33
+ m_lookupTable,
34
+ m_this = this;
35
+
36
+ /**
37
+ * If the specified coordinates are in the rendered quad, use the basis
38
+ * information from the quad to determine the pixelmap index value so that it
39
+ * can be included in the `found` results.
40
+ *
41
+ * @param {geo.geoPosition} geo Coordinate.
42
+ * @param {string|geo.transform|null} [gcs] Input gcs. `undefined` to use
43
+ * the interface gcs, `null` to use the map gcs, or any other transform.
44
+ * @returns {geo.feature.searchResult} An object with a list of features and
45
+ * feature indices that are located at the specified point.
46
+ */
47
+ this.pointSearch = function (geo, gcs) {
48
+ if (m_quadFeature && m_this.m_info) {
49
+ let result = m_quadFeature.pointSearch(geo, gcs);
50
+ // use the last index by preference, since for tile layers, this is the
51
+ // topmosttile
52
+ let idxIdx = result.index.length - 1;
53
+ for (; idxIdx >= 0; idxIdx -= 1) {
54
+ if (result.extra[result.index[idxIdx]]._quad &&
55
+ result.extra[result.index[idxIdx]]._quad.image) {
56
+ const img = result.extra[result.index[idxIdx]]._quad.image;
57
+ const basis = result.extra[result.index[idxIdx]].basis;
58
+ const x = Math.floor(basis.x * img.width);
59
+ const y = Math.floor(basis.y * img.height);
60
+ const canvas = document.createElement('canvas');
61
+ canvas.width = canvas.height = 1;
62
+ const context = canvas.getContext('2d');
63
+ context.drawImage(img, x, y, 1, 1, 0, 0, 1, 1);
64
+ const pixel = context.getImageData(0, 0, 1, 1).data;
65
+ const idx = pixel[0] + pixel[1] * 256 + pixel[2] * 256 * 256;
66
+ result = {
67
+ index: [idx],
68
+ found: [m_this.data()[idx]]
69
+ };
70
+ return result;
71
+ }
72
+ }
73
+ }
74
+ return {index: [], found: []};
75
+ };
76
+
77
+ /**
78
+ * Given the loaded pixelmap image, create a texture for the colors and a
79
+ * quad that will use it.
80
+ */
81
+ this._computePixelmap = function () {
82
+ var data = m_this.data() || [],
83
+ colorFunc = m_this.style.get('color');
84
+
85
+ let indexRange = m_this.indexModified(undefined, 'clear');
86
+ let fullUpdate = m_this.dataTime().timestamp() >= m_this.buildTime().timestamp() || indexRange === undefined;
87
+ if (!m_lookupTable) {
88
+ m_lookupTable = lookupTable2D();
89
+ m_lookupTable.setTextureUnit(1);
90
+ fullUpdate = true;
91
+ }
92
+ let clrLen = Math.max(1, data.length);
93
+ const maxWidth = m_lookupTable.maxWidth();
94
+ if (clrLen > maxWidth && clrLen % maxWidth) {
95
+ clrLen += maxWidth - (clrLen % maxWidth);
96
+ }
97
+
98
+ let colors;
99
+ if (!fullUpdate) {
100
+ colors = m_lookupTable.colorTable();
101
+ fullUpdate = colors.length !== clrLen * 4;
102
+ indexRange[0] = Math.max(0, indexRange[0]);
103
+ indexRange[1] = Math.min(data.length, indexRange[1] + 1);
104
+ }
105
+ if (fullUpdate) {
106
+ colors = new Uint8Array(clrLen * 4);
107
+ indexRange = [0, data.length];
108
+ }
109
+ for (let i = indexRange[0]; i < indexRange[1]; i += 1) {
110
+ const d = data[i];
111
+ const color = util.convertColor(colorFunc.call(m_this, d, i));
112
+ colors[i * 4] = color.r * 255;
113
+ colors[i * 4 + 1] = color.g * 255;
114
+ colors[i * 4 + 2] = color.b * 255;
115
+ colors[i * 4 + 3] = color.a === undefined ? 255 : (color.a * 255);
116
+ }
117
+ m_this.m_info = {colors: colors};
118
+ // check if colors haven't changed
119
+ var oldcolors = m_lookupTable.colorTable();
120
+ if (oldcolors && oldcolors.length === colors.length) {
121
+ let idx = indexRange[0] * 4;
122
+ for (; idx < indexRange[1] * 4; idx += 1) {
123
+ if (colors[idx] !== oldcolors[idx]) {
124
+ break;
125
+ }
126
+ }
127
+ if (idx === indexRange[1] * 4) {
128
+ return;
129
+ }
130
+ }
131
+ m_lookupTable.colorTable(colors);
132
+ /* If we haven't made a quad feature, make one now */
133
+ if (!m_quadFeature) {
134
+ m_quadFeature = m_this.layer().createFeature('quad', {
135
+ selectionAPI: false,
136
+ gcs: m_this.gcs(),
137
+ visible: m_this.visible(undefined, true)
138
+ });
139
+ m_quadFeatureInit = false;
140
+ }
141
+ if (!m_quadFeatureInit) {
142
+ m_this.dependentFeatures([m_quadFeature]);
143
+ m_quadFeature.setShader('image_fragment', fragmentShader);
144
+ m_quadFeature._hookBuild = (prog) => {
145
+ const lutSampler = new vgl.uniform(vgl.GL.INT, 'lutSampler');
146
+ lutSampler.set(m_lookupTable.textureUnit());
147
+ prog.addUniform(lutSampler);
148
+ const lutWidth = new vgl.uniform(vgl.GL.INT, 'lutWidth');
149
+ lutWidth.set(m_lookupTable.width);
150
+ prog.addUniform(lutWidth);
151
+ const lutHeight = new vgl.uniform(vgl.GL.INT, 'lutHeight');
152
+ lutHeight.set(m_lookupTable.height);
153
+ prog.addUniform(lutHeight);
154
+ };
155
+ m_quadFeature._hookRenderImageQuads = (renderState, quads) => {
156
+ quads.forEach((quad) => {
157
+ if (quad.image && quad.texture && !quad.texture.nearestPixel()) {
158
+ quad.texture.setNearestPixel(true);
159
+ }
160
+ });
161
+ m_lookupTable.bind(renderState, quads);
162
+ };
163
+ if (m_quadFeatureInit === false) {
164
+ m_quadFeature.style({
165
+ image: m_this.m_srcImage,
166
+ position: m_this.style.get('position')})
167
+ .data([{}])
168
+ .draw();
169
+ }
170
+ m_quadFeatureInit = true;
171
+ }
172
+ };
173
+
174
+ /**
175
+ * Destroy. Deletes the associated quadFeature.
176
+ *
177
+ * @returns {this}
178
+ */
179
+ this._exit = function () {
180
+ if (m_quadFeature && m_this.layer()) {
181
+ m_this.layer().deleteFeature(m_quadFeature);
182
+ m_quadFeature = null;
183
+ m_this.dependentFeatures([]);
184
+ }
185
+ s_exit();
186
+ return m_this;
187
+ };
188
+
189
+ if (arg.quadFeature) {
190
+ m_quadFeature = arg.quadFeature;
191
+ }
192
+ this._init(arg);
193
+ return this;
194
+ };
195
+
196
+ inherit(webgl_pixelmapFeature, pixelmapFeature);
197
+
198
+ // Now register it
199
+ var capabilities = {};
200
+ capabilities[pixelmapFeature.capabilities.lookup] = true;
201
+
202
+ registerFeature('webgl', 'pixelmap', webgl_pixelmapFeature, capabilities);
203
+ module.exports = webgl_pixelmapFeature;
@@ -3,6 +3,8 @@ var registerFeature = require('../registry').registerFeature;
3
3
  var quadFeature = require('../quadFeature');
4
4
  var timestamp = require('../timestamp');
5
5
 
6
+ let _memoryCheckLargestTested = 4096 * 4096;
7
+
6
8
  /**
7
9
  * Create a new instance of class quadFeature.
8
10
  *
@@ -158,7 +160,7 @@ var webgl_quadFeature = function (arg) {
158
160
  * Build this feature.
159
161
  */
160
162
  this._build = function () {
161
- var mapper, mat, prog, srctex, unicrop, unicropsource, geom, context;
163
+ var mapper, mat, prog, srctex, unicrop, unicropsource, geom, context, sampler2d;
162
164
 
163
165
  if (!m_this.position()) {
164
166
  return;
@@ -183,6 +185,10 @@ var webgl_quadFeature = function (arg) {
183
185
  prog.addUniform(new vgl.projectionUniform('projectionMatrix'));
184
186
  prog.addUniform(new vgl.floatUniform('opacity', 1.0));
185
187
  prog.addUniform(new vgl.floatUniform('zOffset', 0.0));
188
+ /* Use texture unit 0 */
189
+ sampler2d = new vgl.uniform(vgl.GL.INT, 'sampler2d');
190
+ sampler2d.set(0);
191
+ prog.addUniform(sampler2d);
186
192
  context = m_this.renderer()._glContext();
187
193
  unicrop = new vgl.uniform(context.FLOAT_VEC2, 'crop');
188
194
  unicrop.set([1.0, 1.0]);
@@ -194,6 +200,9 @@ var webgl_quadFeature = function (arg) {
194
200
  context.VERTEX_SHADER, context, vertexShaderImage));
195
201
  prog.addShader(vgl.getCachedShader(
196
202
  context.FRAGMENT_SHADER, context, fragmentShaderImage));
203
+ if (m_this._hookBuild) {
204
+ m_this._hookBuild(prog);
205
+ }
197
206
  mat.addAttribute(prog);
198
207
  mat.addAttribute(new vgl.blend());
199
208
  /* This is similar to vgl.planeSource */
@@ -355,14 +364,22 @@ var webgl_quadFeature = function (arg) {
355
364
  cropsrc = {x0: 0, y0: 0, x1: 1, y1: 1}, quadcropsrc,
356
365
  w, h, quadw, quadh;
357
366
 
367
+ if (m_this._hookRenderImageQuads) {
368
+ m_this._hookRenderImageQuads(renderState, m_quads.imgQuads);
369
+ }
358
370
  context.bindBuffer(context.ARRAY_BUFFER, m_glBuffers.imgQuadsPosition);
359
371
  $.each(m_quads.imgQuads, function (idx, quad) {
360
372
  if (!quad.image) {
361
373
  return;
362
374
  }
363
375
  quad.texture.bind(renderState);
364
- if (context.getError() === context.OUT_OF_MEMORY) {
365
- console.log('Insufficient GPU memory for texture');
376
+ // only check if the context is out of memory when using modestly large
377
+ // textures. The check is slow.
378
+ if (quad.image.width * quad.image.height > _memoryCheckLargestTested) {
379
+ _memoryCheckLargestTested = quad.image.width * quad.image.height;
380
+ if (context.getError() === context.OUT_OF_MEMORY) {
381
+ console.log('Insufficient GPU memory for texture');
382
+ }
366
383
  }
367
384
  if (quad.opacity !== opacity) {
368
385
  opacity = quad.opacity;
@@ -450,6 +467,27 @@ var webgl_quadFeature = function (arg) {
450
467
  m_this.modified();
451
468
  };
452
469
 
470
+ /**
471
+ * Set the image or color vertex or fragment shader.
472
+ *
473
+ * @param {string} shaderType One of `image_vertex`, `image_fragment`,
474
+ * `color_vertex`, or `color_fragment`.
475
+ * @param {string} shaderCode The shader program.
476
+ * @returns {this} The class instance on success, undefined in an unknown
477
+ * shaderType was specified.
478
+ */
479
+ this.setShader = function (shaderType, shaderCode) {
480
+ switch (shaderType) {
481
+ case 'image_vertex': vertexShaderImage = shaderCode; break;
482
+ case 'image_fragment': fragmentShaderImage = shaderCode; break;
483
+ case 'color_vertex': vertexShaderColor = shaderCode; break;
484
+ case 'color_fragment': fragmentShaderColor = shaderCode; break;
485
+ default:
486
+ return;
487
+ }
488
+ return m_this;
489
+ };
490
+
453
491
  /**
454
492
  * Destroy.
455
493
  */