ShaderBuilder / add support for color interpolation in parse
This commit is contained in:
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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']);
|
||||||
|
|||||||
Reference in New Issue
Block a user