ShaderBuilder / add support for color interpolation in parse

This commit is contained in:
Olivier Guyot
2019-10-22 18:13:41 +02:00
parent a29fc016f5
commit 85c3aae454
2 changed files with 52 additions and 11 deletions

View File

@@ -50,7 +50,7 @@ export function formatColor(color) {
* Possible inferred types from a given value or expression * Possible inferred types from a given value or expression
* @enum {number} * @enum {number}
*/ */
const ValueTypes = { export const ValueTypes = {
UNKNOWN: -1, UNKNOWN: -1,
NUMBER: 0, NUMBER: 0,
STRING: 1, STRING: 1,
@@ -163,7 +163,7 @@ export function check(value) {
} }
// check operator arguments // check operator arguments
if (Array.isArray(value)) { if (Array.isArray(value) && typeof value[0] === 'string') {
switch (value[0]) { switch (value[0]) {
case 'get': case 'get':
case 'var': case 'var':
@@ -226,22 +226,35 @@ export function check(value) {
* For attributes, a prefix must be specified so that the attributes can either be written as `a_name` or `v_name` in * For attributes, a prefix must be specified so that the attributes can either be written as `a_name` or `v_name` in
* the final assignment string (depending on whether we're outputting a vertex or fragment shader). * the final assignment string (depending on whether we're outputting a vertex or fragment shader).
* *
* If a wrong value type is supplied to an operator (i. e. using colors with the `clamp` operator), an exception
* will be thrown.
*
* Note that by default, the `string` value type will be given precedence over `color`, so for example the
* `'yellow'` literal value will be parsed as a `string` while being a valid CSS color. This can be changed with
* the `typeHint` optional parameter which disambiguates what kind of value is expected.
*
* @param {import("../style/LiteralStyle").ExpressionValue} value Either literal or an operator. * @param {import("../style/LiteralStyle").ExpressionValue} value Either literal or an operator.
* @param {Array<string>} attributes Array containing the attribute names **without a prefix**; * @param {Array<string>} attributes Array containing the attribute names **without a prefix**;
* it is passed along recursively * it is passed along recursively
* @param {string} attributePrefix Prefix added to attribute names in the final output (typically `a_` or `v_`). * @param {string} attributePrefix Prefix added to attribute names in the final output (typically `a_` or `v_`).
* @param {Array<string>} variables Array containing the variable names **without a prefix**; * @param {Array<string>} variables Array containing the variable names **without a prefix**;
* it is passed along recursively * it is passed along recursively
* @param {ValueTypes} [typeHint] Hint for inferred type
* @returns {string} Assignment string. * @returns {string} Assignment string.
*/ */
export function parse(value, attributes, attributePrefix, variables) { export function parse(value, attributes, attributePrefix, variables, typeHint) {
check(value); check(value);
const v = value; const v = value;
function p(value) { function p(value) {
return parse(value, attributes, attributePrefix, variables); return parse(value, attributes, attributePrefix, variables);
} }
if (Array.isArray(v)) { function pC(value) {
return parse(value, attributes, attributePrefix, variables, ValueTypes.COLOR);
}
// operator
if (Array.isArray(v) && typeof value[0] === 'string') {
switch (v[0]) { switch (v[0]) {
// reading operators // reading operators
case 'get': case 'get':
@@ -263,6 +276,10 @@ export function parse(value, attributes, attributePrefix, variables) {
case 'clamp': return `clamp(${p(v[1])}, ${p(v[2])}, ${p(v[3])})`; case 'clamp': return `clamp(${p(v[1])}, ${p(v[2])}, ${p(v[3])})`;
case 'stretch': return `(clamp(${p(v[1])}, ${p(v[2])}, ${p(v[3])}) * ((${p(v[5])} - ${p(v[4])}) / (${p(v[3])} - ${p(v[2])})) + ${p(v[4])})`; case 'stretch': return `(clamp(${p(v[1])}, ${p(v[2])}, ${p(v[3])}) * ((${p(v[5])} - ${p(v[4])}) / (${p(v[3])} - ${p(v[2])})) + ${p(v[4])})`;
// color operators
case 'interpolate':
return `mix(${pC(v[2])}, ${pC(v[3])}, ${p(v[1])})`;
// logical operators // logical operators
case '>': case '>':
case '>=': case '>=':
@@ -275,12 +292,14 @@ export function parse(value, attributes, attributePrefix, variables) {
case 'between': case 'between':
return `(${p(v[1])} >= ${p(v[2])} && ${p(v[1])} <= ${p(v[3])} ? 1.0 : 0.0)`; return `(${p(v[1])} >= ${p(v[2])} && ${p(v[1])} <= ${p(v[3])} ? 1.0 : 0.0)`;
default: throw new Error('Unrecognized literal style expression: ' + JSON.stringify(value)); default: throw new Error('Invalid style expression: ' + JSON.stringify(value));
} }
} else if (typeof value === 'number') { } else if (isValueTypeNumber(value)) {
return formatNumber(value); return formatNumber(/** @type {number} */(value));
} else if (isValueTypeString(value) && (typeHint === undefined || typeHint == ValueTypes.STRING)) {
return `"${value}"`;
} else { } else {
throw new Error('Invalid value type in expression: ' + JSON.stringify(value)); return formatColor(/** @type {number[]|string} */(value));
} }
} }

View File

@@ -8,7 +8,8 @@ import {
isValueTypeString, isValueTypeString,
parse, parse,
parseLiteralStyle, parseLiteralStyle,
ShaderBuilder ShaderBuilder,
ValueTypes
} from '../../../../src/ol/webgl/ShaderBuilder.js'; } from '../../../../src/ol/webgl/ShaderBuilder.js';
describe('ol.webgl.ShaderBuilder', function() { describe('ol.webgl.ShaderBuilder', function() {
@@ -270,6 +271,10 @@ void main(void) {
it('does not throw on valid expressions', function(done) { it('does not throw on valid expressions', function(done) {
check(1); check(1);
check('attr');
check('rgba(12, 34, 56, 0.5)');
check([255, 255, 255, 1]);
check([255, 255, 255]);
check(['get', 'myAttr']); check(['get', 'myAttr']);
check(['var', 'myValue']); check(['var', 'myValue']);
check(['time']); check(['time']);
@@ -283,6 +288,8 @@ void main(void) {
check(['==', 10, ['get', 'attr4']]); check(['==', 10, ['get', 'attr4']]);
check(['between', ['get', 'attr4'], -4.0, 5.0]); check(['between', ['get', 'attr4'], -4.0, 5.0]);
check(['!', ['get', 'attr4']]); check(['!', ['get', 'attr4']]);
check(['interpolate', ['get', 'attr4'], 'green', '#3344FF']);
check(['interpolate', 0.2, [10, 20, 30], [255, 255, 255, 1]]);
done(); done();
}); });
@@ -303,6 +310,11 @@ void main(void) {
} catch (e) { } catch (e) {
thrown = true; thrown = true;
} }
try {
check(['interpolate', ['get', 'attr4'], 1, '#3344FF']);
} catch (e) {
thrown = true;
}
expect(thrown).to.be(true); expect(thrown).to.be(true);
}); });
@@ -355,13 +367,16 @@ void main(void) {
attributes = []; attributes = [];
variables = []; variables = [];
prefix = 'a_'; prefix = 'a_';
parseFn = function(value) { parseFn = function(value, type) {
return parse(value, attributes, prefix, variables); return parse(value, attributes, prefix, variables, type);
}; };
}); });
it('parses expressions & literal values', function() { it('parses expressions & literal values', function() {
expect(parseFn(1)).to.eql('1.0'); expect(parseFn(1)).to.eql('1.0');
expect(parseFn('a_random_string')).to.eql('"a_random_string"');
expect(parseFn([255, 127.5, 63.75, 0.1])).to.eql('vec4(1.0, 0.5, 0.25, 0.1)');
expect(parseFn([255, 127.5, 63.75])).to.eql('vec4(1.0, 0.5, 0.25, 1.0)');
expect(parseFn(['get', 'myAttr'])).to.eql('a_myAttr'); expect(parseFn(['get', 'myAttr'])).to.eql('a_myAttr');
expect(parseFn(['var', 'myValue'])).to.eql('u_myValue'); expect(parseFn(['var', 'myValue'])).to.eql('u_myValue');
expect(parseFn(['time'])).to.eql('u_time'); expect(parseFn(['time'])).to.eql('u_time');
@@ -375,10 +390,17 @@ void main(void) {
expect(parseFn(['==', 10, ['get', 'attr4']])).to.eql('(10.0 == a_attr4 ? 1.0 : 0.0)'); expect(parseFn(['==', 10, ['get', 'attr4']])).to.eql('(10.0 == a_attr4 ? 1.0 : 0.0)');
expect(parseFn(['between', ['get', 'attr4'], -4.0, 5.0])).to.eql('(a_attr4 >= -4.0 && a_attr4 <= 5.0 ? 1.0 : 0.0)'); expect(parseFn(['between', ['get', 'attr4'], -4.0, 5.0])).to.eql('(a_attr4 >= -4.0 && a_attr4 <= 5.0 ? 1.0 : 0.0)');
expect(parseFn(['!', ['get', 'attr4']])).to.eql('(a_attr4 > 0.0 ? 0.0 : 1.0)'); expect(parseFn(['!', ['get', 'attr4']])).to.eql('(a_attr4 > 0.0 ? 0.0 : 1.0)');
expect(parseFn(['interpolate', ['get', 'attr4'], [255, 255, 255, 1], 'transparent'])).to.eql(
'mix(vec4(1.0, 1.0, 1.0, 1.0), vec4(0.0, 0.0, 0.0, 0.0), a_attr4)');
expect(attributes).to.eql(['myAttr', 'size', 'attr2', 'attr3', 'attr4']); expect(attributes).to.eql(['myAttr', 'size', 'attr2', 'attr3', 'attr4']);
expect(variables).to.eql(['myValue']); expect(variables).to.eql(['myValue']);
}); });
it('gives precedence to the string type unless asked otherwise', function() {
expect(parseFn('lightgreen')).to.eql('"lightgreen"');
expect(parseFn('lightgreen', ValueTypes.COLOR)).to.eql('vec4(0.5647058823529412, 0.9333333333333333, 0.5647058823529412, 1.0)');
});
it('does not register an attribute several times', function() { it('does not register an attribute several times', function() {
parseFn(['get', 'myAttr']); parseFn(['get', 'myAttr']);
parseFn(['var', 'myVar']); parseFn(['var', 'myVar']);