diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html index 3fe63914b5f8..733b9d6edc37 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html @@ -130,7 +130,7 @@ }); addStyle('Min and Max', { - color : "rgb(min(${POSITION}[0], 0.75) * 255, max(${POSITION}[2], 0.25) * 255, 255)", + color : "rgb(min(${POSITION}.x, 0.75) * 255, max(${POSITION}.z, 0.25) * 255, 255)", pointSize : "5" }); @@ -141,12 +141,11 @@ addStyle('Secondary Color', { color : { - expression : "[${secondaryColor}[0], ${secondaryColor}[1], ${secondaryColor}[2], 1.0]", conditions : [ - ["${id} < 250", "${expression}"], - ["${id} < 500", "${expression} * ${expression}"], - ["${id} < 750", "${expression} / 5.0"], - ["${id} < 1000", "rgb(0, 0, Number(${expression}[0] < 0.5) * 255)"] + ["${id} < 250", "vec4(${secondaryColor}, 1.0)"], + ["${id} < 500", "vec4(${secondaryColor} * ${secondaryColor}, 1.0)"], + ["${id} < 750", "vec4(${secondaryColor} / 5.0, 1.0)"], + ["${id} < 1000", "rgb(0, 0, Number(${secondaryColor}.x < 0.5) * 255)"] ] } }); @@ -159,8 +158,9 @@ show : "${POSITION}[0] > 0.5 || ${POSITION}[1] > 0.5 || ${POSITION}[2] > 0.5" }); +// POSITION contains 0 as its last component, so add 1.0 to make the point cloud opaque addStyle('Color based on position', { - color : "rgb(${POSITION}[0] * 255, ${POSITION}[1] * 255, ${POSITION}[2] * 255)" + color : "vec4(${POSITION}, 1.0)" }); addStyle('Style point size', { diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index d3eaaa8b4f55..17ae36798635 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -1,5 +1,8 @@ /*global define*/ define([ + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartesian4', '../Core/Color', '../Core/defined', '../Core/defineProperties', @@ -9,6 +12,9 @@ define([ '../ThirdParty/jsep', './ExpressionNodeType' ], function( + Cartesian2, + Cartesian3, + Cartesian4, Color, defined, defineProperties, @@ -31,17 +37,53 @@ define([ var ScratchStorage = { scratchColorIndex : 0, - scratchColors : [new Color()], + scratchColorArray : [new Color()], + scratchArrayIndex : 0, + scratchArrayArray : [[]], + scratchCartesian2Index : 0, + scratchCartesian3Index : 0, + scratchCartesian4Index : 0, + scratchCartesian2Array : [new Cartesian2()], + scratchCartesian3Array : [new Cartesian3()], + scratchCartesian4Array : [new Cartesian4()], reset : function() { this.scratchColorIndex = 0; + this.scratchArrayIndex = 0; + this.scratchCartesian2Index = 0; + this.scratchCartesian3Index = 0; + this.scratchCartesian4Index = 0; }, getColor : function() { - if (this.scratchColorIndex >= this.scratchColors.length) { - this.scratchColors.push(new Color()); + if (this.scratchColorIndex >= this.scratchColorArray.length) { + this.scratchColorArray.push(new Color()); } - var scratchColor = this.scratchColors[this.scratchColorIndex]; - ++this.scratchColorIndex; - return scratchColor; + return this.scratchColorArray[this.scratchColorIndex++]; + }, + getArray : function() { + if (this.scratchArrayIndex >= this.scratchArrayArray.length) { + this.scratchArrayArray.push([]); + } + var scratchArray = this.scratchArrayArray[this.scratchArrayIndex++]; + scratchArray.length = 0; + return scratchArray; + }, + getCartesian2 : function() { + if (this.scratchCartesian2Index >= this.scratchCartesian2Array.length) { + this.scratchCartesian2Array.push(new Cartesian2()); + } + return this.scratchCartesian2Array[this.scratchCartesian2Index++]; + }, + getCartesian3 : function() { + if (this.scratchCartesian3Index >= this.scratchCartesian3Array.length) { + this.scratchCartesian3Array.push(new Cartesian3()); + } + return this.scratchCartesian3Array[this.scratchCartesian3Index++]; + }, + getCartesian4 : function() { + if (this.scratchCartesian4Index >= this.scratchCartesian4Array.length) { + this.scratchCartesian4Array.push(new Cartesian4()); + } + return this.scratchCartesian4Array[this.scratchCartesian4Index++]; } }; @@ -143,16 +185,18 @@ define([ * is of type Boolean, Number, or String, the corresponding JavaScript * primitive type will be returned. If the result is a RegExp, a Javascript RegExp * object will be returned. If the result is a Color, a {@link Color} object will be returned. + * If the result is a Cartesian2, Cartesian3, or Cartesian4, + * a {@link Cartesian2}, {@link Cartesian3}, or {@link Cartesian4} object will be returned. * * @param {FrameState} frameState The frame state. * @param {Cesium3DTileFeature} feature The feature who's properties may be used as variables in the expression. - * @returns {Boolean|Number|String|Color|RegExp} The result of evaluating the expression. + * @returns {Boolean|Number|String|Color|Cartesian2|Cartesian3|Cartesian4|RegExp} The result of evaluating the expression. */ Expression.prototype.evaluate = function(frameState, feature) { ScratchStorage.reset(); var result = this._runtimeAst.evaluate(frameState, feature); - if (result instanceof Color) { - return Color.clone(result); + if ((result instanceof Color) || (result instanceof Cartesian2) || (result instanceof Cartesian3) || (result instanceof Cartesian4)) { + return result.clone(); } return result; }; @@ -286,6 +330,7 @@ define([ function parseCall(expression, ast) { var args = ast.arguments; + var argsLength = args.length; var call; var val, left, right; @@ -300,7 +345,7 @@ define([ throw new DeveloperError('Error: ' + call + ' is not a function.'); } //>>includeEnd('debug'); - if (args.length === 0) { + if (argsLength === 0) { if (call === 'test') { return new Node(ExpressionNodeType.LITERAL_BOOLEAN, false); } else { @@ -323,7 +368,7 @@ define([ // Non-member function calls call = ast.callee.name; if (call === 'color') { - if (args.length === 0) { + if (argsLength === 0) { return new Node(ExpressionNodeType.LITERAL_COLOR, call); } val = createRuntimeAst(expression, args[0]); @@ -334,7 +379,7 @@ define([ return new Node(ExpressionNodeType.LITERAL_COLOR, call, [val]); } else if (call === 'rgb' || call === 'hsl') { //>>includeStart('debug', pragmas.debug); - if (args.length < 3) { + if (argsLength < 3) { throw new DeveloperError('Error: ' + call + ' requires three arguments.'); } //>>includeEnd('debug'); @@ -346,7 +391,7 @@ define([ return new Node(ExpressionNodeType.LITERAL_COLOR, call, val); } else if (call === 'rgba' || call === 'hsla') { //>>includeStart('debug', pragmas.debug); - if (args.length < 4) { + if (argsLength < 4) { throw new DeveloperError('Error: ' + call + ' requires four arguments.'); } //>>includeEnd('debug'); @@ -357,8 +402,15 @@ define([ createRuntimeAst(expression, args[3]) ]; return new Node(ExpressionNodeType.LITERAL_COLOR, call, val); + } else if (call === 'vec2' || call === 'vec3' || call === 'vec4') { + // Check for invalid constructors at evaluation time + val = new Array(argsLength); + for (var i = 0; i < argsLength; ++i) { + val[i] = createRuntimeAst(expression, args[i]); + } + return new Node(ExpressionNodeType.LITERAL_VECTOR, call, val); } else if (call === 'isNaN' || call === 'isFinite') { - if (args.length === 0) { + if (argsLength === 0) { if (call === 'isNaN') { return new Node(ExpressionNodeType.LITERAL_BOOLEAN, true); } else { @@ -369,7 +421,7 @@ define([ return new Node(ExpressionNodeType.UNARY, call, val); } else if (call === 'isExactClass' || call === 'isClass') { //>>includeStart('debug', pragmas.debug); - if (args.length < 1 || args.length > 1) { + if (argsLength < 1 || argsLength > 1) { throw new DeveloperError('Error: ' + call + ' requires exactly one argument.'); } //>>includeEnd('debug'); @@ -377,14 +429,14 @@ define([ return new Node(ExpressionNodeType.UNARY, call, val); } else if (call === 'getExactClassName') { //>>includeStart('debug', pragmas.debug); - if (args.length > 0) { + if (argsLength > 0) { throw new DeveloperError('Error: ' + call + ' does not take any argument.'); } //>>includeEnd('debug'); return new Node(ExpressionNodeType.UNARY, call); } else if (defined(unaryFunctions[call])) { //>>includeStart('debug', pragmas.debug); - if (args.length !== 1) { + if (argsLength !== 1) { throw new DeveloperError('Error: ' + call + ' requires exactly one argument.'); } //>>includeEnd('debug'); @@ -392,7 +444,7 @@ define([ return new Node(ExpressionNodeType.UNARY, call, val); } else if (defined(binaryFunctions[call])) { //>>includeStart('debug', pragmas.debug); - if (args.length !== 2) { + if (argsLength !== 2) { throw new DeveloperError('Error: ' + call + ' requires exactly two arguments.'); } //>>includeEnd('debug'); @@ -401,7 +453,7 @@ define([ return new Node(ExpressionNodeType.BINARY, call, left, right); } else if (defined(ternaryFunctions[call])) { //>>includeStart('debug', pragmas.debug); - if (args.length !== 3) { + if (argsLength !== 3) { throw new DeveloperError('Error: ' + call + ' requires exactly three arguments.'); } //>>includeEnd('debug'); @@ -410,19 +462,19 @@ define([ var test = createRuntimeAst(expression, args[2]); return new Node(ExpressionNodeType.TERNARY, call, left, right, test); } else if (call === 'Boolean') { - if (args.length === 0) { + if (argsLength === 0) { return new Node(ExpressionNodeType.LITERAL_BOOLEAN, false); } val = createRuntimeAst(expression, args[0]); return new Node(ExpressionNodeType.UNARY, call, val); } else if (call === 'Number') { - if (args.length === 0) { + if (argsLength === 0) { return new Node(ExpressionNodeType.LITERAL_NUMBER, 0); } val = createRuntimeAst(expression, args[0]); return new Node(ExpressionNodeType.UNARY, call, val); } else if (call === 'String') { - if (args.length === 0) { + if (argsLength === 0) { return new Node(ExpressionNodeType.LITERAL_STRING, ''); } val = createRuntimeAst(expression, args[0]); @@ -499,12 +551,14 @@ define([ } function parseMemberExpression(expression, ast) { + var val; var obj = createRuntimeAst(expression, ast.object); if (ast.computed) { - var val = createRuntimeAst(expression, ast.property); + val = createRuntimeAst(expression, ast.property); return new Node(ExpressionNodeType.MEMBER, 'brackets', obj, val); } else { - return new Node(ExpressionNodeType.MEMBER, 'dot', obj, ast.property.name); + val = new Node(ExpressionNodeType.LITERAL_STRING, ast.property.name); + return new Node(ExpressionNodeType.MEMBER, 'dot', obj, val); } } @@ -677,6 +731,8 @@ define([ node.evaluate = node._evaluateVariableString; } else if (node._type === ExpressionNodeType.LITERAL_COLOR) { node.evaluate = node._evaluateLiteralColor; + } else if (node._type === ExpressionNodeType.LITERAL_VECTOR) { + node.evaluate = node._evaluateLiteralVector; } else if (node._type === ExpressionNodeType.LITERAL_STRING) { node.evaluate = node._evaluateLiteralString; } else if (node._type === ExpressionNodeType.REGEX) { @@ -726,42 +782,101 @@ define([ if (!defined(args)) { return Color.fromBytes(255, 255, 255, 255, result); } else if (args.length > 1) { - Color.fromCssColorString(args[0].evaluate(frameState, feature, result), result); - result.alpha = args[1].evaluate(frameState, feature, result); + Color.fromCssColorString(args[0].evaluate(frameState, feature), result); + result.alpha = args[1].evaluate(frameState, feature); } else { - Color.fromCssColorString(args[0].evaluate(frameState, feature, result), result); + Color.fromCssColorString(args[0].evaluate(frameState, feature), result); } } else if (this._value === 'rgb') { Color.fromBytes( - args[0].evaluate(frameState, feature, result), - args[1].evaluate(frameState, feature, result), - args[2].evaluate(frameState, feature, result), + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), 255, result); } else if (this._value === 'rgba') { // convert between css alpha (0 to 1) and cesium alpha (0 to 255) - var a = args[3].evaluate(frameState, feature, result) * 255; + var a = args[3].evaluate(frameState, feature) * 255; Color.fromBytes( - args[0].evaluate(frameState, feature, result), - args[1].evaluate(frameState, feature, result), - args[2].evaluate(frameState, feature, result), + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), a, result); } else if (this._value === 'hsl') { Color.fromHsl( - args[0].evaluate(frameState, feature, result), - args[1].evaluate(frameState, feature, result), - args[2].evaluate(frameState, feature, result), + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), 1.0, result); } else if (this._value === 'hsla') { Color.fromHsl( - args[0].evaluate(frameState, feature, result), - args[1].evaluate(frameState, feature, result), - args[2].evaluate(frameState, feature, result), - args[3].evaluate(frameState, feature, result), + args[0].evaluate(frameState, feature), + args[1].evaluate(frameState, feature), + args[2].evaluate(frameState, feature), + args[3].evaluate(frameState, feature), result); } return result; }; + Node.prototype._evaluateLiteralVector = function(frameState, feature) { + // Gather the components that make up the vector, which includes components from interior vectors. + // For example vec3(1, 2, 3) or vec3(vec2(1, 2), 3) are both valid. + // + // If the number of components does not equal the vector's size, then a DeveloperError is thrown - with two exceptions: + // 1. A vector may be constructed from a larger vector and drop the extra components. + // 2. A vector may be constructed from a single component - vec3(1) will become vec3(1, 1, 1). + // + // Examples of invalid constructors include: + // vec4(1, 2) // not enough components + // vec3(vec2(1, 2)) // not enough components + // vec3(1, 2, 3, 4) // too many components + // vec2(vec4(1), 1) // too many components + + var components = ScratchStorage.getArray(); + var args = this._left; + var argsLength = args.length; + for (var i = 0; i < argsLength; ++i) { + var value = args[i].evaluate(frameState, feature); + if (typeof(value) === 'number') { + components.push(value); + } else if (value instanceof Cartesian2) { + components.push(value.x, value.y); + } else if (value instanceof Cartesian3) { + components.push(value.x, value.y, value.z); + } else if (value instanceof Cartesian4) { + components.push(value.x, value.y, value.z, value.w); + } + } + + var componentsLength = components.length; + var call = this._value; + var vectorLength = parseInt(call.charAt(3)); + + //>>includeStart('debug', pragmas.debug); + if (componentsLength === 0) { + throw new DeveloperError('Error: Invalid ' + call + ' constructor. No valid arguments.'); + } else if ((componentsLength < vectorLength) && (componentsLength > 1)) { + throw new DeveloperError('Error: Invalid ' + call + ' constructor. Not enough arguments.'); + } else if ((componentsLength > vectorLength) && (argsLength > 1)) { + throw new DeveloperError('Error: Invalid ' + call + ' constructor. Too many arguments.'); + } + //>>includeEnd('debug'); + + if (componentsLength === 1) { + // Add the same component 3 more times + var component = components[0]; + components.push(component, component, component); + } + + if (call === 'vec2') { + return Cartesian2.fromArray(components, 0, ScratchStorage.getCartesian2()); + } else if (call === 'vec3') { + return Cartesian3.fromArray(components, 0, ScratchStorage.getCartesian3()); + } else if (call === 'vec4') { + return Cartesian4.fromArray(components, 0, ScratchStorage.getCartesian4()); + } + }; + Node.prototype._evaluateLiteralString = function(frameState, feature) { return this._value; }; @@ -793,25 +908,66 @@ define([ // PERFORMANCE_IDEA: Determine if parent property needs to be computed before runtime Node.prototype._evaluateMemberDot = function(frameState, feature) { - if(checkFeature(this._left)) { - return feature.getProperty(this._right); + if (checkFeature(this._left)) { + return feature.getProperty(this._right.evaluate(frameState, feature)); } var property = this._left.evaluate(frameState, feature); if (!defined(property)) { return undefined; } - return property[this._right]; + + var member = this._right.evaluate(frameState, feature); + if (property instanceof Color) { + // Color components may be accessed with .x, .y, .z, .w and implicitly with .red, .green, .blue, .alpha + if (member === 'x') { + return property.red; + } else if (member === 'y') { + return property.green; + } else if (member === 'z') { + return property.blue; + } else if (member === 'w') { + return property.alpha; + } + } + + return property[member]; }; Node.prototype._evaluateMemberBrackets = function(frameState, feature) { - if(checkFeature(this._left)) { + if (checkFeature(this._left)) { return feature.getProperty(this._right.evaluate(frameState, feature)); } var property = this._left.evaluate(frameState, feature); if (!defined(property)) { return undefined; } - return property[this._right.evaluate(frameState, feature)]; + + var member = this._right.evaluate(frameState, feature); + if (property instanceof Color) { + // Color components may be accessed with [0][1][2][3], ['x']['y']['z']['w'], and implicitly with ['red']['green']['blue']['alpha'] + if (member === 0 || member === 'x') { + return property.red; + } else if (member === 1 || member === 'y') { + return property.green; + } else if (member === 2 || member === 'z') { + return property.blue; + } else if (member === 3 || member === 'w') { + return property.alpha; + } + } else if ((property instanceof Cartesian2) || (property instanceof Cartesian3) || (property instanceof Cartesian4)) { + // Vector components may be accessed with [0][1][2][3] and implicitly with ['x']['y']['z']['w'] + // For Cartesian2 and Cartesian3 out-of-range components will just return undefined + if (member === 0) { + return property.x; + } else if (member === 1) { + return property.y; + } else if (member === 2) { + return property.z; + } else if (member === 3) { + return property.w; + } + } + return property[member]; }; Node.prototype._evaluateArray = function(frameState, feature) { @@ -830,11 +986,23 @@ define([ }; Node.prototype._evaluateNegative = function(frameState, feature) { - return -(this._left.evaluate(frameState, feature)); + var left = this._left.evaluate(frameState, feature); + if (left instanceof Cartesian2) { + return Cartesian2.negate(left, ScratchStorage.getCartesian2()); + } else if (left instanceof Cartesian3) { + return Cartesian3.negate(left, ScratchStorage.getCartesian3()); + } else if (left instanceof Cartesian4) { + return Cartesian4.negate(left, ScratchStorage.getCartesian4()); + } + return -left; }; Node.prototype._evaluatePositive = function(frameState, feature) { - return +(this._left.evaluate(frameState, feature)); + var left = this._left.evaluate(frameState, feature); + if ((left instanceof Color) || (left instanceof Cartesian2) || (left instanceof Cartesian3) || (left instanceof Cartesian4)) { + return left; + } + return +left; }; Node.prototype._evaluateLessThan = function(frameState, feature) { @@ -910,6 +1078,12 @@ define([ var right = this._right.evaluate(frameState, feature); if ((right instanceof Color) && (left instanceof Color)) { return Color.add(left, right, ScratchStorage.getColor()); + } else if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.add(left, right, ScratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.add(left, right, ScratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.add(left, right, ScratchStorage.getCartesian4()); } return left + right; }; @@ -919,6 +1093,12 @@ define([ var right = this._right.evaluate(frameState, feature); if ((right instanceof Color) && (left instanceof Color)) { return Color.subtract(left, right, ScratchStorage.getColor()); + } else if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.subtract(left, right, ScratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.subtract(left, right, ScratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.subtract(left, right, ScratchStorage.getCartesian4()); } return left - right; }; @@ -932,6 +1112,24 @@ define([ return Color.multiplyByScalar(right, left, ScratchStorage.getColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { return Color.multiplyByScalar(left, right, ScratchStorage.getColor()); + } else if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.multiplyComponents(left, right, ScratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian2) && (typeof(left) === 'number')) { + return Cartesian2.multiplyByScalar(right, left, ScratchStorage.getCartesian2()); + } else if ((left instanceof Cartesian2) && (typeof(right) === 'number')) { + return Cartesian2.multiplyByScalar(left, right, ScratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.multiplyComponents(left, right, ScratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian3) && (typeof(left) === 'number')) { + return Cartesian3.multiplyByScalar(right, left, ScratchStorage.getCartesian3()); + } else if ((left instanceof Cartesian3) && (typeof(right) === 'number')) { + return Cartesian3.multiplyByScalar(left, right, ScratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.multiplyComponents(left, right, ScratchStorage.getCartesian4()); + } else if ((right instanceof Cartesian4) && (typeof(left) === 'number')) { + return Cartesian4.multiplyByScalar(right, left, ScratchStorage.getCartesian4()); + } else if ((left instanceof Cartesian4) && (typeof(right) === 'number')) { + return Cartesian4.multiplyByScalar(left, right, ScratchStorage.getCartesian4()); } return left * right; }; @@ -943,6 +1141,18 @@ define([ return Color.divide(left, right, ScratchStorage.getColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { return Color.divideByScalar(left, right, ScratchStorage.getColor()); + } else if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.divideComponents(left, right, ScratchStorage.getCartesian2()); + } else if ((left instanceof Cartesian2) && (typeof(right) === 'number')) { + return Cartesian2.divideByScalar(left, right, ScratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.divideComponents(left, right, ScratchStorage.getCartesian3()); + } else if ((left instanceof Cartesian3) && (typeof(right) === 'number')) { + return Cartesian3.divideByScalar(left, right, ScratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.divideComponents(left, right, ScratchStorage.getCartesian4()); + } else if ((left instanceof Cartesian4) && (typeof(right) === 'number')) { + return Cartesian4.divideByScalar(left, right, ScratchStorage.getCartesian4()); } return left / right; }; @@ -952,6 +1162,12 @@ define([ var right = this._right.evaluate(frameState, feature); if ((right instanceof Color) && (left instanceof Color)) { return Color.mod(left, right, ScratchStorage.getColor()); + } else if ((right instanceof Cartesian2) && (left instanceof Cartesian2)) { + return Cartesian2.fromElements(left.x % right.x, left.y % right.y, ScratchStorage.getCartesian2()); + } else if ((right instanceof Cartesian3) && (left instanceof Cartesian3)) { + return Cartesian3.fromElements(left.x % right.x, left.y % right.y, left.z % right.z, ScratchStorage.getCartesian3()); + } else if ((right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return Cartesian4.fromElements(left.x % right.x, left.y % right.y, left.z % right.z, left.w % right.w, ScratchStorage.getCartesian4()); } return left % right; }; @@ -959,8 +1175,11 @@ define([ Node.prototype._evaluateEqualsStrict = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); var right = this._right.evaluate(frameState, feature); - if ((right instanceof Color) && (left instanceof Color)) { - return Color.equals(left, right); + if ((right instanceof Color) && (left instanceof Color) || + (right instanceof Cartesian2) && (left instanceof Cartesian2) || + (right instanceof Cartesian3) && (left instanceof Cartesian3) || + (right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return left.equals(right); } return left === right; }; @@ -968,8 +1187,11 @@ define([ Node.prototype._evaluateEquals = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); var right = this._right.evaluate(frameState, feature); - if ((right instanceof Color) && (left instanceof Color)) { - return Color.equals(left, right); + if ((right instanceof Color) && (left instanceof Color) || + (right instanceof Cartesian2) && (left instanceof Cartesian2) || + (right instanceof Cartesian3) && (left instanceof Cartesian3) || + (right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return left.equals(right); } // Specifically want to do an abstract equality comparison (==) instead of a strict equality comparison (===) @@ -980,8 +1202,11 @@ define([ Node.prototype._evaluateNotEqualsStrict = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); var right = this._right.evaluate(frameState, feature); - if ((right instanceof Color) && (left instanceof Color)) { - return !Color.equals(left, right); + if ((right instanceof Color) && (left instanceof Color) || + (right instanceof Cartesian2) && (left instanceof Cartesian2) || + (right instanceof Cartesian3) && (left instanceof Cartesian3) || + (right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return !left.equals(right); } return left !== right; }; @@ -989,8 +1214,11 @@ define([ Node.prototype._evaluateNotEquals = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); var right = this._right.evaluate(frameState, feature); - if ((right instanceof Color) && (left instanceof Color)) { - return !Color.equals(left, right); + if ((right instanceof Color) && (left instanceof Color) || + (right instanceof Cartesian2) && (left instanceof Cartesian2) || + (right instanceof Cartesian3) && (left instanceof Cartesian3) || + (right instanceof Cartesian4) && (left instanceof Cartesian4)) { + return !left.equals(right); } // Specifically want to do an abstract inequality comparison (!=) instead of a strict inequality comparison (!==) // so that cases like "5 != '5'" return false. Tell jsHint to ignore this line. @@ -1093,7 +1321,7 @@ define([ Node.prototype._evaluateToString = function(frameState, feature) { var left = this._left.evaluate(frameState, feature); - if ((left instanceof RegExp) || (left instanceof Color)) { + if ((left instanceof RegExp) || (left instanceof Color) || (left instanceof Cartesian2) || (left instanceof Cartesian3) || (left instanceof Cartesian4)) { return String(left); } //>>includeStart('debug', pragmas.debug); @@ -1162,11 +1390,11 @@ define([ return 'vec4(' + r + ', ' + g + ', ' + b + ', ' + a + ')'; } - function getExpressionArray(array, attributePrefix, shaderState) { + function getExpressionArray(array, attributePrefix, shaderState, parent) { var length = array.length; var expressions = new Array(length); for (var i = 0; i < length; ++i) { - var shader = array[i].getShaderExpression(attributePrefix, shaderState); + var shader = array[i].getShaderExpression(attributePrefix, shaderState, parent); if (!defined(shader)) { // If any of the expressions are not valid, the array is not valid return undefined; @@ -1176,7 +1404,7 @@ define([ return expressions; } - Node.prototype.getShaderExpression = function(attributePrefix, shaderState) { + Node.prototype.getShaderExpression = function(attributePrefix, shaderState, parent) { var color; var left; var right; @@ -1185,21 +1413,12 @@ define([ var type = this._type; var value = this._value; - // Right may be a string if it's a member variable: e.g. "${property.name}" - if (typeof(this._right) === 'string') { - //>>includeStart('debug', pragmas.debug); - throw new DeveloperError('Error generating style shader: string members are not supported.'); - //>>includeEnd('debug'); - // Return undefined when not in debug. Tell jsHint to ignore this line. - return; // jshint ignore:line - } - if (defined(this._left)) { if (isArray(this._left)) { - // Left can be an array if the type is LITERAL_COLOR - left = getExpressionArray(this._left, attributePrefix, shaderState); + // Left can be an array if the type is LITERAL_COLOR or LITERAL_VECTOR + left = getExpressionArray(this._left, attributePrefix, shaderState, this); } else { - left = this._left.getShaderExpression(attributePrefix, shaderState); + left = this._left.getShaderExpression(attributePrefix, shaderState, this); } if (!defined(left)) { // If the left side is not valid shader code, then the expression is not valid @@ -1208,7 +1427,7 @@ define([ } if (defined(this._right)) { - right = this._right.getShaderExpression(attributePrefix, shaderState); + right = this._right.getShaderExpression(attributePrefix, shaderState, this); if (!defined(right)) { // If the right side is not valid shader code, then the expression is not valid return undefined; @@ -1216,7 +1435,7 @@ define([ } if (defined(this._test)) { - test = this._test.getShaderExpression(attributePrefix, shaderState); + test = this._test.getShaderExpression(attributePrefix, shaderState, this); if (!defined(test)) { // If the test is not valid shader code, then the expression is not valid return undefined; @@ -1225,7 +1444,7 @@ define([ if (isArray(this._value)) { // For ARRAY type - value = getExpressionArray(this._value, attributePrefix, shaderState); + value = getExpressionArray(this._value, attributePrefix, shaderState, this); if (!defined(value)) { // If the values are not valid shader code, then the expression is not valid return undefined; @@ -1247,14 +1466,15 @@ define([ return 'cos(' + left + ')'; } else if (value === 'sqrt') { return 'sqrt(' + left + ')'; + } else if ((value === 'isNaN') || (value === 'isFinite') || (value === 'String') || (value === 'isExactClass') || (value === 'isClass') || (value === 'getExactClassName')) { + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: "' + value + '" is not supported.'); + //>>includeEnd('debug'); + // Return undefined when not in debug. Tell jsHint to ignore this line. + return undefined; // jshint ignore:line } else if (defined(unaryFunctions[value])) { return value + '(' + left + ')'; } - //>>includeStart('debug', pragmas.debug); - else if ((value === 'isNaN') || (value === 'isFinite') || (value === 'String') || (value === 'isExactClass') || (value === 'isClass') || (value === 'getExactClassName')) { - throw new DeveloperError('Error generating style shader: "' + value + '" is not supported.'); - } - //>>includeEnd('debug'); return value + left; case ExpressionNodeType.BINARY: // Supported types: ||, &&, ===, ==, !==, !=, <, >, <=, >=, +, -, *, /, % @@ -1278,7 +1498,18 @@ define([ case ExpressionNodeType.CONDITIONAL: return '(' + test + ' ? ' + left + ' : ' + right + ')'; case ExpressionNodeType.MEMBER: - // This is intended for accessing the components of vec2, vec3, and vec4 properties. String members aren't supported. + // This is intended for accessing the components of vector properties. String members aren't supported. + // Check for 0.0 rather than 0 because all numbers are previously converted to decimals. + // In this shader there is not much distinction between colors and vectors so allow .red to access the 0th component for both. + if (right === 'red' || right === 'x' || right === '0.0') { + return left + '[0]'; + } else if (right === 'green' || right === 'y' || right === '1.0') { + return left + '[1]'; + } else if (right === 'blue' || right === 'z' || right === '2.0') { + return left + '[2]'; + } else if (right === 'alpha' || right === 'w' || right === '3.0') { + return left + '[3]'; + } return left + '[int(' + right + ')]'; case ExpressionNodeType.FUNCTION_CALL: //>>includeStart('debug', pragmas.debug); @@ -1315,7 +1546,15 @@ define([ case ExpressionNodeType.LITERAL_NUMBER: return numberToString(value); case ExpressionNodeType.LITERAL_STRING: - // The only supported strings are css color strings + // Check if parent is of type MEMBER. Otherwise it is not possible to know whether 'red', 'green', and 'blue' + // refer to CSS strings or component accessors. + if (defined(parent) && (parent._type === ExpressionNodeType.MEMBER)) { + if (value === 'red' || value === 'green' || value === 'blue' || value === 'alpha' || + value === 'x' || value === 'y' || value === 'z' || value === 'w') { + return value; + } + } + // Check for css color strings color = Color.fromCssColorString(value, scratchColor); if (defined(color)) { return colorToVec3(color); @@ -1377,6 +1616,17 @@ define([ } } break; + case ExpressionNodeType.LITERAL_VECTOR: + var length = left.length; + var vectorExpression = value + '('; + for (var i = 0; i < length; ++i) { + vectorExpression += left[i]; + if (i < (length - 1)) { + vectorExpression += ', '; + } + } + vectorExpression += ')'; + return vectorExpression; case ExpressionNodeType.LITERAL_REGEX: //>>includeStart('debug', pragmas.debug); throw new DeveloperError('Error generating style shader: Regular expressions are not supported.'); diff --git a/Source/Scene/ExpressionNodeType.js b/Source/Scene/ExpressionNodeType.js index bf8ccd9e9094..f7d9a85fa1ca 100644 --- a/Source/Scene/ExpressionNodeType.js +++ b/Source/Scene/ExpressionNodeType.js @@ -24,9 +24,10 @@ define([ LITERAL_NUMBER : 12, LITERAL_STRING : 13, LITERAL_COLOR : 14, - LITERAL_REGEX : 15, - LITERAL_UNDEFINED : 16, - LITERAL_GLOBAL : 17 + LITERAL_VECTOR : 15, + LITERAL_REGEX : 16, + LITERAL_UNDEFINED : 17, + LITERAL_GLOBAL : 18 }; return freezeObject(ExpressionNodeType); diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index ae1972107bae..e00cea225aec 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -908,12 +908,7 @@ define([ attributeLocations.a_batchId = batchIdLocation; } - var vs = 'attribute vec3 a_position; \n' + - 'varying vec4 v_color; \n' + - 'uniform float u_pointSize; \n' + - 'uniform vec4 u_constantColor; \n' + - 'uniform vec4 u_highlightColor; \n' + - 'uniform float u_tilesetTime; \n'; + var attributeDeclarations = ''; var length = styleableProperties.length; for (i = 0; i < length; ++i) { @@ -934,10 +929,19 @@ define([ attributeType = 'vec' + componentCount; } - vs += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; + attributeDeclarations += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; attributeLocations[attributeName] = attribute.location; } + var vs = 'attribute vec3 a_position; \n' + + 'varying vec4 v_color; \n' + + 'uniform float u_pointSize; \n' + + 'uniform vec4 u_constantColor; \n' + + 'uniform vec4 u_highlightColor; \n' + + 'uniform float u_tilesetTime; \n'; + + vs += attributeDeclarations; + if (usesColors) { if (isTranslucent) { vs += 'attribute vec4 a_color; \n'; diff --git a/Specs/Scene/ExpressionSpec.js b/Specs/Scene/ExpressionSpec.js index c8b8ced4c52d..078761b40a2e 100644 --- a/Specs/Scene/ExpressionSpec.js +++ b/Specs/Scene/ExpressionSpec.js @@ -1,11 +1,17 @@ /*global defineSuite*/ defineSuite([ 'Scene/Expression', + 'Core/Cartesian2', + 'Core/Cartesian3', + 'Core/Cartesian4', 'Core/Color', 'Core/Math', 'Scene/ExpressionNodeType' ], function( Expression, + Cartesian2, + Cartesian3, + Cartesian4, Color, CesiumMath, ExpressionNodeType) { @@ -485,7 +491,7 @@ defineSuite([ }).toThrowDeveloperError(); }); - it('evaluates color properties', function() { + it('evaluates color properties (reg, green, blue, alpha)', function() { var expression = new Expression('color(\'#ffffff\').red'); expect(expression.evaluate(frameState, undefined)).toEqual(1); @@ -499,6 +505,254 @@ defineSuite([ expect(expression.evaluate(frameState, undefined)).toEqual(0.5); }); + it('evaluates color properties (x, y, z, w)', function() { + var expression = new Expression('color(\'#ffffff\').x'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgb(255, 255, 0).y'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('color("cyan").z'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgba(255, 255, 0, 0.5).w'); + expect(expression.evaluate(frameState, undefined)).toEqual(0.5); + }); + + it('evaluates color properties ([0], [1], [2]. [3])', function() { + var expression = new Expression('color(\'#ffffff\')[0]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgb(255, 255, 0)[1]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('color("cyan")[2]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgba(255, 255, 0, 0.5)[3]'); + expect(expression.evaluate(frameState, undefined)).toEqual(0.5); + }); + + it('evaluates color properties (["red"], ["green"], ["blue"], ["alpha"])', function() { + var expression = new Expression('color(\'#ffffff\')["red"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgb(255, 255, 0)["green"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('color("cyan")["blue"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgba(255, 255, 0, 0.5)["alpha"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(0.5); + }); + + it('evaluates color properties (["x"], ["y"], ["z"], ["w"])', function() { + var expression = new Expression('color(\'#ffffff\')["x"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgb(255, 255, 0)["y"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('color("cyan")["z"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1); + + expression = new Expression('rgba(255, 255, 0, 0.5)["w"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(0.5); + }); + + it('evaluates vec2', function() { + var expression = new Expression('vec2(2.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(2.0, 2.0)); + + expression = new Expression('vec2(3.0, 4.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3.0, 4.0)); + + expression = new Expression('vec2(vec2(3.0, 4.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3.0, 4.0)); + + expression = new Expression('vec2(vec3(3.0, 4.0, 5.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3.0, 4.0)); + + expression = new Expression('vec2(vec4(3.0, 4.0, 5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3.0, 4.0)); + }); + + it('throws if vec2 has invalid number of arguments', function() { + var expression = new Expression('vec2()'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec2(3.0, 4.0, 5.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec2(vec2(3.0, 4.0), 5.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + }); + + it('evaluates vec3', function() { + var expression = new Expression('vec3(2.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(2.0, 2.0, 2.0)); + + expression = new Expression('vec3(3.0, 4.0, 5.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + + expression = new Expression('vec3(vec2(3.0, 4.0), 5.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + + expression = new Expression('vec3(3.0, vec2(4.0, 5.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + + expression = new Expression('vec3(vec3(3.0, 4.0, 5.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + + expression = new Expression('vec3(vec4(3.0, 4.0, 5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3.0, 4.0, 5.0)); + }); + + it ('throws if vec3 has invalid number of arguments', function() { + var expression = new Expression('vec3()'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec3(3.0, 4.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec3(3.0, 4.0, 5.0, 6.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec3(vec2(3.0, 4.0), vec2(5.0, 6.0))'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec3(vec4(3.0, 4.0, 5.0, 6.0), 1.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + }); + + it('evaluates vec4', function() { + var expression = new Expression('vec4(2.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(2.0, 2.0, 2.0, 2.0)); + + expression = new Expression('vec4(3.0, 4.0, 5.0, 6.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(vec2(3.0, 4.0), 5.0, 6.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(3.0, vec2(4.0, 5.0), 6.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(3.0, 4.0, vec2(5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(vec3(3.0, 4.0, 5.0), 6.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(3.0, vec3(4.0, 5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + + expression = new Expression('vec4(vec4(3.0, 4.0, 5.0, 6.0))'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3.0, 4.0, 5.0, 6.0)); + }); + + it ('throws if vec4 has invalid number of arguments', function() { + var expression = new Expression('vec4()'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec4(3.0, 4.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec4(3.0, 4.0, 5.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec4(3.0, 4.0, 5.0, 6.0, 7.0)'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + + expression = new Expression('vec4(vec3(3.0, 4.0, 5.0))'); + expect(function() { + expression.evaluate(frameState, undefined); + }).toThrowDeveloperError(); + }); + + it('evaluates vector with expressions as arguments', function() { + var feature = new MockFeature(); + feature.addProperty('height', 2); + feature.addProperty('width', 4); + feature.addProperty('depth', 3); + feature.addProperty('scale', 1); + + var expression = new Expression('vec4(${height}, ${width}, ${depth}, ${scale})'); + expect(expression.evaluate(frameState, feature)).toEqual(new Cartesian4(2.0, 4.0, 3.0, 1.0)); + }); + + it('evaluates expression with multiple nested vectors', function() { + var expression = new Expression('vec4(vec2(1, 2)[vec3(6, 1, 5).y], 2, vec4(1.0).w, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(2.0, 2.0, 1.0, 5.0)); + }); + + it('evaluates vector properties (x, y, z, w)', function() { + var expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0).x'); + expect(expression.evaluate(frameState, undefined)).toEqual(1.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0).y'); + expect(expression.evaluate(frameState, undefined)).toEqual(2.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0).z'); + expect(expression.evaluate(frameState, undefined)).toEqual(3.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0).w'); + expect(expression.evaluate(frameState, undefined)).toEqual(4.0); + }); + + it('evaluates vector properties ([0], [1], [2], [3])', function() { + var expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)[0]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)[1]'); + expect(expression.evaluate(frameState, undefined)).toEqual(2.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)[2]'); + expect(expression.evaluate(frameState, undefined)).toEqual(3.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)[3]'); + expect(expression.evaluate(frameState, undefined)).toEqual(4.0); + }); + + it('evaluates vector properties (["x"], ["y"], ["z"]. ["w"])', function() { + var expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)["x"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(1.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)["y"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(2.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)["z"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(3.0); + + expression = new Expression('vec4(1.0, 2.0, 3.0, 4.0)["w"]'); + expect(expression.evaluate(frameState, undefined)).toEqual(4.0); + }); + it('evaluates unary not', function() { var expression = new Expression('!true'); expect(expression.evaluate(frameState, undefined)).toEqual(false); @@ -748,7 +1002,10 @@ defineSuite([ }); it('evaluates color operations', function() { - var expression = new Expression('rgba(255, 0, 0, 0.5) + rgba(0, 0, 255, 0.5)'); + var expression = new Expression('+rgba(255, 0, 0, 1.0)'); + expect(expression.evaluate(frameState, undefined)).toEqual(Color.RED); + + expression = new Expression('rgba(255, 0, 0, 0.5) + rgba(0, 0, 255, 0.5)'); expect(expression.evaluate(frameState, undefined)).toEqual(Color.MAGENTA); expression = new Expression('rgba(0, 255, 255, 1.0) - rgba(0, 255, 0, 0)'); @@ -783,6 +1040,140 @@ defineSuite([ expression = new Expression('color(\'green\') != color(\'green\')'); expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('color(\'green\') !== color(\'green\')'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + }); + + it('evaluates vector operations', function() { + var expression = new Expression('+vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(1, 2)); + + expression = new Expression('+vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(1, 2, 3)); + + expression = new Expression('+vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(1, 2, 3, 4)); + + expression = new Expression('-vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(-1, -2)); + + expression = new Expression('-vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(-1, -2, -3)); + + expression = new Expression('-vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(-1, -2, -3, -4)); + + expression = new Expression('vec2(1, 2) + vec2(3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(4, 6)); + + expression = new Expression('vec3(1, 2, 3) + vec3(3, 4, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(4, 6, 8)); + + expression = new Expression('vec4(1, 2, 3, 4) + vec4(3, 4, 5, 6)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(4, 6, 8, 10)); + + expression = new Expression('vec2(1, 2) - vec2(3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(-2, -2)); + + expression = new Expression('vec3(1, 2, 3) - vec3(3, 4, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(-2, -2, -2)); + + expression = new Expression('vec4(1, 2, 3, 4) - vec4(3, 4, 5, 6)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(-2, -2, -2, -2)); + + expression = new Expression('vec2(1, 2) * vec2(3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3, 8)); + + expression = new Expression('vec2(1, 2) * 3.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3, 6)); + + expression = new Expression('3.0 * vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(3, 6)); + + expression = new Expression('vec3(1, 2, 3) * vec3(3, 4, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3, 8, 15)); + + expression = new Expression('vec3(1, 2, 3) * 3.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3, 6, 9)); + + expression = new Expression('3.0 * vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(3, 6, 9)); + + expression = new Expression('vec4(1, 2, 3, 4) * vec4(3, 4, 5, 6)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3, 8, 15, 24)); + + expression = new Expression('vec4(1, 2, 3, 4) * 3.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3, 6, 9, 12)); + + expression = new Expression('3.0 * vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(3, 6, 9, 12)); + + expression = new Expression('vec2(1, 2) / vec2(2, 5)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(0.5, 0.4)); + + expression = new Expression('vec2(1, 2) / 2.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(0.5, 1.0)); + + expression = new Expression('vec3(1, 2, 3) / vec3(2, 5, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(0.5, 0.4, 1.0)); + + expression = new Expression('vec3(1, 2, 3) / 2.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(0.5, 1.0, 1.5)); + + expression = new Expression('vec4(1, 2, 3, 4) / vec4(2, 5, 3, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(0.5, 0.4, 1.0, 2.0)); + + expression = new Expression('vec4(1, 2, 3, 4) / 2.0'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(0.5, 1.0, 1.5, 2.0)); + + expression = new Expression('vec2(2, 3) % vec2(3, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian2(2, 0)); + + expression = new Expression('vec3(2, 3, 4) % vec3(3, 3, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian3(2, 0, 1)); + + expression = new Expression('vec4(2, 3, 4, 5) % vec4(3, 3, 3, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(new Cartesian4(2, 0, 1, 1)); + + expression = new Expression('vec2(1, 3) == vec2(1, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec3(1, 3, 4) == vec3(1, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec4(1, 3, 4, 6) == vec4(1, 3, 4, 6)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec2(1, 2) === vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec3(1, 2, 3) === vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec4(1, 2, 3, 4) === vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('!!vec4(1.0) == true'); + expect(expression.evaluate(frameState, undefined)).toEqual(true); + + expression = new Expression('vec2(1, 2) != vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec3(1, 2, 3) != vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec4(1, 2, 3, 4) != vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec2(1, 2) !== vec2(1, 2)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec3(1, 2, 3) !== vec3(1, 2, 3)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); + + expression = new Expression('vec4(1, 2, 3, 4) !== vec4(1, 2, 3, 4)'); + expect(expression.evaluate(frameState, undefined)).toEqual(false); }); it('evaluates color toString function', function() { @@ -799,6 +1190,23 @@ defineSuite([ expect(expression.evaluate(frameState, feature)).toEqual('(0, 0, 1, 1)'); }); + it('evaluates vector toString function', function() { + var feature = new MockFeature(); + feature.addProperty('property', new Cartesian4(1, 2, 3, 4)); + + var expression = new Expression('vec2(1, 2).toString()'); + expect(expression.evaluate(frameState, undefined)).toEqual('(1, 2)'); + + expression = new Expression('vec3(1, 2, 3).toString()'); + expect(expression.evaluate(frameState, undefined)).toEqual('(1, 2, 3)'); + + expression = new Expression('vec4(1, 2, 3, 4).toString()'); + expect(expression.evaluate(frameState, undefined)).toEqual('(1, 2, 3, 4)'); + + expression = new Expression('${property}.toString()'); + expect(expression.evaluate(frameState, feature)).toEqual('(1, 2, 3, 4)'); + }); + it('evaluates isNaN function', function() { var expression = new Expression('isNaN()'); expect(expression.evaluate(frameState, undefined)).toEqual(true); @@ -1524,7 +1932,7 @@ defineSuite([ expect(expression.evaluate(frameState, feature)).toEqual(false); }); - it('throws if test is not call with a RegExp', function() { + it('throws if test is not called with a RegExp', function() { expect(function() { return new Expression('color("blue").test()'); }).toThrowDeveloperError(); @@ -1780,12 +2188,12 @@ defineSuite([ it('gets shader expression for array indexing', function() { var expression = new Expression('${property[0]}'); var shaderExpression = expression.getShaderExpression('', {}); - var expected = 'property[int(0.0)]'; + var expected = 'property[0]'; expect(shaderExpression).toEqual(expected); - expression = new Expression('rgb(0,0,0)[1]'); + expression = new Expression('${property[4 / 2]}'); shaderExpression = expression.getShaderExpression('', {}); - expected = 'vec4(0.0, 0.0, 0.0, 1.0)[int(1.0)]'; + expected = 'property[int((4.0 / 2.0))]'; expect(shaderExpression).toEqual(expected); }); @@ -1944,6 +2352,55 @@ defineSuite([ expect(shaderState.translucent).toBe(true); }); + it('gets shader expression for color components', function() { + // .red, .green, .blue, .alpha + var expression = new Expression('color().red + color().green + color().blue + color().alpha'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(((vec4(1.0)[0] + vec4(1.0)[1]) + vec4(1.0)[2]) + vec4(1.0)[3])'; + expect(shaderExpression).toEqual(expected); + + // .x, .y, .z, .w + expression = new Expression('color().x + color().y + color().z + color().w'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual(expected); + + // [0], [1], [2], [3] + expression = new Expression('color()[0] + color()[1] + color()[2] + color()[3]'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for vector', function() { + var expression = new Expression('vec4(1, 2, 3, 4)'); + var shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual('vec4(1.0, 2.0, 3.0, 4.0)'); + + expression = new Expression('vec4(1) + vec4(2)'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual('(vec4(1.0) + vec4(2.0))'); + + expression = new Expression('vec4(1, ${property}, vec2(1, 2).x, 0)'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual('vec4(1.0, property, vec2(1.0, 2.0)[0], 0.0)'); + + expression = new Expression('vec4(vec3(2), 1.0)'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual('vec4(vec3(2.0), 1.0)'); + }); + + it('gets shader expression for vector components', function() { + // .x, .y, .z, .w + var expression = new Expression('vec4(1).x + vec4(1).y + vec4(1).z + vec4(1).w'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(((vec4(1.0)[0] + vec4(1.0)[1]) + vec4(1.0)[2]) + vec4(1.0)[3])'; + expect(shaderExpression).toEqual(expected); + + // [0], [1], [2], [3] + expression = new Expression('vec4(1)[0] + vec4(1)[1] + vec4(1)[2] + vec4(1)[3]'); + shaderExpression = expression.getShaderExpression('', {}); + expect(shaderExpression).toEqual(expected); + }); + it('gets shader expression for TILES3D_TILESET_TIME', function() { var expression = new Expression('TILES3D_TILESET_TIME'); var shaderExpression = expression.getShaderExpression('', {});