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
* @enum {number}
*/
const ValueTypes = {
export const ValueTypes = {
UNKNOWN: -1,
NUMBER: 0,
STRING: 1,
@@ -163,7 +163,7 @@ export function check(value) {
}
// check operator arguments
if (Array.isArray(value)) {
if (Array.isArray(value) && typeof value[0] === 'string') {
switch (value[0]) {
case 'get':
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
* 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 {Array<string>} attributes Array containing the attribute names **without a prefix**;
* it is passed along recursively
* @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**;
* it is passed along recursively
* @param {ValueTypes} [typeHint] Hint for inferred type
* @returns {string} Assignment string.
*/
export function parse(value, attributes, attributePrefix, variables) {
export function parse(value, attributes, attributePrefix, variables, typeHint) {
check(value);
const v = value;
function p(value) {
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]) {
// reading operators
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 '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
case '>':
case '>=':
@@ -275,12 +292,14 @@ export function parse(value, attributes, attributePrefix, variables) {
case 'between':
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') {
return formatNumber(value);
} else if (isValueTypeNumber(value)) {
return formatNumber(/** @type {number} */(value));
} else if (isValueTypeString(value) && (typeHint === undefined || typeHint == ValueTypes.STRING)) {
return `"${value}"`;
} 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,
parse,
parseLiteralStyle,
ShaderBuilder
ShaderBuilder,
ValueTypes
} from '../../../../src/ol/webgl/ShaderBuilder.js';
describe('ol.webgl.ShaderBuilder', function() {
@@ -270,6 +271,10 @@ void main(void) {
it('does not throw on valid expressions', function(done) {
check(1);
check('attr');
check('rgba(12, 34, 56, 0.5)');
check([255, 255, 255, 1]);
check([255, 255, 255]);
check(['get', 'myAttr']);
check(['var', 'myValue']);
check(['time']);
@@ -283,6 +288,8 @@ void main(void) {
check(['==', 10, ['get', 'attr4']]);
check(['between', ['get', 'attr4'], -4.0, 5.0]);
check(['!', ['get', 'attr4']]);
check(['interpolate', ['get', 'attr4'], 'green', '#3344FF']);
check(['interpolate', 0.2, [10, 20, 30], [255, 255, 255, 1]]);
done();
});
@@ -303,6 +310,11 @@ void main(void) {
} catch (e) {
thrown = true;
}
try {
check(['interpolate', ['get', 'attr4'], 1, '#3344FF']);
} catch (e) {
thrown = true;
}
expect(thrown).to.be(true);
});
@@ -355,13 +367,16 @@ void main(void) {
attributes = [];
variables = [];
prefix = 'a_';
parseFn = function(value) {
return parse(value, attributes, prefix, variables);
parseFn = function(value, type) {
return parse(value, attributes, prefix, variables, type);
};
});
it('parses expressions & literal values', function() {
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(['var', 'myValue'])).to.eql('u_myValue');
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(['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(['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(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() {
parseFn(['get', 'myAttr']);
parseFn(['var', 'myVar']);