Merge pull request #10214 from jahow/webgl-layer-boolean-operators

WebGL / Add 'case' operator for style expressions
This commit is contained in:
Andreas Hocevar
2019-10-28 21:11:47 +01:00
committed by GitHub
7 changed files with 326 additions and 83 deletions
+82 -24
View File
@@ -23,10 +23,13 @@ import {asArray, isStringColor} from '../color.js';
* * `['+', value1, value1]` adds `value1` and `value2`
* * `['-', value1, value1]` subtracts `value2` from `value1`
* * `['clamp', value, low, high]` clamps `value` between `low` and `high`
* * `['mod', value1, value1]` returns the result of `value1 % value2` (modulo)
* * `['%', value1, value1]` returns the result of `value1 % value2` (modulo)
* * `['^', value1, value1]` returns the value of `value1` raised to the `value2` power
*
* * Transform operators:
* * `['case', condition1, output1, ...conditionN, outputN, fallback]` selects the first output whose corresponding
* condition evaluates to `true`. If no match is found, returns the `fallback` value.
* All conditions should be `boolean`, output and fallback can be any kind.
* * `['match', input, match1, output1, ...matchN, outputN, fallback]` compares the `input` value against all
* provided `matchX` values, returning the output associated with the first valid match. If no match is found,
* returns the `fallback` value.
@@ -41,14 +44,15 @@ import {asArray, isStringColor} from '../color.js';
* between `output1` and `outputN`.
*
* * Logical operators:
* * `['<', value1, value2]` returns `1` if `value1` is strictly lower than value 2, or `0` otherwise.
* * `['<=', value1, value2]` returns `1` if `value1` is lower than or equals value 2, or `0` otherwise.
* * `['>', value1, value2]` returns `1` if `value1` is strictly greater than value 2, or `0` otherwise.
* * `['>=', value1, value2]` returns `1` if `value1` is greater than or equals value 2, or `0` otherwise.
* * `['==', value1, value2]` returns `1` if `value1` equals value 2, or `0` otherwise.
* * `['!', value1]` returns `0` if `value1` strictly greater than `0`, or `1` otherwise.
* * `['between', value1, value2, value3]` returns `1` if `value1` is contained between `value2` and `value3`
* (inclusively), or `0` otherwise.
* * `['<', value1, value2]` returns `true` if `value1` is strictly lower than value 2, or `false` otherwise.
* * `['<=', value1, value2]` returns `true` if `value1` is lower than or equals value 2, or `false` otherwise.
* * `['>', value1, value2]` returns `true` if `value1` is strictly greater than value 2, or `false` otherwise.
* * `['>=', value1, value2]` returns `true` if `value1` is greater than or equals value 2, or `false` otherwise.
* * `['==', value1, value2]` returns `true` if `value1` equals value 2, or `false` otherwise.
* * `['!=', value1, value2]` returns `true` if `value1` equals value 2, or `false` otherwise.
* * `['!', value1]` returns `false` if `value1` is `true` or greater than `0`, or `true` otherwise.
* * `['between', value1, value2, value3]` returns `true` if `value1` is contained between `value2` and `value3`
* (inclusively), or `false` otherwise.
*
* * Conversion operators:
* * `['array', value1, ...valueN]` creates a numerical array from `number` values; please note that the amount of
@@ -59,6 +63,7 @@ import {asArray, isStringColor} from '../color.js';
*
* Values can either be literals or another operator, as they will be evaluated recursively.
* Literal values can be of the following types:
* * `boolean`
* * `number`
* * `string`
* * {@link module:ol/color~Color}
@@ -252,9 +257,9 @@ function assertNumber(value) {
throw new Error(`A numeric value was expected, got ${JSON.stringify(value)} instead`);
}
}
function assertNumbers(arr) {
for (let i = 0; i < arr.length; i++) {
assertNumber(arr[i]);
function assertNumbers(values) {
for (let i = 0; i < values.length; i++) {
assertNumber(values[i]);
}
}
function assertString(value) {
@@ -287,6 +292,11 @@ function assertArgsEven(args) {
throw new Error(`An even amount of arguments was expected, got ${args} instead`);
}
}
function assertArgsOdd(args) {
if (args.length % 2 === 0) {
throw new Error(`An even amount of arguments was expected, got ${args} instead`);
}
}
function assertUniqueInferredType(args, types) {
if (!isTypeUnique(types)) {
throw new Error(`Could not infer only one type from the following expression: ${JSON.stringify(args)}`);
@@ -349,6 +359,7 @@ Operators['resolution'] = {
return 'u_resolution';
}
};
Operators['*'] = {
getReturnType: function(args) {
return ValueTypes.NUMBER;
@@ -401,7 +412,7 @@ Operators['clamp'] = {
return `clamp(${expressionToGlsl(context, args[0])}, ${min}, ${max})`;
}
};
Operators['mod'] = {
Operators['%'] = {
getReturnType: function(args) {
return ValueTypes.NUMBER;
},
@@ -421,6 +432,7 @@ Operators['^'] = {
return `pow(${expressionToGlsl(context, args[0])}, ${expressionToGlsl(context, args[1])})`;
}
};
Operators['>'] = {
getReturnType: function(args) {
return ValueTypes.BOOLEAN;
@@ -461,16 +473,31 @@ Operators['<='] = {
return `(${expressionToGlsl(context, args[0])} <= ${expressionToGlsl(context, args[1])})`;
}
};
Operators['=='] = {
getReturnType: function(args) {
return ValueTypes.BOOLEAN;
},
toGlsl: function(context, args) {
assertArgsCount(args, 2);
assertNumbers(args);
return `(${expressionToGlsl(context, args[0])} == ${expressionToGlsl(context, args[1])})`;
}
};
function getEqualOperator(operator) {
return {
getReturnType: function(args) {
return ValueTypes.BOOLEAN;
},
toGlsl: function(context, args) {
assertArgsCount(args, 2);
// find common type
let type = ValueTypes.ANY;
for (let i = 0; i < args.length; i++) {
type = type & getValueType(args[i]);
}
if (type === 0) {
throw new Error(`All arguments should be of compatible type, got ${JSON.stringify(args)} instead`);
}
return `(${expressionToGlsl(context, args[0], type)} ${operator} ${expressionToGlsl(context, args[1], type)})`;
}
};
}
Operators['=='] = getEqualOperator('==');
Operators['!='] = getEqualOperator('!=');
Operators['!'] = {
getReturnType: function(args) {
return ValueTypes.BOOLEAN;
@@ -494,6 +521,7 @@ Operators['between'] = {
return `(${value} >= ${min} && ${value} <= ${max})`;
}
};
Operators['array'] = {
getReturnType: function(args) {
return ValueTypes.NUMBER_ARRAY;
@@ -526,6 +554,7 @@ Operators['color'] = {
return `vec${args.length}(${parsedArgs.join(', ')})`;
}
};
Operators['interpolate'] = {
getReturnType: function(args) {
let type = ValueTypes.COLOR | ValueTypes.NUMBER;
@@ -580,7 +609,6 @@ Operators['match'] = {
assertArgsEven(args);
assertArgsMinCount(args, 4);
// compute input/output types
const typeHint = opt_typeHint !== undefined ? opt_typeHint : ValueTypes.ANY;
const outputType = Operators['match'].getReturnType(args) & typeHint;
assertUniqueInferredType(args, outputType);
@@ -596,3 +624,33 @@ Operators['match'] = {
return result;
}
};
Operators['case'] = {
getReturnType: function(args) {
let type = ValueTypes.ANY;
for (let i = 1; i < args.length; i += 2) {
type = type & getValueType(args[i]);
}
type = type & getValueType(args[args.length - 1]);
return type;
},
toGlsl: function(context, args, opt_typeHint) {
assertArgsOdd(args);
assertArgsMinCount(args, 3);
const typeHint = opt_typeHint !== undefined ? opt_typeHint : ValueTypes.ANY;
const outputType = Operators['case'].getReturnType(args) & typeHint;
assertUniqueInferredType(args, outputType);
for (let i = 0; i < args.length - 1; i += 2) {
assertBoolean(args[i]);
}
const fallback = expressionToGlsl(context, args[args.length - 1], outputType);
let result = null;
for (let i = args.length - 3; i >= 0; i -= 2) {
const condition = expressionToGlsl(context, args[i]);
const output = expressionToGlsl(context, args[i + 1], outputType);
result = `(${condition} ? ${output} : ${result || fallback})`;
}
return result;
}
};
+10 -4
View File
@@ -3,7 +3,7 @@
* @module ol/webgl/ShaderBuilder
*/
import {expressionToGlsl, ValueTypes} from '../style/expressions.js';
import {expressionToGlsl, stringToGlsl, ValueTypes} from '../style/expressions.js';
/**
* @typedef {Object} VaryingDescription
@@ -445,8 +445,14 @@ export function parseLiteralStyle(style) {
fragContext.variables.forEach(function(varName) {
builder.addUniform(`float u_${varName}`);
uniforms[`u_${varName}`] = function() {
return style.variables && style.variables[varName] !== undefined ?
style.variables[varName] : 0;
if (!style.variables || style.variables[varName] === undefined) {
throw new Error(`The following variable is missing from the style: ${varName}`);
}
let value = style.variables[varName];
if (typeof value === 'string') {
value = parseFloat(stringToGlsl(vertContext, value));
}
return value !== undefined ? value : -9999999; // to avoid matching with the first string literal
};
});
@@ -481,7 +487,7 @@ export function parseLiteralStyle(style) {
callback: function(feature) {
let value = feature.get(attributeName);
if (typeof value === 'string') {
value = vertContext.stringLiteralsMap[value];
value = parseFloat(stringToGlsl(vertContext, value));
}
return value !== undefined ? value : -9999999; // to avoid matching with the first string literal
}