diff --git a/src/markerFeature.js b/src/markerFeature.js index b28290c4b8..feb5ea08a6 100644 --- a/src/markerFeature.js +++ b/src/markerFeature.js @@ -18,7 +18,7 @@ var pointFeature = require('./pointFeature'); * * @typedef {geo.feature.styleSpec} geo.markerFeature.styleSpec * @extends geo.feature.styleSpec - * @property {number|Function} [radius=5] Radius of each marker in pixels. + * @property {number|Function} [radius=6.25] Radius of each marker in pixels. * This includes the stroke width if `strokeOffset` is -1, excludes it if * `strokeOffset` is 1, and includes half the stroke width if `strokeOffset` * is 0. Note that is `radiusIncludesStroke` is `false`, this never diff --git a/src/polygonFeature.js b/src/polygonFeature.js index 3ba4589bad..dcd2374a12 100644 --- a/src/polygonFeature.js +++ b/src/polygonFeature.js @@ -15,6 +15,45 @@ var transform = require('./transform'); * style options. */ +/** + * Style specification for a polygon pattern. + * + * @typedef {geo.polygonPattern} geo.polygonPattern + * @property {geo.geoColor} [fillColor] RGBA fill color. Default is polygon + * strokeColor and strokeOpacity. + * @property {geo.geoColor} [strokeColor] RGBA stroke color. Default is + * polygon fillColor and fillOpacity. + * @property {number} [strokeWidth=1.25] The weight of the pattern marker's + * stroke in pixels. Set this or A on strokeFill to zero to not have a + * stroke. + * @property {number} [strokeOffset=-1] The position of the stroke compared to + * the pattern radius. This can only be -1, 0, or 1 (the sign of the value + * is used). + * @property {boolean} [radiusIncludesStroke=true] If truthy or undefined, the + * `radius` includes the `strokeWidth` based on the `strokeOffset`. If + * defined and falsy, the radius does not include the `strokeWidth`. + * @property {number} [symbol=0] One of the predefined symbol numbers. This is + * one of `geo.markerFeature.symbols`. + * @property {number|number[]} [symbolValue=0] A value the affects the + * appearance of the symbol. Some symbols can take an array of numbers. + * @property {number} [rotation=0] The rotation of the symbol in clockwise + * radians. + * @property {geo.markerFeature.scaleMode} [scaleWithZoom='none'] This + * determines if the fill, stroke, or both scale with zoom. If set, the + * values for radius and strokeWidth are the values at zoom-level zero. + * @property {boolean} [rotateWithMap=false] If truthy, rotate symbols with the + * map. If falsy, symbol orientation is absolute. + * @property {number} [radius=6.25] Radius of each marker in pixels. This + * includes the stroke width if `strokeOffset` is -1, excludes it if + * `strokeOffset` is 1, and includes half the stroke width if `strokeOffset` + * is 0. Note that is `radiusIncludesStroke` is `false`, this never + * includes the stroke width. + * @property {number} [spacing=20] Spacing in pixels between pattern symbols; + * scaled if either radius or strokeWidth is scaled. If positive, patterns + * are on a square grid. If negative, patterns are on a triangular grid. + * @property {number[]} [origin=[0, 0]] Origin of the pattern. + */ + /** * Style specification for a polygon feature. * @@ -35,6 +74,9 @@ var transform = require('./transform'); * 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. + * @property {geo.polygonPattern|Function} [pattern] Pattern to apply to each + * polygon. Each polygon can be distinct, but the pattern is uniform across + * any one polygon. */ /** diff --git a/src/util/common.js b/src/util/common.js index 0dfd27941a..93fb8c80ac 100644 --- a/src/util/common.js +++ b/src/util/common.js @@ -1506,6 +1506,27 @@ var util = { return d; }, + /** + * Pack an array of three numbers and one boolean into a single float. Each + * numerical value is either undefined or on the scale of [0, 1] and is + * mapped to an integer range of [0, 250]. + * + * @param {number|number[]} value A single value or an array of up to four + * values where the first three values are numbers and the last is a + * boolean. + * @returns {number} A packed number. + */ + packFloats: function (value) { + if (!value.length) { + return value === undefined ? 0 : Math.floor(Math.abs(value) * 250) + 1; + } + return ( + (value[0] === undefined ? 0 : Math.floor(Math.abs(value[0]) * 250) + 1) + + (value[1] === undefined ? 0 : Math.floor(Math.abs(value[1]) * 250) + 1) * 252 + + (value[2] === undefined ? 0 : Math.floor(Math.abs(value[2]) * 250) + 1) * 252 * 252 + ) * (value[3] ? -1 : 1); + }, + /////////////////////////////////////////////////////////////////////////// /* * Utility member properties. diff --git a/src/webgl/markerFeature.js b/src/webgl/markerFeature.js index 77d354b16a..24e56fcf3c 100644 --- a/src/webgl/markerFeature.js +++ b/src/webgl/markerFeature.js @@ -74,27 +74,6 @@ var webgl_markerFeature = function (arg) { return shader; } - /** - * Pack an array of three numbers and one boolean into a single float. Each - * numerical value is either undefined or on the scale of [0, 1] and is - * mapped to an integer range of [0, 250]. - * - * @param {number|number[]} value A single value or an array of up to four - * values where the first three values are numbers and the last is a - * boolean. - * @returns {number} A packed number. - */ - function packFloats(value) { - if (!value.length) { - return value === undefined ? 0 : Math.floor(Math.abs(value) * 250) + 1; - } - return ( - (value[0] === undefined ? 0 : Math.floor(Math.abs(value[0]) * 250) + 1) + - (value[1] === undefined ? 0 : Math.floor(Math.abs(value[1]) * 250) + 1) * 252 + - (value[2] === undefined ? 0 : Math.floor(Math.abs(value[2]) * 250) + 1) * 252 * 252 - ) * (value[3] ? -1 : 1); - } - /** * Create and style the data needed to render the markers. * @@ -195,7 +174,7 @@ var webgl_markerFeature = function (arg) { ((Math.sign(styleVal.radiusIncludesStroke !== undefined && styleVal.radiusIncludesStroke ? styleVal.strokeOffset : 1) + 1) * 16) + styleVal.symbol * 64); if (styleVal.symbolValue && styleVal.symbol >= markerFeature.symbols.arrow && styleVal.symbol < markerFeature.symbols.arrow + markerFeature.symbols.arrowMax) { - styleVal.symbolValue = packFloats(styleVal.symbolValue); + styleVal.symbolValue = util.packFloats(styleVal.symbolValue); } for (j = 0; j < vpf; j += 1, ivpf += 1, ivpf3 += 3) { if (!onlyStyle) { diff --git a/src/webgl/markerFeatureFS.glsl b/src/webgl/markerFeatureFS.glsl index 96bd20a57f..f1b9312b6d 100644 --- a/src/webgl/markerFeatureFS.glsl +++ b/src/webgl/markerFeatureFS.glsl @@ -256,14 +256,42 @@ vec2 rotationalSymmetry(vec2 pos, int repetitions) { return vec2(cos(ang), sin(ang)) * length(pos); } -void markerFeatureFragment(vec2 pos) { +float markerFeatureFragment(vec3 posAndSpacing) { + vec2 pos = posAndSpacing.xy; + float spacing = posAndSpacing.z; + // square lattice + if (spacing > 0.0) { + pos.x = mod(pos.x + spacing * 0.5, spacing) - spacing * 0.5; + pos.y = mod(pos.y + spacing * 0.5, spacing) - spacing * 0.5; + } + // triangular lattice + if (spacing < 0.0) { + spacing = spacing * -1.0; + float cz = (2.0 * pos.y) / (sqrt(3.0) * spacing); + float cx = pos.x / spacing - 0.5 * cz; + float cy = -cx - cz; + float rx = floor(cx + 0.5); + float ry = floor(cy + 0.5); + float rz = floor(cz + 0.5); + float dx = abs(rx - cx); + float dy = abs(ry - cy); + float dz = abs(rz - cz); + if (dx > dy && dx > dz) { + rx = -ry - rz; + } else if (dy > dz) { + ry = -rx - rz; + } else { + rz = -rx - ry; + } + vec2 center = vec2(spacing * (rx + 0.5 * rz), (sqrt(3.0) * spacing * 0.5) * rz); + pos = pos - center; + } // rad is a value in pixels from the edge of the symbol where negative is // inside the shape float rad = length(pos.xy) - radiusVar; // never allow points outside of the main radius if (rad > 0.0) { - discard; - return; + return 0.0; } // apply clockwise rotation if (rotationVar != 0.0) { @@ -309,8 +337,7 @@ void markerFeatureFragment(vec2 pos) { } if (rad >= 0.0) { - discard; - return; + return 0.0; } // If there is no stroke, the fill region should transition to nothing if (strokeColorVar.a == 0.0 || strokeWidthVar <= 0.0) { @@ -326,13 +353,19 @@ void markerFeatureFragment(vec2 pos) { } else { fillColor = fillColorVar; } + float alpha = 1.0; if (rad <= endStep) { float step = smoothstep(endStep - antialiasDist, endStep, rad); vec4 color = mix(fillColor, strokeColor, step); float step2 = smoothstep(-antialiasDist, 0.0, rad); gl_FragColor = mix(color, vec4(color.rgb, 0.0), step2); + if (color.a > 0.0) + alpha = gl_FragColor.a / color.a; } else { float step = smoothstep(-antialiasDist, 0.0, rad); gl_FragColor = mix(strokeColor, vec4(strokeColor.rgb, 0.0), step); + if (strokeColor.a > 0.0) + alpha = gl_FragColor.a / strokeColor.a; } + return alpha; } diff --git a/src/webgl/markerFeaturePoly.frag b/src/webgl/markerFeaturePoly.frag index 8192976e24..5240db1b7b 100644 --- a/src/webgl/markerFeaturePoly.frag +++ b/src/webgl/markerFeaturePoly.frag @@ -7,5 +7,6 @@ varying vec2 unitVar; // distinct for square/triangle void main() { if (fillColorVar.a == 0.0 && strokeColorVar.a == 0.0) discard; - markerFeatureFragment(unitVar); + if (markerFeatureFragment(vec3(unitVar, 0.0)) == 0.0) + discard; } diff --git a/src/webgl/markerFeatureSprite.frag b/src/webgl/markerFeatureSprite.frag index a88c31d6ff..43c2cb9d93 100644 --- a/src/webgl/markerFeatureSprite.frag +++ b/src/webgl/markerFeatureSprite.frag @@ -10,5 +10,6 @@ void main(void) { discard; // for sprites, convert the position to [-radius,radius],[-radius,radius] vec2 pos = (gl_PointCoord.xy - 0.5) * 2.0 * radiusVar; - markerFeatureFragment(pos); + if (markerFeatureFragment(vec3(pos, 0.0)) == 0.0) + discard; } diff --git a/src/webgl/polygonFeature.js b/src/webgl/polygonFeature.js index 58f595f3a4..a636b08e0f 100644 --- a/src/webgl/polygonFeature.js +++ b/src/webgl/polygonFeature.js @@ -25,8 +25,11 @@ var webgl_polygonFeature = function (arg) { var transform = require('../transform'); var util = require('../util'); var object = require('./object'); + var markerFeature = require('../markerFeature'); var fragmentShader = require('./polygonFeature.frag'); + var fragmentPatternShader = require('./polygonPatternFeature.frag'); var vertexShader = require('./polygonFeature.vert'); + var vertexPatternShader = require('./polygonPatternFeature.vert'); object.call(this); @@ -35,11 +38,11 @@ var webgl_polygonFeature = function (arg) { */ var m_this = this, s_exit = this._exit, - m_actor = vgl.actor(), - m_mapper = vgl.mapper(), - m_material = vgl.material(), + m_actor = null, + m_mapper = null, m_geometry, m_origin, + m_uniforms, m_modelViewUniform, s_init = this._init, s_update = this._update, @@ -48,16 +51,71 @@ var webgl_polygonFeature = function (arg) { function createVertexShader() { var shader = new vgl.shader(vgl.GL.VERTEX_SHADER); - shader.setShaderSource(vertexShader); + if (!m_this._hasPatterns) { + shader.setShaderSource(vertexShader); + } else { + shader.setShaderSource(vertexPatternShader); + } return shader; } function createFragmentShader() { var shader = new vgl.shader(vgl.GL.FRAGMENT_SHADER); - shader.setShaderSource(fragmentShader); + if (!m_this._hasPatterns) { + shader.setShaderSource(fragmentShader); + } else { + shader.setShaderSource(fragmentPatternShader); + } return shader; } + function _resolvePattern(val, func, d, idx, v0) { + if (!val && func === undefined) { + return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + } + const pattern = Array(16); + let fillColor, strokeColor; + if (val === undefined) { + val = func(d, idx); + } + if (val.fillColor === undefined) { + fillColor = util.convertColor(m_this.style.get('strokeColor')(v0, 0, d, idx)); + fillColor.a = m_this.style.get('strokeOpacity')(v0, 0, d, idx); + } else { + fillColor = util.convertColor(val.fillColor); + } + pattern[0] = fillColor.r; + pattern[1] = fillColor.g; + pattern[2] = fillColor.b; + pattern[3] = fillColor.a; + if (val.strokeColor === undefined) { + strokeColor = util.convertColor(m_this.style.get('fillColor')(v0, 0, d, idx)); + strokeColor.a = m_this.style.get('fillOpacity')(v0, 0, d, idx); + } else { + strokeColor = util.convertColor(val.strokeColor); + } + pattern[4] = strokeColor.r; + pattern[5] = strokeColor.g; + pattern[6] = strokeColor.b; + pattern[7] = strokeColor.a; + pattern[8] = val.strokeWidth === undefined ? 1.25 : val.strokeWidth; + pattern[9] = val.radius === undefined ? 6.25 : val.radius; + let scaleWithZoom = val.scaleWithZoom === undefined ? markerFeature.scaleMode.none : val.scaleWithZoom; + scaleWithZoom = markerFeature.scaleMode[scaleWithZoom] || scaleWithZoom; + const strokeOffset = val.strokeOffset === undefined || val.strokeOffset < 0 ? -1 : (val.strokeOffset > 0 ? 1 : 0); + pattern[10] = scaleWithZoom + (!val.rotateWithMap ? 4 : 0) + ((val.radiusIncludeStroke ? strokeOffset : 1) + 1) * 16 + (val.symbol || 0) * 64; + if (val.symbol && val.symbolValue && val.symbol >= markerFeature.symbols.arrow && val.symbol < markerFeature.symbols.arrow + markerFeature.symbols.arrowMax) { + pattern[11] = util.packFloats(val.symbolValue || 0); + } else { + pattern[11] = val.symbolValue || 0; + } + pattern[12] = val.rotation || 0; + pattern[13] = val.spacing === undefined ? 20 : val.spacing; + pattern[14] = val.origin === undefined ? 0 : val.origin[0]; + pattern[15] = val.origin === undefined ? 0 : val.origin[1]; + return pattern; + } + /** * Create and style the triangles needed to render the polygons. * @@ -78,13 +136,16 @@ var webgl_polygonFeature = function (arg) { fillColor, fillColorFunc, fillColorVal, fillOpacity, fillOpacityFunc, fillOpacityVal, fillFunc, fillVal, + patternFunc, patternVal, pattern, + patternFillColor, patternStrokeColor, + patternSymbolProps, patternPositionProps, uniformFunc, uniformVal, uniform, indices, items = [], itemsk, itemsktri, target_gcs = m_this.gcs(), map_gcs = m_this.layer().map().gcs(), numPts = 0, - geom = m_mapper.geometryData(), + geom, color, opacity, fill, d, d3, vertices, i, j, k, n, record, item, itemIndex, original; @@ -96,6 +157,14 @@ var webgl_polygonFeature = function (arg) { fillVal = util.isFunction(m_this.style('fill')) ? undefined : fillFunc(); uniformFunc = m_this.style.get('uniformPolygon'); uniformVal = util.isFunction(m_this.style('uniformPolygon')) ? undefined : uniformFunc(); + patternFunc = m_this.style.get('pattern'); + patternVal = util.isFunction(m_this.style('pattern')) ? undefined : (patternFunc() || null); + if (patternVal !== null && m_this._hasPatterns !== true) { + m_this.renderer().contextRenderer().removeActor(m_actor); + m_actor = null; + m_this._init(true); + } + geom = m_mapper.geometryData(); if (!onlyStyle) { posFunc = m_this.style.get('position'); @@ -184,6 +253,12 @@ var webgl_polygonFeature = function (arg) { d = d3 = 0; color = fillColorVal; fill = fillVal; + if (m_this._hasPatterns) { + patternFillColor = util.getGeomBuffer(geom, 'patternFillColor', numPts * 4); + patternStrokeColor = util.getGeomBuffer(geom, 'patternStrokeColor', numPts * 4); + patternSymbolProps = util.getGeomBuffer(geom, 'patternSymbolProps', numPts * 4); + patternPositionProps = util.getGeomBuffer(geom, 'patternPositionProps', numPts * 4); + } for (k = 0; k < items.length; k += 1) { itemsk = items[k]; itemsktri = itemsk.triangles; @@ -208,6 +283,11 @@ var webgl_polygonFeature = function (arg) { if (!fill) { opacity = 0; } + if (m_this._hasPatterns) { + if ((pattern === undefined && patternVal) || patternVal === undefined) { + pattern = _resolvePattern(patternVal, patternFunc, item, itemIndex, vertices[0]); + } + } if (uniform && onlyStyle && itemsk.uniform && itemsk.color && color.r === itemsk.color.r && color.g === itemsk.color.g && color.b === itemsk.color.b && opacity === itemsk.opacity) { @@ -240,6 +320,24 @@ var webgl_polygonFeature = function (arg) { } fillOpacity[d] = opacity; } + if (m_this._hasPatterns) { + patternFillColor[d * 4] = pattern[0]; + patternFillColor[d * 4 + 1] = pattern[1]; + patternFillColor[d * 4 + 2] = pattern[2]; + patternFillColor[d * 4 + 3] = pattern[3]; + patternStrokeColor[d * 4] = pattern[4]; + patternStrokeColor[d * 4 + 1] = pattern[5]; + patternStrokeColor[d * 4 + 2] = pattern[6]; + patternStrokeColor[d * 4 + 3] = pattern[7]; + patternSymbolProps[d * 4] = pattern[8]; + patternSymbolProps[d * 4 + 1] = pattern[9]; + patternSymbolProps[d * 4 + 2] = pattern[10]; + patternSymbolProps[d * 4 + 3] = pattern[11]; + patternPositionProps[d * 4] = pattern[12]; + patternPositionProps[d * 4 + 1] = pattern[13]; + patternPositionProps[d * 4 + 2] = pattern[14]; + patternPositionProps[d * 4 + 3] = pattern[15]; + } } if (uniform || itemsk.uniform) { itemsk.uniform = uniform; @@ -254,6 +352,12 @@ var webgl_polygonFeature = function (arg) { } else { m_mapper.updateSourceBuffer('fillOpacity'); m_mapper.updateSourceBuffer('fillColor'); + if (m_this._hasPatterns) { + m_mapper.updateSourceBuffer('patternFillColor'); + m_mapper.updateSourceBuffer('patternStrokeColor'); + m_mapper.updateSourceBuffer('patternSymbolProps'); + m_mapper.updateSourceBuffer('patternPositionProps'); + } } } @@ -264,6 +368,7 @@ var webgl_polygonFeature = function (arg) { * feature. */ this._init = function (arg) { + m_this._hasPatterns = (arg === true); var prog = vgl.shaderProgram(), posAttr = vgl.vertexAttribute('pos'), fillColorAttr = vgl.vertexAttribute('fillColor'), @@ -278,28 +383,65 @@ var webgl_polygonFeature = function (arg) { 3, vgl.vertexAttributeKeysIndexed.Two, {name: 'fillColor'}), sourceFillOpacity = vgl.sourceDataAnyfv( 1, vgl.vertexAttributeKeysIndexed.Three, {name: 'fillOpacity'}), - trianglePrimitive = vgl.triangles(); + trianglePrimitive = vgl.triangles(), + mat = vgl.material(); m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix'); prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position); prog.addVertexAttribute(fillColorAttr, vgl.vertexAttributeKeysIndexed.Two); prog.addVertexAttribute(fillOpacityAttr, vgl.vertexAttributeKeysIndexed.Three); - + if (m_this._hasPatterns) { + var patternFillColorAttr = vgl.vertexAttribute('patternFillColor'), + patternStrokeColorAttr = vgl.vertexAttribute('patternStrokeColor'), + patternSymbolPropsAttr = vgl.vertexAttribute('patternSymbolProps'), + patternPositionPropsAttr = vgl.vertexAttribute('patternPositionProps'); + prog.addVertexAttribute(patternFillColorAttr, vgl.vertexAttributeKeysIndexed.Four); + prog.addVertexAttribute(patternStrokeColorAttr, vgl.vertexAttributeKeysIndexed.Five); + prog.addVertexAttribute(patternSymbolPropsAttr, vgl.vertexAttributeKeysIndexed.Six); + prog.addVertexAttribute(patternPositionPropsAttr, vgl.vertexAttributeKeysIndexed.Seven); + } prog.addUniform(m_modelViewUniform); prog.addUniform(projectionUniform); prog.addShader(fragmentShader); prog.addShader(vertexShader); - m_material.addAttribute(prog); - m_material.addAttribute(blend); + mat.addAttribute(prog); + mat.addAttribute(blend); - m_actor.setMaterial(m_material); + m_mapper = vgl.mapper(); + m_actor = vgl.actor(); + m_actor.setMaterial(mat); m_actor.setMapper(m_mapper); geom.addSource(sourcePositions); geom.addSource(sourceFillColor); geom.addSource(sourceFillOpacity); + if (m_this._hasPatterns) { + var uniforms = { + pixelWidth: vgl.GL.FLOAT, + aspect: vgl.GL.FLOAT, + zoom: vgl.GL.FLOAT, + rotationUniform: vgl.GL.FLOAT + }; + m_uniforms = {}; + Object.keys(uniforms).forEach((key) => { + m_uniforms[key] = new vgl.uniform(uniforms[key], key); + prog.addUniform(m_uniforms[key]); + }); + var sourcePatternFillColor = vgl.sourceDataAnyfv( + 4, vgl.vertexAttributeKeysIndexed.Four, {name: 'patternFillColor'}), + sourcePatternStrokeColor = vgl.sourceDataAnyfv( + 4, vgl.vertexAttributeKeysIndexed.Five, {name: 'patternStrokeColor'}), + sourcePatternSymbolProps = vgl.sourceDataAnyfv( + 4, vgl.vertexAttributeKeysIndexed.Six, {name: 'patternSymbolProps'}), + sourcePatternPositionProps = vgl.sourceDataAnyfv( + 4, vgl.vertexAttributeKeysIndexed.Seven, {name: 'patternPositionProps'}); + geom.addSource(sourcePatternFillColor); + geom.addSource(sourcePatternStrokeColor); + geom.addSource(sourcePatternSymbolProps); + geom.addSource(sourcePatternPositionProps); + } geom.addPrimitive(trianglePrimitive); /* We don't need vgl to compute bounds, so make the geo.computeBounds just * set them to 0. */ @@ -308,7 +450,9 @@ var webgl_polygonFeature = function (arg) { }; m_mapper.setGeometryData(geom); - s_init.call(m_this, arg); + if (arg !== true) { + s_init.call(m_this, arg); + } }; /** @@ -359,6 +503,13 @@ var webgl_polygonFeature = function (arg) { m_this._build(); } + if (m_this._hasPatterns) { + // Update uniforms + m_uniforms.pixelWidth.set(2.0 / m_this.renderer().width()); + m_uniforms.aspect.set(m_this.renderer().width() / m_this.renderer().height()); + m_uniforms.zoom.set(m_this.renderer().map().zoom()); + m_uniforms.rotationUniform.set(m_this.renderer().map().rotation()); + } m_actor.setVisible(m_this.visible()); m_actor.material().setBinNumber(m_this.bin()); m_this.updateTime().modified(); @@ -373,6 +524,8 @@ var webgl_polygonFeature = function (arg) { m_updateAnimFrameRef = null; } m_this.renderer().contextRenderer().removeActor(m_actor); + m_actor = null; + m_uniforms = {}; s_exit(); }; diff --git a/src/webgl/polygonPatternFeature.frag b/src/webgl/polygonPatternFeature.frag new file mode 100644 index 0000000000..91ac65d638 --- /dev/null +++ b/src/webgl/polygonPatternFeature.frag @@ -0,0 +1,15 @@ +/* polygonFeature fragment shader */ + +$markerFeatureFS + +varying vec4 polyFillColorVar; +varying vec3 patternPosVar; + +void main () { + + float used = 0.0; + if (fillColorVar.a != 0.0 || strokeColorVar.a != 0.0) + used = markerFeatureFragment(patternPosVar); + if (used != 1.0) + gl_FragColor = polyFillColorVar * (1.0 - used) + gl_FragColor * used; +} diff --git a/src/webgl/polygonPatternFeature.vert b/src/webgl/polygonPatternFeature.vert new file mode 100644 index 0000000000..3233fc676c --- /dev/null +++ b/src/webgl/polygonPatternFeature.vert @@ -0,0 +1,91 @@ +/* polygonPatternFeature vertex shader */ + +#ifdef GL_ES + precision highp float; +#endif +uniform float pixelWidth; +uniform float aspect; +uniform float zoom; +uniform float rotationUniform; +attribute vec3 pos; +attribute vec3 fillColor; +attribute float fillOpacity; +attribute vec4 patternFillColor; +attribute vec4 patternStrokeColor; +/* Symbol props are strokeWidth, radius, symbol + flags, symbolValue */ +attribute vec4 patternSymbolProps; +/* Position props are rotation, spacing, origin x, origin y */ +attribute vec4 patternPositionProps; +uniform mat4 modelViewMatrix; +uniform mat4 projectionMatrix; +varying vec4 polyFillColorVar; +varying float radiusVar; +varying vec4 fillColorVar; +varying vec4 strokeColorVar; +varying float strokeWidthVar; +varying float symbolVar; /* contains some bit fields */ +varying float symbolValueVar; +varying float rotationVar; +varying vec3 patternPosVar; +const float PI = 3.14159265358979323846264; + +void main(void) +{ + polyFillColorVar = vec4(fillColor, fillOpacity); + /* This is _nearly_ the same as what is in markerFeatureVS.glsl, but the + * attributes are named differently. */ + radiusVar = patternSymbolProps.y; + strokeWidthVar = patternSymbolProps.x; + int scaleMode = int(mod(patternSymbolProps.z, 4.0)); + float strokeOffset = mod(floor(patternSymbolProps.z / 16.0), 4.0) - 1.0; + if (radiusVar < 0.0 || (patternFillColor.a < 0.0 && patternStrokeColor.a < 0.0)) { + radiusVar = 0.0; + } else { + radiusVar += (strokeOffset + 1.0) / 2.0 * strokeWidthVar; + if (scaleMode == 1) { // fill + radiusVar = (radiusVar - strokeWidthVar) * exp2(zoom) + strokeWidthVar; + } else if (scaleMode == 2) { // stroke + radiusVar += strokeWidthVar * (exp2(zoom) - 1.0); + strokeWidthVar *= exp2(zoom); + } else if (scaleMode == 3) { // all + radiusVar *= exp2(zoom); + strokeWidthVar *= exp2(zoom); + } + } + fillColorVar = patternFillColor; + strokeColorVar = patternStrokeColor; + symbolVar = patternSymbolProps.z; + symbolValueVar = patternSymbolProps.w; + rotationVar = patternPositionProps.x; + if (bool(mod(floor(symbolVar / 4.0), 2.0))) { + rotationVar += rotationUniform; + } + /* This is distinct for polygonPattern */ + vec4 clipPos = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1.0); + if (clipPos.w != 0.0) { + clipPos = clipPos / clipPos.w; + } + if (radiusVar > 0.0) { + vec4 origPos = projectionMatrix * modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0); + if (origPos.w != 0.0) + origPos = origPos / origPos.w; + patternPosVar.x = (clipPos.x - origPos.x) / pixelWidth; + patternPosVar.y = -(clipPos.y - origPos.y) / pixelWidth / aspect; + float spacing = patternPositionProps.y; + if (scaleMode == 0) { + patternPosVar.x -= patternPositionProps.z; + patternPosVar.y -= patternPositionProps.w; + } else { + spacing = spacing * exp2(zoom); + patternPosVar.x -= patternPositionProps.z * exp2(zoom); + patternPosVar.y -= patternPositionProps.w * exp2(zoom); + } + if (rotationUniform != 0.0) { + float cosR = cos(rotationUniform); + float sinR = sin(rotationUniform); + patternPosVar.xy = vec2(cosR * patternPosVar.x + sinR * patternPosVar.y, sinR * patternPosVar.x - cosR * patternPosVar.y); + } + patternPosVar.z = spacing; + } + gl_Position = clipPos; +} diff --git a/tests/cases/polygonFeature.js b/tests/cases/polygonFeature.js index ee139fc183..dde5c55100 100644 --- a/tests/cases/polygonFeature.js +++ b/tests/cases/polygonFeature.js @@ -62,6 +62,24 @@ describe('geo.polygonFeature', function () { return d.uniformPolygon !== undefined ? d.uniformPolygon : false; } }; + var patternStyle = function (d, idx, poly, polyidx) { + stylesVisited.push({style: 'pattern', params: [d, idx, poly, polyidx]}); + return { + fillColor: '#0000FF40', + strokeColor: '#FFFF00C0', + strokeWidth: 1.25 / 16, + strokeOffset: 1, + radiusIncludesStroke: true, + symbol: geo.markerFeature.symbols.star5, + symbolValue: 0.6, + rotation: -Math.PI / 2, + scaleWithZoom: geo.markerFeature.scaleMode.all, + rotateWithMap: false, + radius: 30.25 / 32, + spacing: -70 / 32, + origin: [0, 0], + }; + }; describe('create', function () { it('create function', function () { @@ -439,6 +457,16 @@ describe('geo.polygonFeature', function () { return vgl.mockCounts().bufferSubData >= (glCounts.bufferSubData || 0) + 1 && buildTime !== polygons.buildTime().timestamp(); }); + it('update to a pattern style', function () { + polygons.style('pattern', patternStyle); + glCounts = $.extend({}, vgl.mockCounts()); + buildTime = polygons.buildTime().timestamp(); + polygons.draw(); + }); + waitForIt('next render gl E', function () { + return vgl.mockCounts().bufferSubData >= (glCounts.bufferSubData || 0) + 1 && + buildTime !== polygons.buildTime().timestamp(); + }); it('poor data', function () { polygons.data([undefined, testPolygons[1]]); polygons.style('fill', true); @@ -446,7 +474,7 @@ describe('geo.polygonFeature', function () { buildTime = polygons.buildTime().timestamp(); polygons.draw(); }); - waitForIt('next render gl E', function () { + waitForIt('next render gl F', function () { return vgl.mockCounts().bufferData >= (glCounts.bufferData || 0) + 1 && buildTime !== polygons.buildTime().timestamp(); });