Skip to content

Commit

Permalink
Improve webgl coordinate precision.
Browse files Browse the repository at this point in the history
For line, point, polygon, and contour features, this uses a localized
origin for the view matrix to reduce jitter at high zoom levels.

Using global coordinates runs into the limitations of float precision,
which manes that small movements are functionally rounded by that
precision.  When panning at high zoom levels this manifests as jitter
where the features jump between discrete locations instead of panning
smoothly.  The default is to use the first coordinate of the data of the
feature for the origin.  For features that span a large area, this will
not help the jitter far from that coordinate (but won't be any worse
that before this PR).  The origin function is exposed on the style
object, so if a large area feature is needed, the application can bind
geo.event.pan to recompute the origin upon significant changes in the
map center at high zoom values.

Resolves #831.
  • Loading branch information
manthey committed Jun 24, 2019
1 parent 8319ac2 commit d0aa6a3
Show file tree
Hide file tree
Showing 11 changed files with 89 additions and 19 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

### Improvements
- More response zooming via mouse wheel (#993)
- If a point has no stroke or fill, don't return it from pointSearch (#1003)
- WebGL point, line, polygon, and contour features use a localized origin for improved precision at high zoom levels. This reduces panning jitter in zoom levels 19 and up

### Changes
- Idle handlers no longer defer to scene-graph parents. Parents still wait for all children to be idle
- Idle handlers no longer defer to scene-graph parents. Parents still wait for all children to be idle (#1001)

### Bug Fixes
- Better handling of tiles with overlap (#997)
Expand Down
8 changes: 7 additions & 1 deletion src/contourFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ var meshFeature = require('./meshFeature');
* that point won't be included in the results.
* @property {number|function} [opacity=1] The opacity for the whole feature on
* a scale of 0 to 1.
* @property {number[]|function} [origin] Origin in map gcs coordinates used
* for to ensure high precision drawing in this location. When called as a
* function, this is passed the vertex positions as a single continuous array
* in map gcs coordinates. It defaults to the first vertex used in the
* contour.
*/

/**
Expand Down Expand Up @@ -214,7 +219,8 @@ var contourFeature = function (arg) {
opacity: 1.0,
value: function (d, i) {
return m_this.position()(d, i).z;
}
},
origin: (p) => (p.length >= 3 ? [p[0], p[1], 0] : [0, 0, 0])
},
arg.style === undefined ? {} : arg.style
);
Expand Down
8 changes: 7 additions & 1 deletion src/lineFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ var util = require('./util');
* visible gradient. This is a single value that applies to all lines.
* @property {string|function} [debug] If 'debug', render lines in debug mode.
* This is a single value that applies to all lines.
* @property {number[]|function} [origin] Origin in map gcs coordinates used
* for to ensure high precision drawing in this location. When called as a
* function, this is passed the vertex positions as a single continuous array
* in map gcs coordinates. It defaults to the first line's first vertex's
* position.
*/

/**
Expand Down Expand Up @@ -349,7 +354,8 @@ var lineFeature = function (arg) {
antialiasing: 2.0,
closed: false,
line: function (d) { return d; },
position: function (d) { return d; }
position: function (d) { return d; },
origin: (p) => (p.length >= 3 ? p.slice(0, 3) : [0, 0, 0])
},
arg.style === undefined ? {} : arg.style
);
Expand Down
7 changes: 6 additions & 1 deletion src/pointFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ var feature = require('./feature');
* @property {geo.geoColor|function} [fillColor] Color to fill each point.
* @property {number|function} [fillOpacity=1] Opacity for each point. Opacity
* is on a [0-1] scale.
* @property {number[]|function} [origin] Origin in map gcs coordinates used
* for to ensure high precision drawing in this location. When called as a
* function, this is passed the point positions as a single continuous array
* in map gcs coordinates. It defaults to the first point's position.
*/

/**
Expand Down Expand Up @@ -411,7 +415,8 @@ var pointFeature = function (arg) {
fillColor: { r: 1.0, g: 0.839, b: 0.439 },
fill: true,
fillOpacity: 0.8,
position: function (d) { return d; }
position: function (d) { return d; },
origin: (p) => (p.length >= 3 ? p.slice(0, 3) : [0, 0, 0])
},
arg.style === undefined ? {} : arg.style
);
Expand Down
15 changes: 14 additions & 1 deletion src/polygonFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ var transform = require('./transform');
* each polygon has a uniform style (uniform fill color, fill opacity, stroke
* color, and stroke opacity). Can vary by polygon.
* @property {boolean|function} [closed=true] Ignored. Always `true`.
* @property {number[]|function} [origin] Origin in map gcs coordinates used
* for to ensure high precision drawing in this location. When called as a
* function, this is passed an array of items, each of which has a vertices
* property that is a single continuous array in map gcs coordinates. It
* defaults to the first polygon's first vertex's position.
*/

/**
Expand Down Expand Up @@ -583,7 +588,15 @@ var polygonFeature = function (arg) {
strokeColor: {r: 0.0, g: 1.0, b: 1.0},
strokeOpacity: 1.0,
polygon: function (d) { return d; },
position: function (d) { return d; }
position: function (d) { return d; },
origin: (items) => {
for (let i = 0; i < items.length; i += 1) {
if (items[i].vertices.length >= 3) {
return items[i].vertices.slice(0, 3);
}
}
return [0, 0, 0];
}
},
arg.style === undefined ? {} : arg.style
);
Expand Down
2 changes: 1 addition & 1 deletion src/quadFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ var quadFeature = function (arg) {
clrQuads: clrQuads,
imgQuads: imgQuads,
vidQuads: vidQuads,
origin: origin
origin: new Float32Array(origin)
};
return m_quads;
};
Expand Down
16 changes: 14 additions & 2 deletions src/webgl/contourFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ var webgl_contourFeature = function (arg) {
m_stepsUniform = null,
m_steppedUniform = null,
m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw,
m_origin,
m_modelViewUniform,
s_init = this._init,
s_update = this._update;

Expand Down Expand Up @@ -95,6 +97,16 @@ var webgl_contourFeature = function (arg) {
m_texture.setColorTable(colorTable);
contour.pos = transform.transformCoordinates(
m_this.gcs(), m_this.layer().map().gcs(), contour.pos, 3);
m_origin = new Float32Array(m_this.style.get('origin')(contour.pos));
if (m_origin[0] || m_origin[1] || m_origin[2]) {
for (i = 0; i < contour.pos.length; i += 3) {
contour.pos[i] -= m_origin[0];
contour.pos[i + 1] -= m_origin[1];
contour.pos[i + 2] -= m_origin[2];
}
}
m_modelViewUniform.setOrigin(m_origin);

posBuf = util.getGeomBuffer(geom, 'pos', numPts * 3);
opacityBuf = util.getGeomBuffer(geom, 'opacity', numPts);
valueBuf = util.getGeomBuffer(geom, 'value', numPts);
Expand Down Expand Up @@ -128,7 +140,6 @@ var webgl_contourFeature = function (arg) {
mat = vgl.material(),
tex = vgl.lookupTable(),
geom = vgl.geometryData(),
modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
projectionUniform = new vgl.projectionUniform('projectionMatrix'),
samplerUniform = new vgl.uniform(vgl.GL.INT, 'sampler2d'),
vertexShader = createVertexShader(),
Expand All @@ -142,6 +153,7 @@ var webgl_contourFeature = function (arg) {
sourceOpacity = vgl.sourceDataAnyfv(
1, vgl.vertexAttributeKeysIndexed.Two, {'name': 'opacity'}),
primitive = new vgl.triangles();
m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix');

s_init.call(m_this, arg);
m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw});
Expand All @@ -150,7 +162,7 @@ var webgl_contourFeature = function (arg) {
prog.addVertexAttribute(valueAttr, vgl.vertexAttributeKeysIndexed.One);
prog.addVertexAttribute(opacityAttr, vgl.vertexAttributeKeysIndexed.Two);

prog.addUniform(modelViewUniform);
prog.addUniform(m_modelViewUniform);
prog.addUniform(projectionUniform);
m_minColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'minColor');
prog.addUniform(m_minColorUniform);
Expand Down
16 changes: 14 additions & 2 deletions src/webgl/lineFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ var webgl_lineFeature = function (arg) {
m_flagsUniform,
m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw,
m_geometry,
m_origin,
m_modelViewUniform,
s_init = this._init,
s_update = this._update;

Expand Down Expand Up @@ -186,6 +188,16 @@ var webgl_lineFeature = function (arg) {

position = transform.transformCoordinates(
m_this.gcs(), m_this.layer().map().gcs(), position, 3);
m_origin = new Float32Array(m_this.style.get('origin')(position));
if (m_origin[0] || m_origin[1] || m_origin[2]) {
for (i = 0; i < position.length; i += 3) {
position[i] -= m_origin[0];
position[i + 1] -= m_origin[1];
position[i + 2] -= m_origin[2];
}
}
m_modelViewUniform.setOrigin(m_origin);

len = numSegments * orderLen;
posBuf = util.getGeomBuffer(geom, 'pos', len * 3);
prevBuf = util.getGeomBuffer(geom, 'prev', len * 3);
Expand Down Expand Up @@ -381,7 +393,6 @@ var webgl_lineFeature = function (arg) {
strkColorAttr = vgl.vertexAttribute('strokeColor'),
strkOpacityAttr = vgl.vertexAttribute('strokeOpacity'),
// Shader uniforms
mviUnif = new vgl.modelViewUniform('modelViewMatrix'),
prjUnif = new vgl.projectionUniform('projectionMatrix'),
geom = vgl.geometryData(),
// Sources
Expand All @@ -402,6 +413,7 @@ var webgl_lineFeature = function (arg) {
1, vgl.vertexAttributeKeysIndexed.Three, {name: 'strokeOpacity'}),
// Primitive indices
triangles = vgl.triangles();
m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix');

m_pixelWidthUnif = new vgl.floatUniform(
'pixelWidth', 1.0 / m_this.renderer().width());
Expand All @@ -424,7 +436,7 @@ var webgl_lineFeature = function (arg) {
prog.addVertexAttribute(farAttr, vgl.vertexAttributeKeysIndexed.Six);
prog.addVertexAttribute(flagsAttr, vgl.vertexAttributeKeysIndexed.Seven);

prog.addUniform(mviUnif);
prog.addUniform(m_modelViewUniform);
prog.addUniform(prjUnif);
prog.addUniform(m_pixelWidthUnif);
prog.addUniform(m_aspectUniform);
Expand Down
16 changes: 13 additions & 3 deletions src/webgl/pointFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ var webgl_pointFeature = function (arg) {
* 'triangle' seems to be fastest on low-powered hardware, but 'square'
* visits fewer fragments. */
m_primitiveShape = 'sprite', // arg can change this, below
m_modelViewUniform,
m_origin,
s_init = this._init,
s_update = this._update,
s_updateStyleFromArray = this.updateStyleFromArray;
Expand Down Expand Up @@ -167,6 +169,15 @@ var webgl_pointFeature = function (arg) {
}
position = transform.transformCoordinates(
m_this.gcs(), m_this.layer().map().gcs(), position, 3);
m_origin = new Float32Array(m_this.style.get('origin')(position));
if (m_origin[0] || m_origin[1] || m_origin[2]) {
for (i = i3 = 0; i < numPts; i += 1, i3 += 3) {
position[i3] -= m_origin[0];
position[i3 + 1] -= m_origin[1];
position[i3 + 2] -= m_origin[2];
}
}
m_modelViewUniform.setOrigin(m_origin);

posBuf = util.getGeomBuffer(geom, 'pos', vpf * numPts * 3);

Expand Down Expand Up @@ -363,7 +374,6 @@ var webgl_pointFeature = function (arg) {
strokeAttr = vgl.vertexAttribute('stroke'),
fillOpacityAttr = vgl.vertexAttribute('fillOpacity'),
strokeOpacityAttr = vgl.vertexAttribute('strokeOpacity'),
modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
projectionUniform = new vgl.projectionUniform('projectionMatrix'),
mat = vgl.material(),
blend = vgl.blend(),
Expand All @@ -388,6 +398,7 @@ var webgl_pointFeature = function (arg) {
sourceStrokeOpacity = vgl.sourceDataAnyfv(
1, vgl.vertexAttributeKeysIndexed.Nine, {'name': 'strokeOpacity'}),
primitive = new vgl.triangles();
m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix', m_origin);

if (m_primitiveShape === 'sprite') {
primitive = new vgl.points();
Expand All @@ -401,7 +412,6 @@ var webgl_pointFeature = function (arg) {
s_init.call(m_this, arg);
m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw});

// TODO: Right now this is ugly but we will fix it.
prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position);
if (m_primitiveShape !== 'sprite') {
prog.addVertexAttribute(unitAttr, vgl.vertexAttributeKeysIndexed.One);
Expand All @@ -418,7 +428,7 @@ var webgl_pointFeature = function (arg) {

prog.addUniform(m_pixelWidthUniform);
prog.addUniform(m_aspectUniform);
prog.addUniform(modelViewUniform);
prog.addUniform(m_modelViewUniform);
prog.addUniform(projectionUniform);

prog.addShader(fragmentShader);
Expand Down
14 changes: 9 additions & 5 deletions src/webgl/polygonFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ var webgl_polygonFeature = function (arg) {
m_mapper = vgl.mapper(),
m_material = vgl.material(),
m_geometry,
m_origin,
m_modelViewUniform,
s_init = this._init,
s_update = this._update,
m_builtOnce,
Expand Down Expand Up @@ -167,6 +169,8 @@ var webgl_polygonFeature = function (arg) {
geom.primitive(0).setIndices(indices);
}
m_geometry = {items: items, numPts: numPts};
m_origin = new Float32Array(m_this.style.get('origin')(items));
m_modelViewUniform.setOrigin(m_origin);
} else {
items = m_geometry.items;
numPts = m_geometry.numPts;
Expand Down Expand Up @@ -214,9 +218,9 @@ var webgl_polygonFeature = function (arg) {
} else {
j = items[k].triangles[i] * 3;
if (!onlyStyle) {
posBuf[d3] = vertices[j];
posBuf[d3 + 1] = vertices[j + 1];
posBuf[d3 + 2] = vertices[j + 2];
posBuf[d3] = vertices[j] - m_origin[0];
posBuf[d3 + 1] = vertices[j + 1] - m_origin[1];
posBuf[d3 + 2] = vertices[j + 2] - m_origin[2];
indices[d] = i;
}
if (!uniform && fillColorVal === undefined) {
Expand Down Expand Up @@ -255,7 +259,6 @@ var webgl_polygonFeature = function (arg) {
posAttr = vgl.vertexAttribute('pos'),
fillColorAttr = vgl.vertexAttribute('fillColor'),
fillOpacityAttr = vgl.vertexAttribute('fillOpacity'),
modelViewUniform = new vgl.modelViewUniform('modelViewMatrix'),
projectionUniform = new vgl.projectionUniform('projectionMatrix'),
vertexShader = createVertexShader(),
fragmentShader = createFragmentShader(),
Expand All @@ -267,12 +270,13 @@ var webgl_polygonFeature = function (arg) {
sourceFillOpacity = vgl.sourceDataAnyfv(
1, vgl.vertexAttributeKeysIndexed.Three, {'name': 'fillOpacity'}),
trianglePrimitive = vgl.triangles();
m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix');

prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position);
prog.addVertexAttribute(fillColorAttr, vgl.vertexAttributeKeysIndexed.Two);
prog.addVertexAttribute(fillOpacityAttr, vgl.vertexAttributeKeysIndexed.Three);

prog.addUniform(modelViewUniform);
prog.addUniform(m_modelViewUniform);
prog.addUniform(projectionUniform);

prog.addShader(fragmentShader);
Expand Down
2 changes: 1 addition & 1 deletion tests/external-data/base-images.tgz.sha512
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3b3eb2d6d4bf78cd9ff98319a7745ca7a3c115ec8d47a706165e5bdb224e8e49ba122acacb647ae4edb994fe3e3ab7e1456bfa1577c7dd0fe6e1b524f37d186d
28a26c252b7e013ad542fb3be0286f22b153b6bb8a791e3aef118c54944094807f041e1b4073a02c7a14f45848740dd63e5c6f20141e8801db8a0594ffb8efc5

0 comments on commit d0aa6a3

Please sign in to comment.