diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html new file mode 100644 index 000000000000..9b249c8207d8 --- /dev/null +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html @@ -0,0 +1,170 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.jpg b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.jpg new file mode 100644 index 000000000000..11512b80b98d Binary files /dev/null and b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.jpg differ diff --git a/Apps/Sandcastle/gallery/3D Tiles.html b/Apps/Sandcastle/gallery/3D Tiles.html index ff0c00d22f1a..a320c0bedc37 100644 --- a/Apps/Sandcastle/gallery/3D Tiles.html +++ b/Apps/Sandcastle/gallery/3D Tiles.html @@ -89,17 +89,17 @@ // "color" : "rgb(100, 255, 190)", // "color" : "hsla(0.9, 0.6, 0.7, 0.75)", "color" : { - "conditions" : { - "${Height} >= 83" : "color('purple', 0.5)", - "${Height} >= 80" : "color('red')", - "${Height} >= 70" : "color('orange')", - "${Height} >= 12" : "color('yellow')", - "${Height} >= 7" : "color('lime')", - "${Height} >= 1" : "color('cyan')", - "true" : "color('blue')" - } + "conditions" : [ + ["${Height} >= 83", "color('purple', 0.5)"], + ["${Height} >= 80", "color('red')"], + ["${Height} >= 70", "color('orange')"], + ["${Height} >= 12", "color('yellow')"], + ["${Height} >= 7", "color('lime')"], + ["${Height} >= 1", "color('cyan')"], + ["true", "color('blue')"] + ] }, -// "show": false +// "show": false, // "show" : "${Height} >= 0", "meta" : { "description" : "'Building id ${id} has height ${Height}.'" @@ -416,13 +416,13 @@ } function styleFunction(name) { - var conditions = {}; + var conditions = []; var intervalSize = Math.floor(100/numberofColors); for (var i = numberofColors; i >= 0; --i) { var cond = '${' + name + '} > ' + (i * intervalSize); - conditions[cond] = getRandomColor(); + conditions.push([cond, getRandomColor()]); } - conditions['true'] = getRandomColor(); + conditions.push(['true', getRandomColor()]); tileset.style = new Cesium.Cesium3DTileStyle({ color : { @@ -491,7 +491,7 @@ var leftOperand = 0; var rightOperand = 0; - var op = '==='; + var op = '=='; // Left operand (properties and literals) @@ -505,7 +505,7 @@ // Operator - var ops = ['<', '<=', '>', '>=', '===', '!==']; + var ops = ['<', '<=', '>', '>=', '==', '!=']; var operators = [{ text : 'Operator', onselect : function() { diff --git a/Apps/Sandcastle/gallery/3D Tiles.jpg b/Apps/Sandcastle/gallery/3D Tiles.jpg new file mode 100644 index 000000000000..7d99bc035e2e Binary files /dev/null and b/Apps/Sandcastle/gallery/3D Tiles.jpg differ diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 9b900dab3a82..7c1e64d788d6 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -295,6 +295,13 @@ define([ this.batchTable.setAllColor(color); }; + /** + * Part of the {@link Cesium3DTileContent} interface. + */ + Batched3DModel3DTileContent.prototype.applyStyleWithShader = function(frameState, style) { + return false; + }; + /** * Part of the {@link Cesium3DTileContent} interface. */ diff --git a/Source/Scene/Cesium3DTileContent.js b/Source/Scene/Cesium3DTileContent.js index 2efcd57ccc49..1f8e82413628 100644 --- a/Source/Scene/Cesium3DTileContent.js +++ b/Source/Scene/Cesium3DTileContent.js @@ -198,6 +198,25 @@ define([ DeveloperError.throwInstantiationError(); }; + /** + * Apply a style to the content using a shader instead of a batch table. Currently this is only + * applicable for {@link PointCloud3DTileContent}. + *

+ * This is used to implement the Cesium3DTileContent interface, but is + * not part of the public Cesium API. + *

+ * + * @param {FrameSate} frameState The frame state. + * @param {Cesium3DTileStyle} style The style. + * + * @returns {Boolean} true if this content is styled with a shader; otherwise, false. + * + * @private + */ + Cesium3DTileContent.prototype.applyStyleWithShader = function(frameState, style) { + DeveloperError.throwInstantiationError(); + }; + /** * Called by the tile during tileset traversal to get the draw commands needed to render this content. * When the tile's content is in the PROCESSING state, this creates WebGL resources to ultimately diff --git a/Source/Scene/Cesium3DTileStyle.js b/Source/Scene/Cesium3DTileStyle.js index 644cdda0b9a3..f1fad426e116 100644 --- a/Source/Scene/Cesium3DTileStyle.js +++ b/Source/Scene/Cesium3DTileStyle.js @@ -27,6 +27,7 @@ define([ var DEFAULT_JSON_COLOR_EXPRESSION = 'color("#ffffff")'; var DEFAULT_JSON_BOOLEAN_EXPRESSION = true; + var DEFAULT_JSON_NUMBER_EXPRESSION = 1.0; /** * Evaluates an expression defined using the @@ -40,11 +41,11 @@ define([ * @example * tileset.style = new Cesium.Cesium3DTileStyle({ * color : { - * conditions : { - * '${Height} >= 100' : 'color("purple", 0.5)', - * '${Height} >= 50' : 'color("red")', - * 'true' : 'color("blue")' - * } + * conditions : [ + * ['${Height} >= 100', 'color("purple", 0.5)'], + * ['${Height} >= 50', 'color("red")'], + * ['true', 'color("blue")'] + * ] * }, * show : '${Height} > 0', * meta : { @@ -60,8 +61,16 @@ define([ this._readyPromise = when.defer(); this._color = undefined; this._show = undefined; + this._pointSize = undefined; this._meta = undefined; + this._colorShaderFunction = undefined; + this._showShaderFunction = undefined; + this._pointSizeShaderFunction = undefined; + this._colorShaderFunctionReady = false; + this._showShaderFunctionReady = false; + this._pointSizeShaderFunctionReady = false; + var style = this; if (typeof data === 'string') { RequestScheduler.request(data, loadJson).then(function(styleJson) { @@ -80,8 +89,24 @@ define([ that._style = clone(styleJson, true); styleJson = defaultValue(styleJson, defaultValue.EMPTY_OBJECT); + + if (!defined(styleJson.color)) { + // If there is no color style do not create a shader function. Otherwise a function would be created by the default style (white). + that._colorShaderFunctionReady = true; + } + + if (!defined(styleJson.show)) { + // If there is no show style do not create a shader function. + that._showShaderFunctionReady = true; + } + + if (!defined(styleJson.pointSize)) { + that._pointSizeShaderFunctionReady = true; + } + var colorExpression = defaultValue(styleJson.color, DEFAULT_JSON_COLOR_EXPRESSION); var showExpression = defaultValue(styleJson.show, DEFAULT_JSON_BOOLEAN_EXPRESSION); + var pointSizeExpression = defaultValue(styleJson.pointSize, DEFAULT_JSON_NUMBER_EXPRESSION); var color; if (typeof(colorExpression) === 'string') { @@ -97,10 +122,23 @@ define([ show = new Expression(String(showExpression)); } else if (typeof(showExpression) === 'string') { show = new Expression(showExpression); + } else if (defined(showExpression.conditions)) { + show = new ConditionsExpression(showExpression); } that._show = show; + var pointSize; + if (typeof(pointSizeExpression) === 'number') { + pointSize = new Expression(String(pointSizeExpression)); + } else if (typeof(pointSizeExpression) === 'string') { + pointSize = new Expression(pointSizeExpression); + } else if (defined(pointSizeExpression.conditions)) { + pointSize = new ConditionsExpression(pointSizeExpression); + } + + that._pointSize = pointSize; + var meta = {}; if (defined(styleJson.meta)) { var metaJson = defaultValue(styleJson.meta, defaultValue.EMPTY_OBJECT); @@ -261,6 +299,50 @@ define([ } }, + /** + * Gets or sets the {@link StyleExpression} object used to evaluate the style's pointSize property. + *

+ * The expression must return or convert to a Number. + *

+ * + * @memberof Cesium3DTileStyle.prototype + * + * @type {StyleExpression} + * + * @exception {DeveloperError} The style is not loaded. Use Cesium3DTileStyle.readyPromise or wait for Cesium3DTileStyle.ready to be true. + * + * @example + * var style = new Cesium3DTileStyle({ + * pointSize : '(${Temperature} > 90) ? 2.0 : 1.0' + * }); + * style.pointSize.evaluate(feature); // returns a Number + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override pointSize expression with a custom function + * style.pointSize = { + * evaluate : function(feature) { + * return 1.0; + * } + * }; + * + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language} + */ + pointSize : { + get : function() { + //>>includeStart('debug', pragmas.debug); + if (!this._ready) { + throw new DeveloperError('The style is not loaded. Use Cesium3DTileStyle.readyPromise or wait for Cesium3DTileStyle.ready to be true.'); + } + //>>includeEnd('debug'); + + return this._pointSize; + }, + set : function(value) { + this._pointSize = value; + } + }, + /** * Gets or sets the object containing application-specific expression that can be explicitly * evaluated, e.g., for display in a UI. @@ -294,9 +376,74 @@ define([ set : function(value) { this._meta = value; } - }, - + } }); + /** + * Gets the color shader function for this style. + * + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * + * @returns {String} The shader function. + * + * @private + */ + Cesium3DTileStyle.prototype.getColorShaderFunction = function(functionName, attributePrefix, shaderState) { + if (this._colorShaderFunctionReady) { + // Return the cached result, may be undefined + return this._colorShaderFunction; + } + + this._colorShaderFunctionReady = true; + this._colorShaderFunction = this.color.getShaderFunction(functionName, attributePrefix, shaderState, 'vec4'); + return this._colorShaderFunction; + }; + + /** + * Gets the show shader function for this style. + * + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * + * @returns {String} The shader function. + * + * @private + */ + Cesium3DTileStyle.prototype.getShowShaderFunction = function(functionName, attributePrefix, shaderState) { + if (this._showShaderFunctionReady) { + // Return the cached result, may be undefined + return this._showShaderFunction; + } + + this._showShaderFunctionReady = true; + this._showShaderFunction = this.show.getShaderFunction(functionName, attributePrefix, shaderState, 'bool'); + return this._showShaderFunction; + }; + + /** + * Gets the pointSize shader function for this style. + * + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * + * @returns {String} The shader function. + * + * @private + */ + Cesium3DTileStyle.prototype.getPointSizeShaderFunction = function(functionName, attributePrefix, shaderState) { + if (this._pointSizeShaderFunctionReady) { + // Return the cached result, may be undefined + return this._pointSizeShaderFunction; + } + + this._pointSizeShaderFunctionReady = true; + this._pointSizeShaderFunction = this.pointSize.getShaderFunction(functionName, attributePrefix, shaderState, 'float'); + return this._pointSizeShaderFunction; + }; + return Cesium3DTileStyle; }); diff --git a/Source/Scene/Cesium3DTileStyleEngine.js b/Source/Scene/Cesium3DTileStyleEngine.js index ad29ebabd14d..02e25862f186 100644 --- a/Source/Scene/Cesium3DTileStyleEngine.js +++ b/Source/Scene/Cesium3DTileStyleEngine.js @@ -74,7 +74,7 @@ define([ // 2) this tile is now visible, but it wasn't visible when the style was first assigned if (tile.lastStyleTime !== lastStyleTime) { tile.lastStyleTime = lastStyleTime; - styleCompositeContent(this, tile.content, stats); + styleCompositeContent(this, frameState, tile.content, stats); ++stats.numberOfTilesStyled; } @@ -82,26 +82,33 @@ define([ } }; - function styleCompositeContent(styleEngine, content, stats) { + function styleCompositeContent(styleEngine, frameState, content, stats) { var innerContents = content.innerContents; if (defined(innerContents)) { var length = innerContents.length; for (var i = 0; i < length; ++i) { // Recurse for composites of composites - styleCompositeContent(styleEngine, innerContents[i], stats); + styleCompositeContent(styleEngine, frameState, innerContents[i], stats); } } else { // Not a composite tile - styleContent(styleEngine, content, stats); + styleContent(styleEngine, frameState, content, stats); } } var scratchColor = new Color(); - function styleContent(styleEngine, content, stats) { - var length = content.featuresLength; + function styleContent(styleEngine, frameState, content, stats) { var style = styleEngine._style; + if (!content.applyStyleWithShader(frameState, style)) { + applyStyleWithBatchTable(content, stats, style); + } + + } + + function applyStyleWithBatchTable(content, stats, style) { + var length = content.featuresLength; stats.numberOfFeaturesStyled += length; if (!defined(style)) { diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index e03a74847b9f..4ac0a6b4a5da 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -523,11 +523,11 @@ define([ * @example * tileset.style = new Cesium.Cesium3DTileStyle({ * color : { - * conditions : { - * '${Height} >= 100' : 'color("purple", 0.5)', - * '${Height} >= 50' : 'color("red")', - * 'true' : 'color("blue")' - * } + * conditions : [ + * ['${Height} >= 100', 'color("purple", 0.5)'], + * ['${Height} >= 50', 'color("red")'], + * ['true', 'color("blue")'] + * ] * }, * show : '${Height} > 0', * meta : { diff --git a/Source/Scene/ConditionsExpression.js b/Source/Scene/ConditionsExpression.js index 3a6e85f7b8cb..e1b11bf6f87b 100644 --- a/Source/Scene/ConditionsExpression.js +++ b/Source/Scene/ConditionsExpression.js @@ -32,11 +32,11 @@ define([ * @example * var expression = new Cesium.Expression({ * expression : 'regExp("^1(\\d)").exec(${id})', - * conditions : { - * '${expression} === "1"' : 'color("#FF0000")', - * '${expression} === "2"' : 'color("#00FF00")', - * 'true' : 'color("#FFFFFF")' - * } + * conditions : [ + * ['${expression} == "1"', 'color("#FF0000")'], + * ['${expression} == "2"', 'color("#00FF00")'], + * ['true', 'color("#FFFFFF")'] + * ] * }); * expression.evaluateColor(feature, result); // returns a Cesium.Color object * @@ -78,18 +78,23 @@ define([ function setRuntime(expression) { var runtimeConditions = []; var conditions = expression._conditions; - var exp = expression._expression; - for (var cond in conditions) { - if (conditions.hasOwnProperty(cond)) { - var colorExpression = conditions[cond]; + if (defined(conditions)) { + var exp = expression._expression; + var length = conditions.length; + for (var i = 0; i < length; ++i) { + var statement = conditions[i]; + var cond = String(statement[0]); + var condExpression = String(statement[1]); if (defined(exp)) { cond = cond.replace(expressionPlaceholder, exp); + condExpression = condExpression.replace(expressionPlaceholder, exp); } else { cond = cond.replace(expressionPlaceholder, 'undefined'); + condExpression = condExpression.replace(expressionPlaceholder, 'undefined'); } runtimeConditions.push(new Statement( new Expression(cond), - new Expression(colorExpression) + new Expression(condExpression) )); } } @@ -134,12 +139,59 @@ define([ var length = conditions.length; for (var i = 0; i < length; ++i) { var statement = conditions[i]; - if (statement.condition.evaluate(feature, result)) { + if (statement.condition.evaluate(feature)) { return statement.expression.evaluateColor(feature, result); } } } }; + /** + * Gets the shader function for this expression. + * Returns undefined if the shader function can't be generated from this expression. + * + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * @param {String} returnType The return type of the generated function. + * + * @returns {String} The shader function. + * + * @private + */ + ConditionsExpression.prototype.getShaderFunction = function(functionName, attributePrefix, shaderState, returnType) { + var conditions = this._runtimeConditions; + if (!defined(conditions) || conditions.length === 0) { + return undefined; + } + + var shaderFunction = ''; + var length = conditions.length; + for (var i = 0; i < length; ++i) { + var statement = conditions[i]; + var condition = statement.condition.getShaderExpression(attributePrefix, shaderState); + var expression = statement.expression.getShaderExpression(attributePrefix, shaderState); + + if (!defined(condition) || !defined(expression)) { + return undefined; + } + + // Build the if/else chain from the list of conditions + shaderFunction += + ' ' + ((i === 0) ? 'if' : 'else if') + ' (' + condition + ') \n' + + ' { \n' + + ' return ' + expression + '; \n' + + ' } \n'; + } + + shaderFunction = returnType + ' ' + functionName + '() \n' + + '{ \n' + + shaderFunction + + ' return ' + returnType + '(1.0); \n' + // Return a default value if no conditions are met + '} \n'; + + return shaderFunction; + }; + return ConditionsExpression; }); diff --git a/Source/Scene/Empty3DTileContent.js b/Source/Scene/Empty3DTileContent.js index 29b91f1bc346..421b524db86f 100644 --- a/Source/Scene/Empty3DTileContent.js +++ b/Source/Scene/Empty3DTileContent.js @@ -113,6 +113,13 @@ define([ Empty3DTileContent.prototype.applyDebugSettings = function(enabled, color) { }; + /** + * Part of the {@link Cesium3DTileContent} interface. + */ + Empty3DTileContent.prototype.applyStyleWithShader = function(frameState, style) { + return false; + }; + /** * Part of the {@link Cesium3DTileContent} interface. */ diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index 84db81ac906d..90e893d49874 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -4,6 +4,7 @@ define([ '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', + '../Core/isArray', '../ThirdParty/jsep', './ExpressionNodeType' ], function( @@ -11,12 +12,13 @@ define([ defined, defineProperties, DeveloperError, + isArray, jsep, ExpressionNodeType) { "use strict"; var unaryOperators = ['!', '-', '+']; - var binaryOperators = ['+', '-', '*', '/', '%', '===', '!==', '>', '>=', '<', '<=', '&&', '||', '!~', '=~']; + var binaryOperators = ['+', '-', '*', '/', '%', '===', '==', '!==', '!=', '>', '>=', '<', '<=', '&&', '||', '!~', '=~']; var variableRegex = /\${(.*?)}/g; var backslashRegex = /\\/g; @@ -25,6 +27,22 @@ define([ var scratchColor = new Color(); + var ScratchStorage = { + scratchColorIndex : 0, + scratchColors : [new Color()], + reset : function() { + this.scratchColorIndex = 0; + }, + getColor : function() { + if (this.scratchColorIndex >= this.scratchColors.length) { + this.scratchColors.push(new Color()); + } + var scratchColor = this.scratchColors[this.scratchColorIndex]; + ++this.scratchColorIndex; + return scratchColor; + } + }; + /** * Evaluates an expression defined using the * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}. @@ -103,7 +121,12 @@ define([ * @returns {Boolean|Number|String|Color|RegExp} The result of evaluating the expression. */ Expression.prototype.evaluate = function(feature) { - return this._runtimeAst.evaluate(feature); + ScratchStorage.reset(); + var result = this._runtimeAst.evaluate(feature); + if (result instanceof Color) { + return Color.clone(result); + } + return result; }; /** @@ -114,7 +137,51 @@ define([ * @returns {Color} The modified result parameter or a new Color instance if one was not provided. */ Expression.prototype.evaluateColor = function(feature, result) { - return this._runtimeAst.evaluate(feature, result); + ScratchStorage.reset(); + var color = this._runtimeAst.evaluate(feature); + return Color.clone(color, result); + }; + + /** + * Gets the shader function for this expression. + * Returns undefined if the shader function can't be generated from this expression. + * + * @param {String} functionName Name to give to the generated function. + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * @param {String} returnType The return type of the generated function. + * + * @returns {String} The shader function. + * + * @private + */ + Expression.prototype.getShaderFunction = function(functionName, attributePrefix, shaderState, returnType) { + var shaderExpression = this.getShaderExpression(attributePrefix, shaderState); + if (!defined(shaderExpression)) { + return undefined; + } + + shaderExpression = returnType + ' ' + functionName + '() \n' + + '{ \n' + + ' return ' + shaderExpression + '; \n' + + '} \n'; + + return shaderExpression; + }; + + /** + * Gets the shader expression for this expression. + * Returns undefined if the shader expression can't be generated from this expression. + * + * @param {String} attributePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} shaderState Stores information about the generated shader function, including whether it is translucent. + * + * @returns {String} The shader expression. + * + * @private + */ + Expression.prototype.getShaderExpression = function(attributePrefix, shaderState) { + return this._runtimeAst.getShaderExpression(attributePrefix, shaderState); }; function Node(type, value, left, right, test) { @@ -465,8 +532,12 @@ define([ } else if (node._value === '%') { node.evaluate = node._evaluateMod; } else if (node._value === '===') { + node.evaluate = node._evaluateEqualsStrict; + } else if (node._value === '==') { node.evaluate = node._evaluateEquals; } else if (node._value === '!==') { + node.evaluate = node._evaluateNotEqualsStrict; + } else if (node._value === '!=') { node.evaluate = node._evaluateNotEquals; } else if (node._value === '<') { node.evaluate = node._evaluateLessThan; @@ -530,10 +601,8 @@ define([ return this._value; }; - Node.prototype._evaluateLiteralColor = function(feature, result) { - if (!defined(result)) { - result = new Color(); - } + Node.prototype._evaluateLiteralColor = function(feature) { + var result = ScratchStorage.getColor(); var args = this._left; if (this._value === 'color') { if (!defined(args)) { @@ -542,7 +611,7 @@ define([ Color.fromCssColorString(args[0].evaluate(feature, result), result); result.alpha = args[1].evaluate(feature, result); } else { - Color.fromCssColorString(this._left[0].evaluate(feature, result), result); + Color.fromCssColorString(args[0].evaluate(feature, result), result); } } else if (this._value === 'rgb') { Color.fromBytes( @@ -605,32 +674,32 @@ define([ } // PERFORMANCE_IDEA: Determine if parent property needs to be computed before runtime - Node.prototype._evaluateMemberDot = function(feature, result) { + Node.prototype._evaluateMemberDot = function(feature) { if(checkFeature(this._left)) { return feature.getProperty(this._right); } - var property = this._left.evaluate(feature, result); + var property = this._left.evaluate(feature); if (!defined(property)) { return undefined; } return property[this._right]; }; - Node.prototype._evaluateMemberBrackets = function(feature, result) { + Node.prototype._evaluateMemberBrackets = function(feature) { if(checkFeature(this._left)) { - return feature.getProperty(this._right.evaluate(feature, result)); + return feature.getProperty(this._right.evaluate(feature)); } - var property = this._left.evaluate(feature, result); + var property = this._left.evaluate(feature); if (!defined(property)) { return undefined; } - return property[this._right.evaluate(feature, result)]; + return property[this._right.evaluate(feature)]; }; - Node.prototype._evaluateArray = function(feature, result) { + Node.prototype._evaluateArray = function(feature) { var array = []; - for (var i = 0; i right; }; - Node.prototype._evaluateGreaterThanOrEquals = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateGreaterThanOrEquals = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); return left >= right; }; - Node.prototype._evaluateOr = function(feature, result) { - var left = this._left.evaluate(feature, result); + Node.prototype._evaluateOr = function(feature) { + var left = this._left.evaluate(feature); //>>includeStart('debug', pragmas.debug); if (typeof(left) !== 'boolean') { throw new DeveloperError('Error: Operation is undefined.'); @@ -687,7 +756,7 @@ define([ return true; } - var right = this._right.evaluate(feature, result); + var right = this._right.evaluate(feature); //>>includeStart('debug', pragmas.debug); if (typeof(right) !== 'boolean') { throw new DeveloperError('Error: Operation is undefined.'); @@ -696,8 +765,8 @@ define([ return left || right; }; - Node.prototype._evaluateAnd = function(feature, result) { - var left = this._left.evaluate(feature, result); + Node.prototype._evaluateAnd = function(feature) { + var left = this._left.evaluate(feature); //>>includeStart('debug', pragmas.debug); if (typeof(left) !== 'boolean') { throw new DeveloperError('Error: Operation is undefined.'); @@ -709,7 +778,7 @@ define([ return false; } - var right = this._right.evaluate(feature, result); + var right = this._right.evaluate(feature); //>>includeStart('debug', pragmas.debug); if (typeof(right) !== 'boolean') { throw new DeveloperError('Error: Operation is undefined.'); @@ -718,108 +787,131 @@ define([ return left && right; }; - Node.prototype._evaluatePlus = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluatePlus = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { - return Color.add(left, right, scratchColor); + return Color.add(left, right, ScratchStorage.getColor()); } return left + right; }; - Node.prototype._evaluateMinus = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateMinus = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { - return Color.subtract(left, right, scratchColor); + return Color.subtract(left, right, ScratchStorage.getColor()); } return left - right; }; - Node.prototype._evaluateTimes = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateTimes = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { - return Color.multiply(left, right, scratchColor); + return Color.multiply(left, right, ScratchStorage.getColor()); } else if ((right instanceof Color) && (typeof(left) === 'number')) { - return Color.multiplyByScalar(right, left, scratchColor); + return Color.multiplyByScalar(right, left, ScratchStorage.getColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { - return Color.multiplyByScalar(left, right, scratchColor); + return Color.multiplyByScalar(left, right, ScratchStorage.getColor()); } return left * right; }; - Node.prototype._evaluateDivide = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateDivide = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { - return Color.divide(left, right, scratchColor); + return Color.divide(left, right, ScratchStorage.getColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { - return Color.divideByScalar(left, right, scratchColor); + return Color.divideByScalar(left, right, ScratchStorage.getColor()); } return left / right; }; - Node.prototype._evaluateMod = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateMod = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { - return Color.mod(left, right, scratchColor); + return Color.mod(left, right, ScratchStorage.getColor()); } return left % right; }; - Node.prototype._evaluateEquals = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateEqualsStrict = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { return Color.equals(left, right); } return left === right; }; - Node.prototype._evaluateNotEquals = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateEquals = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); + if ((right instanceof Color) && (left instanceof Color)) { + return Color.equals(left, right); + } + + // Specifically want to do an abstract equality comparison (==) instead of a strict equality comparison (===) + // so that cases like "5 == '5'" return true. Tell jsHint to ignore this line. + return left == right; // jshint ignore:line + }; + + Node.prototype._evaluateNotEqualsStrict = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { return !Color.equals(left, right); } return left !== right; }; - Node.prototype._evaluateConditional = function(feature, result) { - if (this._test.evaluate(feature, result)) { - return this._left.evaluate(feature, result); + Node.prototype._evaluateNotEquals = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); + if ((right instanceof Color) && (left instanceof Color)) { + return !Color.equals(left, 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. + return left != right; // jshint ignore:line + }; + + Node.prototype._evaluateConditional = function(feature) { + if (this._test.evaluate(feature)) { + return this._left.evaluate(feature); } - return this._right.evaluate(feature, result); + return this._right.evaluate(feature); }; - Node.prototype._evaluateNaN = function(feature, result) { - return isNaN(this._left.evaluate(feature, result)); + Node.prototype._evaluateNaN = function(feature) { + return isNaN(this._left.evaluate(feature)); }; - Node.prototype._evaluateIsFinite = function(feature, result) { - return isFinite(this._left.evaluate(feature, result)); + Node.prototype._evaluateIsFinite = function(feature) { + return isFinite(this._left.evaluate(feature)); }; - Node.prototype._evaluateBooleanConversion = function(feature, result) { - return Boolean(this._left.evaluate(feature, result)); + Node.prototype._evaluateBooleanConversion = function(feature) { + return Boolean(this._left.evaluate(feature)); }; - Node.prototype._evaluateNumberConversion = function(feature, result) { - return Number(this._left.evaluate(feature, result)); + Node.prototype._evaluateNumberConversion = function(feature) { + return Number(this._left.evaluate(feature)); }; - Node.prototype._evaluateStringConversion = function(feature, result) { - return String(this._left.evaluate(feature, result)); + Node.prototype._evaluateStringConversion = function(feature) { + return String(this._left.evaluate(feature)); }; - Node.prototype._evaluateRegExp = function(feature, result) { - var pattern = this._value.evaluate(feature, result); + Node.prototype._evaluateRegExp = function(feature) { + var pattern = this._value.evaluate(feature); var flags = ''; if (defined(this._left)) { - flags = this._left.evaluate(feature, result); + flags = this._left.evaluate(feature); } var exp; @@ -833,13 +925,13 @@ define([ return exp; }; - Node.prototype._evaluateRegExpTest = function(feature, result) { - return this._left.evaluate(feature, result).test(this._right.evaluate(feature, result)); + Node.prototype._evaluateRegExpTest = function(feature) { + return this._left.evaluate(feature).test(this._right.evaluate(feature)); }; - Node.prototype._evaluateRegExpMatch = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateRegExpMatch = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); if (left instanceof RegExp) { return left.test(right); } else if (right instanceof RegExp) { @@ -849,9 +941,9 @@ define([ } }; - Node.prototype._evaluateRegExpNotMatch = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateRegExpNotMatch = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); if (left instanceof RegExp) { return !(left.test(right)); } else if (right instanceof RegExp) { @@ -861,16 +953,16 @@ define([ } }; - Node.prototype._evaluateRegExpExec = function(feature, result) { - var exec = this._left.evaluate(feature, result).exec(this._right.evaluate(feature, result)); + Node.prototype._evaluateRegExpExec = function(feature) { + var exec = this._left.evaluate(feature).exec(this._right.evaluate(feature)); if (!defined(exec)) { return null; } return exec[1]; }; - Node.prototype._evaluateToString = function(feature, result) { - var left = this._left.evaluate(feature, result); + Node.prototype._evaluateToString = function(feature) { + var left = this._left.evaluate(feature); if ((left instanceof RegExp) || (left instanceof Color)) { return String(left); } @@ -881,5 +973,273 @@ define([ //>>includeEnd('debug'); }; + function convertHSLToRGB(ast) { + // Check if the color contains any nested expressions to see if the color can be converted here. + // E.g. "hsl(0.9, 0.6, 0.7)" is able to convert directly to rgb, "hsl(0.9, 0.6, ${Height})" is not. + var channels = ast._left; + var length = channels.length; + for (var i = 0; i < length; ++i) { + if (channels[i]._type !== ExpressionNodeType.LITERAL_NUMBER) { + return undefined; + } + } + var h = channels[0]._value; + var s = channels[1]._value; + var l = channels[2]._value; + var a = (length === 4) ? channels[3]._value : 1.0; + return Color.fromHsl(h, s, l, a, scratchColor); + } + + function convertRGBToColor(ast) { + // Check if the color contains any nested expressions to see if the color can be converted here. + // E.g. "rgb(255, 255, 255)" is able to convert directly to Color, "rgb(255, 255, ${Height})" is not. + var channels = ast._left; + var length = channels.length; + for (var i = 0; i < length; ++i) { + if (channels[i]._type !== ExpressionNodeType.LITERAL_NUMBER) { + return undefined; + } + } + var color = scratchColor; + color.red = channels[0]._value / 255.0; + color.green = channels[1]._value / 255.0; + color.blue = channels[2]._value / 255.0; + color.alpha = (length === 4) ? channels[3]._value : 1.0; + return color; + } + + function numberToString(number) { + if (number % 1 === 0) { + // Add a .0 to whole numbers + return number.toFixed(1); + } else { + return number.toString(); + } + } + + function colorToVec3(color) { + var r = numberToString(color.red); + var g = numberToString(color.green); + var b = numberToString(color.blue); + return 'vec3(' + r + ', ' + g + ', ' + b + ')'; + } + + function colorToVec4(color) { + var r = numberToString(color.red); + var g = numberToString(color.green); + var b = numberToString(color.blue); + var a = numberToString(color.alpha); + return 'vec4(' + r + ', ' + g + ', ' + b + ', ' + a + ')'; + } + + function getExpressionArray(array, attributePrefix, shaderState) { + var length = array.length; + var expressions = new Array(length); + for (var i = 0; i < length; ++i) { + var shader = array[i].getShaderExpression(attributePrefix, shaderState); + if (!defined(shader)) { + // If any of the expressions are not valid, the array is not valid + return undefined; + } + expressions[i] = shader; + } + return expressions; + } + + Node.prototype.getShaderExpression = function(attributePrefix, shaderState) { + var color; + var left; + var right; + var test; + + 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); + } else { + left = this._left.getShaderExpression(attributePrefix, shaderState); + } + if (!defined(left)) { + // If the left side is not valid shader code, then the expression is not valid + return undefined; + } + } + + if (defined(this._right)) { + right = this._right.getShaderExpression(attributePrefix, shaderState); + if (!defined(right)) { + // If the right side is not valid shader code, then the expression is not valid + return undefined; + } + } + + if (defined(this._test)) { + test = this._test.getShaderExpression(attributePrefix, shaderState); + if (!defined(test)) { + // If the test is not valid shader code, then the expression is not valid + return undefined; + } + } + + if (isArray(this._value)) { + // For ARRAY type + value = getExpressionArray(this._value, attributePrefix, shaderState); + if (!defined(value)) { + // If the values are not valid shader code, then the expression is not valid + return undefined; + } + } + + switch (type) { + case ExpressionNodeType.VARIABLE: + return attributePrefix + value; + case ExpressionNodeType.UNARY: + // Supported types: +, -, !, Boolean, Number + if (value === 'Boolean') { + return 'bool(' + left + ')'; + } else if (value === 'Number') { + return 'float(' + left + ')'; + } + //>>includeStart('debug', pragmas.debug); + else if ((value === 'isNaN') || (value === 'isFinite') || (value === 'String')) { + throw new DeveloperError('Error generating style shader: "' + value + '" is not supported.'); + } + //>>includeEnd('debug'); + return value + left; + case ExpressionNodeType.BINARY: + // Supported types: ||, &&, ===, ==, !==, !=, <, >, <=, >=, +, -, *, /, % + if (value === '%') { + return 'mod(' + left + ', ' + right + ')'; + } else if (value === '===') { + return '(' + left + ' == ' + right + ')'; + } else if (value === '!==') { + return '(' + left + ' != ' + right + ')'; + } + return '(' + left + ' ' + value + ' ' + right + ')'; + 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. + return left + '[int(' + right + ')]'; + case ExpressionNodeType.FUNCTION_CALL: + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: "' + value + '" is not supported.'); + //>>includeEnd('debug'); + case ExpressionNodeType.ARRAY: + if (value.length === 4) { + return 'vec4(' + value[0] + ', ' + value[1] + ', ' + value[2] + ', ' + value[3] + ')'; + } else if (value.length === 3) { + return 'vec3(' + value[0] + ', ' + value[1] + ', ' + value[2] + ')'; + } else if (value.length === 2) { + return 'vec2(' + value[0] + ', ' + value[1] + ')'; + } + //>>includeStart('debug', pragmas.debug); + else { + throw new DeveloperError('Error generating style shader: Invalid array length. Array length should be 2, 3, or 4.'); + } + //>>includeEnd('debug'); + break; + case ExpressionNodeType.REGEX: + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: Regular expressions are not supported.'); + //>>includeEnd('debug'); + case ExpressionNodeType.VARIABLE_IN_STRING: + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: Converting a variable to a string is not supported.'); + //>>includeEnd('debug'); + case ExpressionNodeType.LITERAL_NULL: + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: null is not supported.'); + //>>includeEnd('debug'); + case ExpressionNodeType.LITERAL_BOOLEAN: + return value ? 'true' : 'false'; + case ExpressionNodeType.LITERAL_NUMBER: + return numberToString(value); + case ExpressionNodeType.LITERAL_STRING: + // The only supported strings are css color strings + color = Color.fromCssColorString(value, scratchColor); + if (defined(color)) { + return colorToVec3(color); + } + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: String literals are not supported.'); + //>>includeEnd('debug'); + case ExpressionNodeType.LITERAL_COLOR: + var args = left; + if (value === 'color') { + if (!defined(args)) { + return 'vec4(1.0)'; + } else if (args.length > 1) { + var rgb = args[0]; + var alpha = args[1]; + if (alpha !== '1.0') { + shaderState.translucent = true; + } + return 'vec4(' + rgb + ', ' + alpha + ')'; + } else { + return 'vec4(' + args[0] + ', 1.0)'; + } + } else if (value === 'rgb') { + color = convertRGBToColor(this); + if (defined(color)) { + return colorToVec4(color); + } else { + return 'vec4(' + args[0] + ' / 255.0, ' + args[1] + ' / 255.0, ' + args[2] + ' / 255.0, 1.0)'; + } + } else if (value === 'rgba') { + if (args[3] !== '1.0') { + shaderState.translucent = true; + } + color = convertRGBToColor(this); + if (defined(color)) { + return colorToVec4(color); + } else { + return 'vec4(' + args[0] + ' / 255.0, ' + args[1] + ' / 255.0, ' + args[2] + ' / 255.0, ' + args[3] + ')'; + } + } else if (value === 'hsl') { + color = convertHSLToRGB(this); + if (defined(color)) { + return colorToVec4(color); + } else { + return 'vec4(czm_HSLToRGB(vec3(' + args[0] + ', ' + args[1] + ', ' + args[2] + ')), 1.0)'; + } + } else if (value === 'hsla') { + color = convertHSLToRGB(this); + if (defined(color)) { + if (color.alpha !== 1.0) { + shaderState.translucent = true; + } + return colorToVec4(color); + } else { + if (args[3] !== '1.0') { + shaderState.translucent = true; + } + return 'vec4(czm_HSLToRGB(vec3(' + args[0] + ', ' + args[1] + ', ' + args[2] + ')), ' + args[3] + ')'; + } + } + break; + case ExpressionNodeType.LITERAL_REGEX: + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: Regular expressions are not supported.'); + //>>includeEnd('debug'); + case ExpressionNodeType.LITERAL_UNDEFINED: + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: undefined is not supported.'); + //>>includeEnd('debug'); + } + }; + return Expression; }); diff --git a/Source/Scene/Instanced3DModel3DTileContent.js b/Source/Scene/Instanced3DModel3DTileContent.js index 0fb180a0f8f6..7b2b223565c4 100644 --- a/Source/Scene/Instanced3DModel3DTileContent.js +++ b/Source/Scene/Instanced3DModel3DTileContent.js @@ -466,6 +466,13 @@ define([ this.batchTable.setAllColor(color); }; + /** + * Part of the {@link Cesium3DTileContent} interface. + */ + Instanced3DModel3DTileContent.prototype.applyStyleWithShader = function(frameState, style) { + return false; + }; + /** * Part of the {@link Cesium3DTileContent} interface. */ diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 9c75f83aab9b..d13a66f26cfe 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -14,6 +14,7 @@ define([ '../Core/loadArrayBuffer', '../Core/Matrix3', '../Core/Matrix4', + '../Core/oneTimeWarning', '../Core/PrimitiveType', '../Core/Request', '../Core/RequestScheduler', @@ -48,6 +49,7 @@ define([ loadArrayBuffer, Matrix3, Matrix4, + oneTimeWarning, PrimitiveType, Request, RequestScheduler, @@ -91,13 +93,28 @@ define([ this._pickCommand = undefined; this._pickId = undefined; // Only defined when batchTable is undefined this._isTranslucent = false; + this._styleTranslucent = false; this._constantColor = Color.clone(Color.WHITE); this._rtcCenter = undefined; + // These values are used to regenerate the shader when the style changes + this._styleableShaderAttributes = undefined; + this._isQuantized = false; + this._isOctEncoded16P = false; + this._isRGB565 = false; + this._hasColors = false; + this._hasNormals = false; + this._hasBatchIds = false; + + // TODO : How to expose this? Will this be part of the point cloud styling or a property of the tileset? + // Use per-point normals to hide back-facing points. + this.backFaceCulling = false; + this._backFaceCulling = false; + this._opaqueRenderState = undefined; this._translucentRenderState = undefined; - this._highlightColor = this._constantColor; + this._highlightColor = Color.clone(Color.WHITE); this._pointSize = 2.0; this._quantizedVolumeScale = undefined; this._quantizedVolumeOffset = undefined; @@ -384,7 +401,7 @@ define([ isOctEncoded16P = true; } - // Get the batchIds and batch table + // Get the batchIds and batch table. BATCH_ID does not need to be defined when the point cloud has per-point properties. var batchIds; if (defined(featureTableJson.BATCH_ID)) { var componentType; @@ -412,10 +429,23 @@ define([ this.batchTable = new Cesium3DTileBatchTable(this, batchLength, batchTableJson, batchTableBinary); } - // If points are not batched and there there are per-point properties, use these properties for styling purposes + // If points are not batched and there are per-point properties, use these properties for styling purposes var styleableProperties; if (!defined(batchIds) && defined(batchTableBinary)) { styleableProperties = Cesium3DTileBatchTable.getBinaryProperties(pointsLength, batchTableJson, batchTableBinary); + + // WebGL does not support UNSIGNED_INT, INT, or DOUBLE vertex attributes. Convert these to FLOAT. + for (var name in styleableProperties) { + if (styleableProperties.hasOwnProperty(name)) { + var property = styleableProperties[name]; + var typedArray = property.typedArray; + var componentDatatype = ComponentDatatype.fromTypedArray(typedArray); + if (componentDatatype === ComponentDatatype.INT || componentDatatype === ComponentDatatype.UNSIGNED_INT || componentDatatype === ComponentDatatype.DOUBLE) { + oneTimeWarning('Cast pnts property to floats', 'Point cloud property "' + name + '" will be casted to a float array because INT, UNSIGNED_INT, and DOUBLE are not valid WebGL vertex attribute types. Some precision may be lost.'); + property.typedArray = new Float32Array(typedArray); + } + } + } } this._parsedContent = { @@ -424,16 +454,26 @@ define([ colors : colors, normals : normals, batchIds : batchIds, - isQuantized : isQuantized, - isRGB565 : isRGB565, - isOctEncoded16P : isOctEncoded16P, styleableProperties : styleableProperties }; + this._isQuantized = isQuantized; + this._isOctEncoded16P = isOctEncoded16P; + this._isRGB565 = isRGB565; + this._hasColors = defined(colors); + this._hasNormals = defined(normals); + this._hasBatchIds = defined(batchIds); + this.state = Cesium3DTileContentState.PROCESSING; this._contentReadyToProcessPromise.resolve(this); }; + var positionLocation = 0; + var colorLocation = 1; + var normalLocation = 2; + var batchIdLocation = 3; + var numberOfAttributes = 4; + function createResources(content, frameState) { var context = frameState.context; var parsedContent = content._parsedContent; @@ -442,59 +482,34 @@ define([ var colors = parsedContent.colors; var normals = parsedContent.normals; var batchIds = parsedContent.batchIds; - var isQuantized = parsedContent.isQuantized; - var isRGB565 = parsedContent.isRGB565; - var isOctEncoded16P = parsedContent.isOctEncoded16P; var styleableProperties = parsedContent.styleableProperties; + var hasStyleableProperties = defined(styleableProperties); + var isQuantized = content._isQuantized; + var isOctEncoded16P = content._isOctEncoded16P; + var isRGB565 = content._isRGB565; var isTranslucent = content._isTranslucent; - - var hasColors = defined(colors); - var hasNormals = defined(normals); - var hasBatchIds = defined(batchIds); + var hasColors = content._hasColors; + var hasNormals = content._hasNormals; + var hasBatchIds = content._hasBatchIds; var batchTable = content.batchTable; var hasBatchTable = defined(batchTable); - var hasStyleableProperties = defined(styleableProperties); - - // TODO : How to expose this? Will this be part of the point cloud styling or a property of the tileset? - // Use per-point normals to hide back-facing points. - var backFaceCulling = false; - - var positionAttributeLocation = 0; - var colorAttributeLocation = 1; - var normalAttributeLocation = 2; - var batchIdAttributeLocation = 3; - var numberOfAttributes = 4; - var styleableShaderAttributes; - var styleableVertexAttributes; - var styleableVertexAttributeLocations; + var styleableVertexAttributes = []; + var styleableShaderAttributes = {}; + content._styleableShaderAttributes = styleableShaderAttributes; if (hasStyleableProperties) { - styleableShaderAttributes = ''; - styleableVertexAttributes = []; - styleableVertexAttributeLocations = {}; - var attributeLocation = numberOfAttributes; for (var name in styleableProperties) { if (styleableProperties.hasOwnProperty(name)) { - var property = styleableProperties[name]; // TODO : this will not handle matrix types currently - var componentCount = property.componentCount; + var property = styleableProperties[name]; var typedArray = property.typedArray; + var componentCount = property.componentCount; var componentDatatype = ComponentDatatype.fromTypedArray(typedArray); - // Append attributes to shader - var attributeName = 'czm_pnts_' + name; - var attributeType; - if (componentCount === 1) { - attributeType = 'float'; - } else { - attributeType = 'vec' + componentCount; - } - styleableShaderAttributes += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; - var vertexBuffer = Buffer.createVertexBuffer({ context : context, typedArray : property.typedArray, @@ -512,116 +527,24 @@ define([ }; styleableVertexAttributes.push(vertexAttribute); - styleableVertexAttributeLocations[attributeName] = attributeLocation; + styleableShaderAttributes[name] = { + location : attributeLocation, + componentCount : componentCount + }; ++attributeLocation; } } } - var vs = 'attribute vec3 a_position; \n' + - 'varying vec4 v_color; \n' + - 'uniform float u_pointSize; \n' + - 'uniform vec4 u_highlightColor; \n'; - - if (hasColors) { - if (isTranslucent) { - vs += 'attribute vec4 a_color; \n'; - } else if (isRGB565) { - vs += 'attribute float a_color; \n' + - 'const float SHIFT_RIGHT_11 = 1.0 / 2048.0; \n' + - 'const float SHIFT_RIGHT_5 = 1.0 / 32.0; \n' + - 'const float SHIFT_LEFT_11 = 2048.0; \n' + - 'const float SHIFT_LEFT_5 = 32.0; \n' + - 'const float NORMALIZE_6 = 1.0 / 64.0; \n' + - 'const float NORMALIZE_5 = 1.0 / 32.0; \n'; - } else { - vs += 'attribute vec3 a_color; \n'; - } - } - if (hasNormals) { - if (isOctEncoded16P) { - vs += 'attribute vec2 a_normal; \n'; - } else { - vs += 'attribute vec3 a_normal; \n'; - } - } - - if (hasBatchIds) { - vs += 'attribute float a_batchId; \n'; - } - - if (hasStyleableProperties) { - vs += styleableShaderAttributes; - } - - if (isQuantized) { - vs += 'uniform vec3 u_quantizedVolumeScale; \n'; - } - - vs += 'void main() \n' + - '{ \n'; - - if (hasColors) { - if (isTranslucent) { - vs += ' vec4 color = a_color * u_highlightColor; \n'; - } else if (isRGB565) { - vs += ' float compressed = a_color; \n' + - ' float r = floor(compressed * SHIFT_RIGHT_11); \n' + - ' compressed -= r * SHIFT_LEFT_11; \n' + - ' float g = floor(compressed * SHIFT_RIGHT_5); \n' + - ' compressed -= g * SHIFT_LEFT_5; \n' + - ' float b = compressed; \n' + - ' vec3 rgb = vec3(r * NORMALIZE_5, g * NORMALIZE_6, b * NORMALIZE_5); \n' + - ' vec4 color = vec4(rgb * u_highlightColor.rgb, u_highlightColor.a); \n'; - } else { - vs += ' vec4 color = vec4(a_color * u_highlightColor.rgb, u_highlightColor.a); \n'; - } - } else { - vs += ' vec4 color = u_highlightColor; \n'; - } - - if (hasNormals) { - if (isOctEncoded16P) { - vs += ' vec3 normal = czm_octDecode(a_normal); \n'; - } else { - vs += ' vec3 normal = a_normal; \n'; - } - - vs += ' normal = czm_normal * normal; \n' + - ' float diffuseStrength = czm_getLambertDiffuse(czm_sunDirectionEC, normal); \n' + - ' diffuseStrength = max(diffuseStrength, 0.4); \n' + // Apply some ambient lighting - ' color *= diffuseStrength; \n'; - } - - if (isQuantized) { - vs += ' vec3 position = a_position * u_quantizedVolumeScale; \n'; - } else { - vs += ' vec3 position = a_position; \n'; - } - - vs += ' v_color = color; \n' + - ' gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n' + - ' gl_PointSize = u_pointSize; \n'; - - if (hasNormals && backFaceCulling) { - vs += ' float visible = step(-normal.z, 0.0); \n' + - ' gl_Position *= visible; \n'; - } - - vs += '} \n'; - - var fs = 'varying vec4 v_color; \n' + - 'void main() \n' + - '{ \n' + - ' gl_FragColor = v_color; \n' + - '} \n'; - var uniformMap = { u_pointSize : function() { return content._pointSize; }, u_highlightColor : function() { return content._highlightColor; + }, + u_constantColor : function() { + return content._constantColor; } }; @@ -669,7 +592,7 @@ define([ var attributes = []; if (isQuantized) { attributes.push({ - index : positionAttributeLocation, + index : positionLocation, vertexBuffer : positionsVertexBuffer, componentsPerAttribute : 3, componentDatatype : ComponentDatatype.UNSIGNED_SHORT, @@ -679,7 +602,7 @@ define([ }); } else { attributes.push({ - index : positionAttributeLocation, + index : positionLocation, vertexBuffer : positionsVertexBuffer, componentsPerAttribute : 3, componentDatatype : ComponentDatatype.FLOAT, @@ -692,7 +615,7 @@ define([ if (hasColors) { if (isRGB565) { attributes.push({ - index : colorAttributeLocation, + index : colorLocation, vertexBuffer : colorsVertexBuffer, componentsPerAttribute : 1, componentDatatype : ComponentDatatype.UNSIGNED_SHORT, @@ -703,7 +626,7 @@ define([ } else { var colorComponentsPerAttribute = isTranslucent ? 4 : 3; attributes.push({ - index : colorAttributeLocation, + index : colorLocation, vertexBuffer : colorsVertexBuffer, componentsPerAttribute : colorComponentsPerAttribute, componentDatatype : ComponentDatatype.UNSIGNED_BYTE, @@ -717,7 +640,7 @@ define([ if (hasNormals) { if (isOctEncoded16P) { attributes.push({ - index : normalAttributeLocation, + index : normalLocation, vertexBuffer : normalsVertexBuffer, componentsPerAttribute : 2, componentDatatype : ComponentDatatype.UNSIGNED_BYTE, @@ -727,7 +650,7 @@ define([ }); } else { attributes.push({ - index : normalAttributeLocation, + index : normalLocation, vertexBuffer : normalsVertexBuffer, componentsPerAttribute : 3, componentDatatype : ComponentDatatype.FLOAT, @@ -740,7 +663,7 @@ define([ if (hasBatchIds) { attributes.push({ - index : batchIdAttributeLocation, + index : batchIdLocation, vertexBuffer : batchIdsVertexBuffer, componentsPerAttribute : 1, componentDatatype : ComponentDatatype.fromTypedArray(batchIds), @@ -759,49 +682,28 @@ define([ attributes : attributes }); - var attributeLocations = { - a_position : positionAttributeLocation - }; + var drawUniformMap = uniformMap; - if (hasColors) { - attributeLocations = combine(attributeLocations, { - a_color : colorAttributeLocation - }); + if (hasBatchTable) { + drawUniformMap = batchTable.getUniformMapCallback()(uniformMap); } - if (hasNormals) { - attributeLocations = combine(attributeLocations, { - a_normal : normalAttributeLocation - }); - } + var pickUniformMap; - if (hasBatchIds) { - attributeLocations = combine(attributeLocations, { - a_batchId : batchIdAttributeLocation + if (hasBatchTable) { + pickUniformMap = batchTable.getPickUniformMapCallback()(uniformMap); + } else { + content._pickId = context.createPickId({ + primitive : content }); - } - if (hasStyleableProperties) { - attributeLocations = combine(attributeLocations, styleableVertexAttributeLocations); - } - - var drawVS = vs; - var drawFS = fs; - var drawUniformMap = uniformMap; - - if (hasBatchTable) { - drawVS = batchTable.getVertexShaderCallback()(vs, false); - drawFS = batchTable.getFragmentShaderCallback()(fs, false); - drawUniformMap = batchTable.getUniformMapCallback()(uniformMap); + pickUniformMap = combine(uniformMap, { + czm_pickColor : function() { + return content._pickId.color; + } + }); } - var shaderProgram = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : drawVS, - fragmentShaderSource : drawFS, - attributeLocations : attributeLocations - }); - content._opaqueRenderState = RenderState.fromCache({ depthTest : { enabled : true @@ -823,43 +725,13 @@ define([ primitiveType : PrimitiveType.POINTS, vertexArray : vertexArray, count : pointsLength, - shaderProgram : shaderProgram, + shaderProgram : undefined, // Updated in createShaders uniformMap : drawUniformMap, renderState : isTranslucent ? content._translucentRenderState : content._opaqueRenderState, pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE, owner : content }); - var pickVS; - var pickFS; - var pickUniformMap; - - if (hasBatchTable) { - pickVS = batchTable.getPickVertexShaderCallback()(vs); - pickFS = batchTable.getPickFragmentShaderCallback()(fs); - pickUniformMap = batchTable.getPickUniformMapCallback()(uniformMap); - } else { - content._pickId = context.createPickId({ - primitive : content - }); - - pickUniformMap = combine(uniformMap, { - czm_pickColor : function() { - return content._pickId.color; - } - }); - - pickVS = vs; - pickFS = ShaderSource.createPickFragmentShaderSource(fs, 'uniform'); - } - - var pickShaderProgram = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : pickVS, - fragmentShaderSource : pickFS, - attributeLocations : attributeLocations - }); - content._pickCommand = new DrawCommand({ boundingVolume : content._tile.contentBoundingVolume.boundingSphere, cull : false, // Already culled by 3D tiles @@ -867,7 +739,7 @@ define([ primitiveType : PrimitiveType.POINTS, vertexArray : vertexArray, count : pointsLength, - shaderProgram : pickShaderProgram, + shaderProgram : undefined, // Updated in createShaders uniformMap : pickUniformMap, renderState : isTranslucent ? content._translucentRenderState : content._opaqueRenderState, pass : isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE, @@ -875,11 +747,374 @@ define([ }); } + var semantics = ['POSITION', 'COLOR', 'NORMAL']; + + function getStyleableProperties(source, properties) { + // Get all the properties used by this style + var regex = /czm_tiles3d_style_(\w+)/g; + var matches = regex.exec(source); + while (matches !== null) { + var name = matches[1]; + if ((semantics.indexOf(name) === -1) && (properties.indexOf(name) === -1)) { + properties.push(name); + } + matches = regex.exec(source); + } + } + + function getStyleableSemantics(source, properties) { + // Get the semantics used by this style + var length = semantics.length; + for (var i = 0; i < length; ++i) { + var semantic = semantics[i]; + var styleName = 'czm_tiles3d_style_' + semantic; + if (source.indexOf(styleName) >= 0) { + properties.push(semantic); + } + } + } + + function getVertexAttribute(vertexArray, index) { + var numberOfAttributes = vertexArray.numberOfAttributes; + for (var i = 0; i < numberOfAttributes; ++i) { + var attribute = vertexArray.getAttribute(i); + if (attribute.index === index) { + return attribute; + } + } + } + + function modifyStyleFunction(source) { + // Replace occurrences of czm_tiles3d_style_SEMANTIC with semantic + var length = semantics.length; + for (var i = 0; i < length; ++i) { + var semantic = semantics[i]; + var styleName = 'czm_tiles3d_style_' + semantic; + var replaceName = semantic.toLowerCase(); + source = source.replace(new RegExp(styleName, 'g'), replaceName); + } + + // Edit the function header to accept the point position, color, and normal + return source.replace('()', '(vec3 position, vec4 color, vec3 normal)'); + } + + function createShaders(content, frameState, style) { + var i; + var name; + var attribute; + + var context = frameState.context; + var batchTable = content.batchTable; + var hasBatchTable = defined(batchTable); + var hasStyle = defined(style); + var isQuantized = content._isQuantized; + var isOctEncoded16P = content._isOctEncoded16P; + var isRGB565 = content._isRGB565; + var isTranslucent = content._isTranslucent; + var hasColors = content._hasColors; + var hasNormals = content._hasNormals; + var hasBatchIds = content._hasBatchIds; + var backFaceCulling = content._backFaceCulling; + var vertexArray = content._drawCommand.vertexArray; + + var colorStyleFunction; + var showStyleFunction; + var pointSizeStyleFunction; + var styleTranslucent = isTranslucent; + + if (hasBatchTable) { + // Styling is handled in the batch table + hasStyle = false; + } + + if (hasStyle) { + var shaderState = { + translucent : false + }; + colorStyleFunction = style.getColorShaderFunction('getColorFromStyle', 'czm_tiles3d_style_', shaderState); + showStyleFunction = style.getShowShaderFunction('getShowFromStyle', 'czm_tiles3d_style_', shaderState); + pointSizeStyleFunction = style.getPointSizeShaderFunction('getPointSizeFromStyle', 'czm_tiles3d_style_', shaderState); + styleTranslucent = shaderState.translucent; + } + + content._styleTranslucent = styleTranslucent; + + var hasColorStyle = defined(colorStyleFunction); + var hasShowStyle = defined(showStyleFunction); + var hasPointSizeStyle = defined(pointSizeStyleFunction); + + // Get the properties in use by the style + var styleableProperties = []; + var styleableSemantics = []; + + if (hasColorStyle) { + getStyleableProperties(colorStyleFunction, styleableProperties); + getStyleableSemantics(colorStyleFunction, styleableSemantics); + colorStyleFunction = modifyStyleFunction(colorStyleFunction); + } + if (hasShowStyle) { + getStyleableProperties(showStyleFunction, styleableProperties); + getStyleableSemantics(showStyleFunction, styleableSemantics); + showStyleFunction = modifyStyleFunction(showStyleFunction); + } + if (hasPointSizeStyle) { + getStyleableProperties(pointSizeStyleFunction, styleableProperties); + getStyleableSemantics(pointSizeStyleFunction, styleableSemantics); + pointSizeStyleFunction = modifyStyleFunction(pointSizeStyleFunction); + } + + var usesColorSemantic = styleableSemantics.indexOf('COLOR') >= 0; + var usesNormalSemantic = styleableSemantics.indexOf('NORMAL') >= 0; + + //>>includeStart('debug', pragmas.debug); + if (usesNormalSemantic && !hasNormals) { + throw new DeveloperError('Style references the NORMAL semantic but the point cloud does not have normals'); + } + //>>includeEnd('debug'); + + // Disable vertex attributes that aren't used in the style, enable attributes that are + var styleableShaderAttributes = content._styleableShaderAttributes; + for (name in styleableShaderAttributes) { + if (styleableShaderAttributes.hasOwnProperty(name)) { + attribute = styleableShaderAttributes[name]; + var enabled = (styleableProperties.indexOf(name) >= 0); + var vertexAttribute = getVertexAttribute(vertexArray, attribute.location); + vertexAttribute.enabled = enabled; + } + } + + var usesColors = hasColors && (!hasColorStyle || usesColorSemantic); + if (hasColors) { + // Disable the color vertex attribute if the color style does not reference the color semantic + var colorVertexAttribute = getVertexAttribute(vertexArray, colorLocation); + colorVertexAttribute.enabled = usesColors; + } + + var attributeLocations = { + a_position : positionLocation + }; + if (usesColors) { + attributeLocations.a_color = colorLocation; + } + if (hasNormals) { + attributeLocations.a_normal = normalLocation; + } + if (hasBatchIds) { + 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'; + + var length = styleableProperties.length; + for (i = 0; i < length; ++i) { + name = styleableProperties[i]; + attribute = styleableShaderAttributes[name]; + //>>includeStart('debug', pragmas.debug); + if (!defined(attribute)) { + throw new DeveloperError('Style references a property "' + name + '" that does not exist or is not styleable.'); + } + //>>includeEnd('debug'); + + var componentCount = attribute.componentCount; + var attributeName = 'czm_tiles3d_style_' + name; + var attributeType; + if (componentCount === 1) { + attributeType = 'float'; + } else { + attributeType = 'vec' + componentCount; + } + + vs += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; + attributeLocations[attributeName] = attribute.location; + } + + if (usesColors) { + if (isTranslucent) { + vs += 'attribute vec4 a_color; \n'; + } else if (isRGB565) { + vs += 'attribute float a_color; \n' + + 'const float SHIFT_RIGHT_11 = 1.0 / 2048.0; \n' + + 'const float SHIFT_RIGHT_5 = 1.0 / 32.0; \n' + + 'const float SHIFT_LEFT_11 = 2048.0; \n' + + 'const float SHIFT_LEFT_5 = 32.0; \n' + + 'const float NORMALIZE_6 = 1.0 / 64.0; \n' + + 'const float NORMALIZE_5 = 1.0 / 32.0; \n'; + } else { + vs += 'attribute vec3 a_color; \n'; + } + } + if (hasNormals) { + if (isOctEncoded16P) { + vs += 'attribute vec2 a_normal; \n'; + } else { + vs += 'attribute vec3 a_normal; \n'; + } + } + + if (hasBatchIds) { + vs += 'attribute float a_batchId; \n'; + } + + if (isQuantized) { + vs += 'uniform vec3 u_quantizedVolumeScale; \n'; + } + + if (hasColorStyle) { + vs += colorStyleFunction; + } + + if (hasShowStyle) { + vs += showStyleFunction; + } + + if (hasPointSizeStyle) { + vs += pointSizeStyleFunction; + } + + vs += 'void main() \n' + + '{ \n'; + + if (usesColors) { + if (isTranslucent) { + vs += ' vec4 color = a_color; \n'; + } else if (isRGB565) { + vs += ' float compressed = a_color; \n' + + ' float r = floor(compressed * SHIFT_RIGHT_11); \n' + + ' compressed -= r * SHIFT_LEFT_11; \n' + + ' float g = floor(compressed * SHIFT_RIGHT_5); \n' + + ' compressed -= g * SHIFT_LEFT_5; \n' + + ' float b = compressed; \n' + + ' vec3 rgb = vec3(r * NORMALIZE_5, g * NORMALIZE_6, b * NORMALIZE_5); \n' + + ' vec4 color = vec4(rgb, 1.0); \n'; + } else { + vs += ' vec4 color = vec4(a_color, 1.0); \n'; + } + } else { + vs += ' vec4 color = u_constantColor; \n'; + } + + if (isQuantized) { + vs += ' vec3 position = a_position * u_quantizedVolumeScale; \n'; + } else { + vs += ' vec3 position = a_position; \n'; + } + + if (hasNormals) { + if (isOctEncoded16P) { + vs += ' vec3 normal = czm_octDecode(a_normal); \n'; + } else { + vs += ' vec3 normal = a_normal; \n'; + } + } else { + vs += ' vec3 normal = vec3(1.0); \n'; + } + + if (hasColorStyle) { + vs += ' color = getColorFromStyle(position, color, normal); \n'; + } + + if (hasShowStyle) { + vs += ' float show = float(getShowFromStyle(position, color, normal)); \n'; + } + + if (hasPointSizeStyle) { + vs += ' gl_PointSize = getPointSizeFromStyle(position, color, normal); \n'; + } else { + vs += ' gl_PointSize = u_pointSize; \n'; + } + + vs += ' color = color * u_highlightColor; \n'; + + if (hasNormals) { + vs += ' normal = czm_normal * normal; \n' + + ' float diffuseStrength = czm_getLambertDiffuse(czm_sunDirectionEC, normal); \n' + + ' diffuseStrength = max(diffuseStrength, 0.4); \n' + // Apply some ambient lighting + ' color *= diffuseStrength; \n'; + } + + vs += ' v_color = color; \n' + + ' gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n'; + + if (hasNormals && backFaceCulling) { + vs += ' float visible = step(-normal.z, 0.0); \n' + + ' gl_Position *= visible; \n'; + } + + if (hasShowStyle) { + vs += ' gl_Position *= show; \n'; + } + + vs += '} \n'; + + var fs = 'varying vec4 v_color; \n' + + 'void main() \n' + + '{ \n' + + ' gl_FragColor = v_color; \n' + + '} \n'; + + var drawVS = vs; + var drawFS = fs; + + if (hasBatchTable) { + drawVS = batchTable.getVertexShaderCallback()(drawVS, false); + drawFS = batchTable.getFragmentShaderCallback()(drawFS, false); + } + + var pickVS = vs; + var pickFS = fs; + + if (hasBatchTable) { + pickVS = batchTable.getPickVertexShaderCallback()(pickVS); + pickFS = batchTable.getPickFragmentShaderCallback()(pickFS); + } else { + pickFS = ShaderSource.createPickFragmentShaderSource(pickFS, 'uniform'); + } + + var drawCommand = content._drawCommand; + if (defined(drawCommand.shaderProgram)) { + // Destroy the old shader + drawCommand.shaderProgram.destroy(); + } + drawCommand.shaderProgram = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : drawVS, + fragmentShaderSource : drawFS, + attributeLocations : attributeLocations + }); + + var pickCommand = content._pickCommand; + if (defined(pickCommand.shaderProgram)) { + // Destroy the old shader + pickCommand.shaderProgram.destroy(); + } + pickCommand.shaderProgram = ShaderProgram.fromCache({ + context : context, + vertexShaderSource : pickVS, + fragmentShaderSource : pickFS, + attributeLocations : attributeLocations + }); + } + /** * Part of the {@link Cesium3DTileContent} interface. */ PointCloud3DTileContent.prototype.applyDebugSettings = function(enabled, color) { - this._highlightColor = enabled ? color : this._constantColor; + this._highlightColor = enabled ? color : Color.WHITE; + }; + + /** + * Part of the {@link Cesium3DTileContent} interface. + */ + PointCloud3DTileContent.prototype.applyStyleWithShader = function(frameState, style) { + if (!defined(this.batchTable)) { + createShaders(this, frameState, style); + return true; + } + return false; }; /** @@ -890,6 +1125,7 @@ define([ if (!defined(this._drawCommand)) { createResources(this, frameState); + createShaders(this, frameState, tileset.style); updateModelMatrix = true; // Set state to ready @@ -909,8 +1145,13 @@ define([ Matrix4.clone(this._drawCommand.modelMatrix, this._pickCommand.modelMatrix); } + if (this.backFaceCulling !== this._backFaceCulling) { + this._backFaceCulling = this.backFaceCulling; + createShaders(this, frameState, tileset.style); + } + // Update the render state - var isTranslucent = (this._highlightColor.alpha < 1.0) || this._isTranslucent; + var isTranslucent = (this._highlightColor.alpha < 1.0) || (this._constantColor.alpha < 1.0) || this._styleTranslucent; this._drawCommand.renderState = isTranslucent ? this._translucentRenderState : this._opaqueRenderState; this._drawCommand.pass = isTranslucent ? Pass.TRANSLUCENT : Pass.OPAQUE; diff --git a/Source/Scene/Tileset3DTileContent.js b/Source/Scene/Tileset3DTileContent.js index b303b8134ba7..9fd0d5c09296 100644 --- a/Source/Scene/Tileset3DTileContent.js +++ b/Source/Scene/Tileset3DTileContent.js @@ -124,6 +124,13 @@ define([ Tileset3DTileContent.prototype.applyDebugSettings = function(enabled, color) { }; + /** + * Part of the {@link Cesium3DTileContent} interface. + */ + Tileset3DTileContent.prototype.applyStyleWithShader = function(frameState, style) { + return false; + }; + /** * Part of the {@link Cesium3DTileContent} interface. */ diff --git a/Source/Shaders/Builtin/Functions/HSBToRGB.glsl b/Source/Shaders/Builtin/Functions/HSBToRGB.glsl new file mode 100644 index 000000000000..63036c4f1478 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/HSBToRGB.glsl @@ -0,0 +1,24 @@ +/** + * Converts an HSB color (hue, saturation, brightness) to RGB + * HSB <-> RGB conversion with minimal branching: {@link http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl} + * + * @name czm_HSBToRGB + * @glslFunction + * + * @param {vec3} hsb The color in HSB. + * + * @returns {vec3} The color in RGB. + * + * @example + * vec3 hsb = czm_RGBToHSB(rgb); + * hsb.z *= 0.1; + * rgb = czm_HSBToRGB(hsb); + */ + +const vec4 K_HSB2RGB = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + +vec3 czm_HSBToRGB(vec3 hsb) +{ + vec3 p = abs(fract(hsb.xxx + K_HSB2RGB.xyz) * 6.0 - K_HSB2RGB.www); + return hsb.z * mix(K_HSB2RGB.xxx, clamp(p - K_HSB2RGB.xxx, 0.0, 1.0), hsb.y); +} diff --git a/Source/Shaders/Builtin/Functions/HSLToRGB.glsl b/Source/Shaders/Builtin/Functions/HSLToRGB.glsl new file mode 100644 index 000000000000..59b06220cd2f --- /dev/null +++ b/Source/Shaders/Builtin/Functions/HSLToRGB.glsl @@ -0,0 +1,31 @@ +/** + * Converts an HSL color (hue, saturation, lightness) to RGB + * HSL <-> RGB conversion: {@link http://www.chilliant.com/rgb2hsv.html} + * + * @name czm_HSLToRGB + * @glslFunction + * + * @param {vec3} rgb The color in HSL. + * + * @returns {vec3} The color in RGB. + * + * @example + * vec3 hsl = czm_RGBToHSL(rgb); + * hsl.z *= 0.1; + * rgb = czm_HSLToRGB(hsl); + */ + +vec3 hueToRGB(float hue) +{ + float r = abs(hue * 6.0 - 3.0) - 1.0; + float g = 2.0 - abs(hue * 6.0 - 2.0); + float b = 2.0 - abs(hue * 6.0 - 4.0); + return clamp(vec3(r, g, b), 0.0, 1.0); +} + +vec3 czm_HSLToRGB(vec3 hsl) +{ + vec3 rgb = hueToRGB(hsl.x); + float c = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y; + return (rgb - 0.5) * c + hsl.z; +} diff --git a/Source/Shaders/Builtin/Functions/RGBToHSB.glsl b/Source/Shaders/Builtin/Functions/RGBToHSB.glsl new file mode 100644 index 000000000000..9826d72723e2 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/RGBToHSB.glsl @@ -0,0 +1,27 @@ +/** + * Converts an RGB color to HSB (hue, saturation, brightness) + * HSB <-> RGB conversion with minimal branching: {@link http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl} + * + * @name czm_RGBToHSB + * @glslFunction + * + * @param {vec3} rgb The color in RGB. + * + * @returns {vec3} The color in HSB. + * + * @example + * vec3 hsb = czm_RGBToHSB(rgb); + * hsb.z *= 0.1; + * rgb = czm_HSBToRGB(hsb); + */ + +const vec4 K_RGB2HSB = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + +vec3 czm_RGBToHSB(vec3 rgb) +{ + vec4 p = mix(vec4(rgb.bg, K_RGB2HSB.wz), vec4(rgb.gb, K_RGB2HSB.xy), step(rgb.b, rgb.g)); + vec4 q = mix(vec4(p.xyw, rgb.r), vec4(rgb.r, p.yzx), step(p.x, rgb.r)); + + float d = q.x - min(q.w, q.y); + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + czm_epsilon7)), d / (q.x + czm_epsilon7), q.x); +} diff --git a/Source/Shaders/Builtin/Functions/RGBToHSL.glsl b/Source/Shaders/Builtin/Functions/RGBToHSL.glsl new file mode 100644 index 000000000000..190f1d63c7bf --- /dev/null +++ b/Source/Shaders/Builtin/Functions/RGBToHSL.glsl @@ -0,0 +1,34 @@ +/** + * Converts an RGB color to HSL (hue, saturation, lightness) + * HSL <-> RGB conversion: {@link http://www.chilliant.com/rgb2hsv.html} + * + * @name czm_RGBToHSL + * @glslFunction + * + * @param {vec3} rgb The color in RGB. + * + * @returns {vec3} The color in HSL. + * + * @example + * vec3 hsl = czm_RGBToHSL(rgb); + * hsl.z *= 0.1; + * rgb = czm_HSLToRGB(hsl); + */ + +vec3 RGBtoHCV(vec3 rgb) +{ + // Based on work by Sam Hocevar and Emil Persson + vec4 p = (rgb.g < rgb.b) ? vec4(rgb.bg, -1.0, 2.0 / 3.0) : vec4(rgb.gb, 0.0, -1.0 / 3.0); + vec4 q = (rgb.r < p.x) ? vec4(p.xyw, rgb.r) : vec4(rgb.r, p.yzx); + float c = q.x - min(q.w, q.y); + float h = abs((q.w - q.y) / (6.0 * c + czm_epsilon7) + q.z); + return vec3(h, c, q.x); +} + +vec3 czm_RGBToHSL(vec3 rgb) +{ + vec3 hcv = RGBtoHCV(rgb); + float l = hcv.z - hcv.y * 0.5; + float s = hcv.y / (1.0 - abs(l * 2.0 - 1.0) + czm_epsilon7); + return vec3(hcv.x, s, l); +} diff --git a/Source/Shaders/SkyAtmosphereFS.glsl b/Source/Shaders/SkyAtmosphereFS.glsl index 9fd4000bb26e..1681501b020f 100644 --- a/Source/Shaders/SkyAtmosphereFS.glsl +++ b/Source/Shaders/SkyAtmosphereFS.glsl @@ -32,7 +32,6 @@ // Code: http://sponeil.net/ // GPU Gems 2 Article: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html - // HSV/HSB <-> RGB conversion with minimal branching: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl #ifdef COLOR_CORRECT uniform vec3 u_hsbShift; // Hue, saturation, brightness @@ -40,31 +39,12 @@ uniform vec3 u_hsbShift; // Hue, saturation, brightness const float g = -0.95; const float g2 = g * g; -const vec4 K_RGB2HSB = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); -const vec4 K_HSB2RGB = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); varying vec3 v_rayleighColor; varying vec3 v_mieColor; varying vec3 v_toCamera; varying vec3 v_positionEC; -#ifdef COLOR_CORRECT -vec3 rgb2hsb(vec3 rgbColor) -{ - vec4 p = mix(vec4(rgbColor.bg, K_RGB2HSB.wz), vec4(rgbColor.gb, K_RGB2HSB.xy), step(rgbColor.b, rgbColor.g)); - vec4 q = mix(vec4(p.xyw, rgbColor.r), vec4(rgbColor.r, p.yzx), step(p.x, rgbColor.r)); - - float d = q.x - min(q.w, q.y); - return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + czm_epsilon7)), d / (q.x + czm_epsilon7), q.x); -} - -vec3 hsb2rgb(vec3 hsbColor) -{ - vec3 p = abs(fract(hsbColor.xxx + K_HSB2RGB.xyz) * 6.0 - K_HSB2RGB.www); - return hsbColor.z * mix(K_HSB2RGB.xxx, clamp(p - K_HSB2RGB.xxx, 0.0, 1.0), hsbColor.y); -} -#endif - void main (void) { // Extra normalize added for Android @@ -81,13 +61,13 @@ void main (void) #ifdef COLOR_CORRECT // Convert rgb color to hsb - vec3 hsb = rgb2hsb(rgb); + vec3 hsb = czm_RGBToHSB(rgb); // Perform hsb shift hsb.x += u_hsbShift.x; // hue hsb.y = clamp(hsb.y + u_hsbShift.y, 0.0, 1.0); // saturation hsb.z = hsb.z > czm_epsilon7 ? hsb.z + u_hsbShift.z : 0.0; // brightness // Convert shifted hsb back to rgb - rgb = hsb2rgb(hsb); + rgb = czm_HSBToRGB(hsb); // Check if correction decreased the luminance to 0 l = min(l, czm_luminance(rgb)); diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/pointCloudWithPerPointProperties.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/pointCloudWithPerPointProperties.pnts index 39d2bb713587..d2c056ec9f23 100644 Binary files a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/pointCloudWithPerPointProperties.pnts and b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/pointCloudWithPerPointProperties.pnts differ diff --git a/Specs/Data/Cesium3DTiles/Style/style.json b/Specs/Data/Cesium3DTiles/Style/style.json index a3fd709355a1..6bd92818892d 100644 --- a/Specs/Data/Cesium3DTiles/Style/style.json +++ b/Specs/Data/Cesium3DTiles/Style/style.json @@ -1,4 +1,5 @@ { "color" : "color('red')", - "show" : "${id} < 100" + "show" : "${id} < 100", + "pointSize" : "${id} / 100" } \ No newline at end of file diff --git a/Specs/Data/Cesium3DTiles/Tilesets/TilesetWithViewerRequestVolume/tileset.json b/Specs/Data/Cesium3DTiles/Tilesets/TilesetWithViewerRequestVolume/tileset.json index 330db20ac20f..067e2d15d12e 100644 --- a/Specs/Data/Cesium3DTiles/Tilesets/TilesetWithViewerRequestVolume/tileset.json +++ b/Specs/Data/Cesium3DTiles/Tilesets/TilesetWithViewerRequestVolume/tileset.json @@ -2,24 +2,6 @@ "asset": { "version": "0.0" }, - "properties": { - "id": { - "minimum": 0, - "maximum": 9 - }, - "Longitude": { - "minimum": -1.3197190069941716, - "maximum": -1.3196399825465384 - }, - "Latitude": { - "minimum": 0.6988468038519597, - "maximum": 0.6989046685398855 - }, - "Height": { - "minimum": 6, - "maximum": 84 - } - }, "geometricError": 240, "root": { "boundingVolume": { diff --git a/Specs/Scene/Cesium3DTileStyleSpec.js b/Specs/Scene/Cesium3DTileStyleSpec.js index 3509c71f332c..6e516df9457a 100644 --- a/Specs/Scene/Cesium3DTileStyleSpec.js +++ b/Specs/Scene/Cesium3DTileStyleSpec.js @@ -69,10 +69,12 @@ defineSuite([ return tileStyle.readyPromise.then(function(style) { expect(style.style).toEqual({ show : '${id} < 100', - color : "color('red')" + color : "color('red')", + pointSize : '${id} / 100' }); expect(style.show).toEqual(new Expression('${id} < 100')); expect(style.color).toEqual(new Expression("color('red')")); + expect(style.pointSize).toEqual(new Expression('${id} / 100')); expect(tileStyle.ready).toEqual(true); }).otherwise(function() { fail('should load style.json'); @@ -95,6 +97,14 @@ defineSuite([ expect(style.color).toEqual(new Expression('color("#ffffff")')); }); + it ('sets pointSize value to default expression', function() { + var style = new Cesium3DTileStyle({}); + expect(style.pointSize).toEqual(new Expression('1')); + + style = new Cesium3DTileStyle(); + expect(style.pointSize).toEqual(new Expression('1')); + }); + it ('sets show value to expression', function() { var style = new Cesium3DTileStyle({ show : 'true' @@ -122,7 +132,21 @@ defineSuite([ expect(style.show).toEqual(new Expression('false')); }); - it ('sets show to undefined if not a string or a boolean', function() { + it ('sets show value to conditional', function() { + var jsonExp = { + conditions : [ + ['${height} > 2', 'false'], + ['true', 'true'] + ] + }; + + var style = new Cesium3DTileStyle({ + show : jsonExp + }); + expect(style.show).toEqual(new ConditionsExpression(jsonExp)); + }); + + it ('sets show to undefined if not a string, boolean, or conditional', function() { var style = new Cesium3DTileStyle({ show : 1 }); @@ -148,10 +172,10 @@ defineSuite([ it ('sets color value to conditional', function() { var jsonExp = { - conditions : { - '${height} > 2' : 'color("cyan")', - 'true' : 'color("blue")' - } + conditions : [ + ['${height} > 2', 'color("cyan")'], + ['true', 'color("blue")'] + ] }; var style = new Cesium3DTileStyle({ @@ -167,6 +191,44 @@ defineSuite([ expect(style.color).toEqual(undefined); }); + it ('sets pointSize value to expression', function() { + var style = new Cesium3DTileStyle({ + pointSize : '2' + }); + expect(style.pointSize).toEqual(new Expression('2')); + + style = new Cesium3DTileStyle({ + pointSize : '${height} / 10' + }); + expect(style.pointSize).toEqual(new Expression('${height} / 10')); + + style = new Cesium3DTileStyle({ + pointSize : 2 + }); + expect(style.pointSize).toEqual(new Expression('2')); + }); + + it ('sets pointSize value to conditional', function() { + var jsonExp = { + conditions : [ + ['${height} > 2', '1.0'], + ['true', '2.0'] + ] + }; + + var style = new Cesium3DTileStyle({ + pointSize : jsonExp + }); + expect(style.pointSize).toEqual(new ConditionsExpression(jsonExp)); + }); + + it ('sets pointSize to undefined if not a number, string, or conditional', function() { + var style = new Cesium3DTileStyle({ + pointSize : true + }); + expect(style.pointSize).toEqual(undefined); + }); + it ('throws on accessing style if not ready', function() { var style = new Cesium3DTileStyle({}); style._ready = false; @@ -194,6 +256,15 @@ defineSuite([ }).toThrowDeveloperError(); }); + it ('throws on accessing pointSize if not ready', function() { + var style = new Cesium3DTileStyle({}); + style._ready = false; + + expect(function() { + return style.pointSize; + }).toThrowDeveloperError(); + }); + it ('sets meta properties', function() { var style = new Cesium3DTileStyle({ meta : { @@ -246,16 +317,18 @@ defineSuite([ it ('applies default style', function() { var style = new Cesium3DTileStyle({ "show" : "true", - "color" : "color('#ffffff')" + "color" : "color('#ffffff')", + "pointSize" : "1.0" }); expect(style.show.evaluate(undefined)).toEqual(true); expect(style.color.evaluate(undefined)).toEqual(Color.WHITE); + expect(style.pointSize.evaluate(undefined)).toEqual(1.0); }); it ('applies show style with variable', function() { var style = new Cesium3DTileStyle({ - "show" : "${ZipCode} === '19341'" + "show" : "${ZipCode} == '19341'" }); expect(style.show.evaluate(feature1)).toEqual(true); @@ -273,6 +346,41 @@ defineSuite([ expect(style.color.evaluate(undefined)).toEqual(Color.WHITE); }); + it ('applies show style with complex conditional', function() { + var style = new Cesium3DTileStyle({ + "show" : { + "expression" : "${Height}", + "conditions" : [ + ["(${expression} >= 1.0) && (${expression} < 10.0)", "true"], + ["(${expression} >= 10.0) && (${expression} < 30.0)", "false"], + ["(${expression} >= 30.0) && (${expression} < 50.0)", "true"], + ["(${expression} >= 50.0) && (${expression} < 70.0)", "false"], + ["(${expression} >= 70.0) && (${expression} < 100.0)", "true"], + ["(${expression} >= 100.0)", "false"] + ] + } + }); + expect(style.show.evaluate(feature1)).toEqual(false); + expect(style.show.evaluate(feature2)).toEqual(true); + }); + + it ('applies show style with conditional', function() { + var style = new Cesium3DTileStyle({ + "show" : { + "conditions" : [ + ["(${Height} >= 100.0)", "false"], + ["(${Height} >= 70.0)", "true"], + ["(${Height} >= 50.0)", "false"], + ["(${Height} >= 30.0)", "true"], + ["(${Height} >= 10.0)", "false"], + ["(${Height} >= 1.0)", "true"] + ] + } + }); + expect(style.show.evaluate(feature1)).toEqual(false); + expect(style.show.evaluate(feature2)).toEqual(true); + }); + it ('applies color style variables', function() { var style = new Cesium3DTileStyle({ "color" : "(${Temperature} > 90) ? color('red') : color('white')" @@ -295,11 +403,11 @@ defineSuite([ var style = new Cesium3DTileStyle({ "color" : { "expression" : "regExp('^1(\\d)').exec(${id})", - "conditions" : { - "${expression} === '1'" : "color('#FF0000')", - "${expression} === '2'" : "color('#00FF00')", - "true" : "color('#FFFFFF')" - } + "conditions" : [ + ["${expression} == '1'", "color('#FF0000')"], + ["${expression} == '2'", "color('#00FF00')"], + ["true", "color('#FFFFFF')"] + ] } }); expect(style.show.evaluate(feature1)).toEqual(true); @@ -311,14 +419,14 @@ defineSuite([ var style = new Cesium3DTileStyle({ "color" : { "expression" : "${Height}", - "conditions" : { - "(${expression} >= 1.0) && (${expression} < 10.0)" : "color('#FF00FF')", - "(${expression} >= 10.0) && (${expression} < 30.0)" : "color('#FF0000')", - "(${expression} >= 30.0) && (${expression} < 50.0)" : "color('#FFFF00')", - "(${expression} >= 50.0) && (${expression} < 70.0)" : "color('#00FF00')", - "(${expression} >= 70.0) && (${expression} < 100.0)" : "color('#00FFFF')", - "(${expression} >= 100.0)" : "color('#0000FF')" - } + "conditions" : [ + ["(${expression} >= 1.0) && (${expression} < 10.0)", "color('#FF00FF')"], + ["(${expression} >= 10.0) && (${expression} < 30.0)", "color('#FF0000')"], + ["(${expression} >= 30.0) && (${expression} < 50.0)", "color('#FFFF00')"], + ["(${expression} >= 50.0) && (${expression} < 70.0)", "color('#00FF00')"], + ["(${expression} >= 70.0) && (${expression} < 100.0)", "color('#00FFFF')"], + ["(${expression} >= 100.0)", "color('#0000FF')"] + ] } }); expect(style.show.evaluate(feature1)).toEqual(true); @@ -329,18 +437,83 @@ defineSuite([ it ('applies color style with conditional', function() { var style = new Cesium3DTileStyle({ "color" : { - "conditions" : { - "(${Height} >= 100.0)" : "color('#0000FF')", - "(${Height} >= 70.0)" : "color('#00FFFF')", - "(${Height} >= 50.0)" : "color('#00FF00')", - "(${Height} >= 30.0)" : "color('#FFFF00')", - "(${Height} >= 10.0)" : "color('#FF0000')", - "(${Height} >= 1.0)" : "color('#FF00FF')" - } + "conditions" : [ + ["(${Height} >= 100.0)", "color('#0000FF')"], + ["(${Height} >= 70.0)", "color('#00FFFF')"], + ["(${Height} >= 50.0)", "color('#00FF00')"], + ["(${Height} >= 30.0)", "color('#FFFF00')"], + ["(${Height} >= 10.0)", "color('#FF0000')"], + ["(${Height} >= 1.0)", "color('#FF00FF')"] + ] } }); expect(style.show.evaluate(feature1)).toEqual(true); expect(style.color.evaluate(feature1)).toEqual(Color.BLUE); expect(style.color.evaluate(feature2)).toEqual(Color.YELLOW); }); + + it ('applies pointSize style with variable', function() { + var style = new Cesium3DTileStyle({ + "pointSize" : "${Temperature} / 10.0" + }); + + expect(style.pointSize.evaluate(feature1)).toEqual(7.8); + expect(style.pointSize.evaluate(feature2)).toEqual(9.2); + }); + + it ('applies pointSize style with regexp and variables', function() { + var style = new Cesium3DTileStyle({ + "pointSize" : "(regExp('^Chest').test(${County})) ? 2.0 : 1.0" + }); + + expect(style.pointSize.evaluate(feature1)).toEqual(2.0); + expect(style.pointSize.evaluate(feature2)).toEqual(1.0); + }); + + it ('applies pointSize style with complex conditional', function() { + var style = new Cesium3DTileStyle({ + "pointSize" : { + "expression" : "${Height}", + "conditions" : [ + ["(${expression} >= 1.0) && (${expression} < 10.0)", "1"], + ["(${expression} >= 10.0) && (${expression} < 30.0)", "2"], + ["(${expression} >= 30.0) && (${expression} < 50.0)", "3"], + ["(${expression} >= 50.0) && (${expression} < 70.0)", "4"], + ["(${expression} >= 70.0) && (${expression} < 100.0)", "5"], + ["(${expression} >= 100.0)", "6"] + ] + } + }); + expect(style.pointSize.evaluate(feature1)).toEqual(6); + expect(style.pointSize.evaluate(feature2)).toEqual(3); + }); + + it ('applies pointSize style with conditional', function() { + var style = new Cesium3DTileStyle({ + "pointSize" : { + "conditions" : [ + ["(${Height} >= 100.0)", "6"], + ["(${Height} >= 70.0)", "5"], + ["(${Height} >= 50.0)", "4"], + ["(${Height} >= 30.0)", "3"], + ["(${Height} >= 10.0)", "2"], + ["(${Height} >= 1.0)", "1"] + ] + } + }); + expect(style.pointSize.evaluate(feature1)).toEqual(6); + expect(style.pointSize.evaluate(feature2)).toEqual(3); + }); + + it('return undefined shader functions when the style is empty', function() { + // The default color style is white, the default show style is true, and the default pointSize is 1.0, + // but the generated generated shader functions should just be undefined. We don't want all the points to be white. + var style = new Cesium3DTileStyle({}); + var colorFunction = style.getColorShaderFunction('getColor', '', {}); + var showFunction = style.getShowShaderFunction('getShow', '', {}); + var pointSizeFunction = style.getPointSizeShaderFunction('getPointSize', '', {}); + expect(colorFunction).toBeUndefined(); + expect(showFunction).toBeUndefined(); + expect(pointSizeFunction).toBeUndefined(); + }); }); diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 18eeaf0b1923..745d7ce61463 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -1247,10 +1247,10 @@ defineSuite([ // ${id} < 10 will always evaluate to true tileset.style = new Cesium3DTileStyle({ color : { - conditions : { - '${id} < 10' : 'color("red")', - 'true' : 'color("blue")' - } + conditions : [ + ['${id} < 10', 'color("red")'], + ['true', 'color("blue")'] + ] } }); var color = scene.renderForSpecs(); @@ -1262,10 +1262,10 @@ defineSuite([ // ${id}>= 10 will always evaluate to false tileset.style = new Cesium3DTileStyle({ color : { - conditions : { - '${id} >= 10' : 'color("red")', - 'true' : 'color("blue")' - } + conditions : [ + ['${id} >= 10', 'color("red")'], + ['true', 'color("blue")'] + ] } }); color = scene.renderForSpecs(); diff --git a/Specs/Scene/ConditionsExpressionSpec.js b/Specs/Scene/ConditionsExpressionSpec.js index aa18b8ecb1d7..50c1e897e569 100644 --- a/Specs/Scene/ConditionsExpressionSpec.js +++ b/Specs/Scene/ConditionsExpressionSpec.js @@ -18,37 +18,37 @@ defineSuite([ }; var jsonExp = { - conditions : { - '${Height} > 100' : 'color("blue")', - '${Height} > 50' : 'color("red")', - 'true' : 'color("green")' - } + conditions : [ + ['${Height} > 100', 'color("blue")'], + ['${Height} > 50', 'color("red")'], + ['true', 'color("lime")'] + ] }; var jsonExpWithExpression = { expression : '${Height}/2', - conditions : { - '${expression} > 50' : 'color("blue")', - '${expression} > 25' : 'color("red")', - 'true' : 'color("green")' - } + conditions : [ + ['${expression} > 50', 'color("blue")'], + ['${expression} > 25', 'color("red")'], + ['true', 'color("lime")'] + ] }; var jsonExpWithMultipleExpression = { expression : '${Height}/2', - conditions : { - '${expression} > 50 && ${expression} < 100' : 'color("blue")', - '${expression} > 25 && ${expression} < 26' : 'color("red")', - 'true' : 'color("green")' - } + conditions : [ + ['${expression} > 50 && ${expression} < 100', 'color("blue")'], + ['${expression} > 25 && ${expression} < 26', 'color("red")'], + ['true', 'color("lime")'] + ] }; var jsonExpWithUndefinedExpression = { - conditions : { - '${expression} === undefined' : 'color("blue")', - 'true' : 'color("green")' - } + conditions : [ + ['${expression} == undefined', 'color("blue")'], + ['true', 'color("lime")'] + ] }; @@ -60,60 +60,60 @@ defineSuite([ it('constructs with expression', function() { var expression = new ConditionsExpression(jsonExpWithExpression); expect(expression._expression).toEqual('${Height}/2'); - expect(expression._conditions).toEqual({ - '${expression} > 50' : 'color("blue")', - '${expression} > 25' : 'color("red")', - 'true' : 'color("green")' - }); + expect(expression._conditions).toEqual([ + ['${expression} > 50', 'color("blue")'], + ['${expression} > 25', 'color("red")'], + ['true', 'color("lime")'] + ]); }); it('evaluates undefined expression', function() { var expression = new ConditionsExpression(jsonExpWithExpression); expect(expression._expression).toEqual('${Height}/2'); - expect(expression._conditions).toEqual({ - '${expression} > 50' : 'color("blue")', - '${expression} > 25' : 'color("red")', - 'true' : 'color("green")' - }); + expect(expression._conditions).toEqual([ + ['${expression} > 50', 'color("blue")'], + ['${expression} > 25', 'color("red")'], + ['true', 'color("lime")'] + ]); }); it('evaluates conditional', function() { var expression = new ConditionsExpression(jsonExp); - expect(expression.evaluate(new MockFeature('101'))).toEqual(Color.BLUE); - expect(expression.evaluate(new MockFeature('52'))).toEqual(Color.RED); - expect(expression.evaluate(new MockFeature('3'))).toEqual(Color.GREEN); + expect(expression.evaluate(new MockFeature(101))).toEqual(Color.BLUE); + expect(expression.evaluate(new MockFeature(52))).toEqual(Color.RED); + expect(expression.evaluate(new MockFeature(3))).toEqual(Color.LIME); }); it('evaluates conditional with multiple expressions', function() { var expression = new ConditionsExpression(jsonExpWithMultipleExpression); - expect(expression.evaluate(new MockFeature('101'))).toEqual(Color.BLUE); - expect(expression.evaluate(new MockFeature('52'))).toEqual(Color.GREEN); - expect(expression.evaluate(new MockFeature('3'))).toEqual(Color.GREEN); + expect(expression.evaluate(new MockFeature(101))).toEqual(Color.BLUE); + expect(expression.evaluate(new MockFeature(52))).toEqual(Color.LIME); + expect(expression.evaluate(new MockFeature(3))).toEqual(Color.LIME); }); it('constructs and evaluates empty conditional', function() { var expression = new ConditionsExpression({ - "conditions" : {} + "conditions" : [] }); - expect(expression._conditions).toEqual({}); - expect(expression.evaluate(new MockFeature('101'))).toEqual(undefined); - expect(expression.evaluate(new MockFeature('52'))).toEqual(undefined); - expect(expression.evaluate(new MockFeature('3'))).toEqual(undefined); + expect(expression._conditions).toEqual([]); + expect(expression.evaluate(new MockFeature(101))).toEqual(undefined); + expect(expression.evaluate(new MockFeature(52))).toEqual(undefined); + expect(expression.evaluate(new MockFeature(3))).toEqual(undefined); }); it('constructs and evaluates empty', function() { - var expression = new ConditionsExpression({}); + var expression = new ConditionsExpression([]); expect(expression._conditions).toEqual(undefined); - expect(expression.evaluate(new MockFeature('101'))).toEqual(undefined); - expect(expression.evaluate(new MockFeature('52'))).toEqual(undefined); - expect(expression.evaluate(new MockFeature('3'))).toEqual(undefined); + expect(expression.evaluate(new MockFeature(101))).toEqual(undefined); + expect(expression.evaluate(new MockFeature(52))).toEqual(undefined); + expect(expression.evaluate(new MockFeature(3))).toEqual(undefined); }); it('evaluates conditional with expression', function() { var expression = new ConditionsExpression(jsonExpWithExpression); - expect(expression.evaluate(new MockFeature('101'))).toEqual(Color.BLUE); - expect(expression.evaluate(new MockFeature('52'))).toEqual(Color.RED); - expect(expression.evaluate(new MockFeature('3'))).toEqual(Color.GREEN); + expect(expression.evaluate(new MockFeature(101))).toEqual(Color.BLUE); + expect(expression.evaluate(new MockFeature(52))).toEqual(Color.RED); + expect(expression.evaluate(new MockFeature(3))).toEqual(Color.LIME); }); it('evaluates undefined conditional expression', function() { @@ -121,4 +121,32 @@ defineSuite([ expect(expression._expression).toEqual(undefined); expect(expression.evaluate(undefined)).toEqual(Color.BLUE); }); + + it('gets shader function', function() { + var expression = new ConditionsExpression(jsonExpWithExpression); + var shaderFunction = expression.getShaderFunction('getColor', '', {}, 'vec4'); + var expected = 'vec4 getColor() \n' + + '{ \n' + + ' if (((Height / 2.0) > 50.0)) \n' + + ' { \n' + + ' return vec4(vec3(0.0, 0.0, 1.0), 1.0); \n' + + ' } \n' + + ' else if (((Height / 2.0) > 25.0)) \n' + + ' { \n' + + ' return vec4(vec3(1.0, 0.0, 0.0), 1.0); \n' + + ' } \n' + + ' else if (true) \n' + + ' { \n' + + ' return vec4(vec3(0.0, 1.0, 0.0), 1.0); \n' + + ' } \n' + + ' return vec4(1.0); \n' + + '} \n'; + expect(shaderFunction).toEqual(expected); + }); + + it('return undefined shader function when there are no conditions', function() { + var expression = new ConditionsExpression([]); + var shaderFunction = expression.getShaderFunction('getColor', '', {}, 'vec4'); + expect(shaderFunction).toBeUndefined(); + }); }); diff --git a/Specs/Scene/ExpressionSpec.js b/Specs/Scene/ExpressionSpec.js index 15159c631546..99032f5f915a 100644 --- a/Specs/Scene/ExpressionSpec.js +++ b/Specs/Scene/ExpressionSpec.js @@ -160,14 +160,6 @@ defineSuite([ return new Expression('2 & 3'); }).toThrowDeveloperError(); - expect(function() { - return new Expression('2 == 3'); - }).toThrowDeveloperError(); - - expect(function() { - return new Expression('2 != 3'); - }).toThrowDeveloperError(); - expect(function() { return new Expression('2 << 3'); }).toThrowDeveloperError(); @@ -543,7 +535,7 @@ defineSuite([ expect(expression.evaluate(undefined)).toEqual(2); }); - it('evaluates binary equals', function() { + it('evaluates binary equals strict', function() { var expression = new Expression('\'hello\' === \'hello\''); expect(expression.evaluate(undefined)).toEqual(true); @@ -552,9 +544,26 @@ defineSuite([ expression = new Expression('false === true === false'); expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('1 === "1"'); + expect(expression.evaluate(undefined)).toEqual(false); }); - it('evaluates binary not equals', function() { + it('evaluates binary equals', function() { + var expression = new Expression('\'hello\' == \'hello\''); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('1 == 2'); + expect(expression.evaluate(undefined)).toEqual(false); + + expression = new Expression('false == true == false'); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('1 == "1"'); + expect(expression.evaluate(undefined)).toEqual(true); + }); + + it('evaluates binary not equals strict', function() { var expression = new Expression('\'hello\' !== \'hello\''); expect(expression.evaluate(undefined)).toEqual(false); @@ -563,6 +572,23 @@ defineSuite([ expression = new Expression('false !== true !== false'); expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('1 !== "1"'); + expect(expression.evaluate(undefined)).toEqual(true); + }); + + it('evaluates binary not equals', function() { + var expression = new Expression('\'hello\' != \'hello\''); + expect(expression.evaluate(undefined)).toEqual(false); + + expression = new Expression('1 != 2'); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('false != true != false'); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('1 != "1"'); + expect(expression.evaluate(undefined)).toEqual(false); }); it('evaluates binary less than', function() { @@ -709,16 +735,16 @@ defineSuite([ expression = new Expression('rgba(255, 255, 255, 1.0) % rgba(255, 255, 255, 1.0)'); expect(expression.evaluate(undefined)).toEqual(new Color(0, 0, 0, 0)); - expression = new Expression('color(\'green\') === color(\'green\')'); + expression = new Expression('color(\'green\') == color(\'green\')'); expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('color() === color()'); + expression = new Expression('color() == color()'); expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('!!color() === true'); + expression = new Expression('!!color() == true'); expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('color(\'green\') !== color(\'green\')'); + expression = new Expression('color(\'green\') != color(\'green\')'); expect(expression.evaluate(undefined)).toEqual(false); }); @@ -951,7 +977,7 @@ defineSuite([ color : Color.BLUE }); - expression = new Expression('${feature} === ${feature.feature}'); + expression = new Expression('${feature} == ${feature.feature}'); expect(expression.evaluate(feature)).toEqual(true); }); @@ -1204,4 +1230,427 @@ defineSuite([ expression = new Expression('${temperatures["values"][0]}'); expect(expression.evaluate(feature)).toEqual(70); }); + + it('gets shader function', function() { + var expression = new Expression('true'); + var shaderFunction = expression.getShaderFunction('getShow', '', {}, 'bool'); + var expected = 'bool getShow() \n' + + '{ \n' + + ' return true; \n' + + '} \n'; + expect(shaderFunction).toEqual(expected); + }); + + it('gets shader expression for variable', function() { + var expression = new Expression('${property}'); + var shaderExpression = expression.getShaderExpression('prefix_', {}); + var expected = 'prefix_property'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for unary not', function() { + var expression = new Expression('!true'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '!true'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for unary negative', function() { + var expression = new Expression('-5.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '-5.0'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for unary positive', function() { + var expression = new Expression('+5.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '+5.0'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for converting to literal boolean', function() { + var expression = new Expression('Boolean(1.0)'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = 'bool(1.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for converting to literal number', function() { + var expression = new Expression('Number(true)'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = 'float(true)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary addition', function() { + var expression = new Expression('1.0 + 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 + 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary subtraction', function() { + var expression = new Expression('1.0 - 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 - 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary multiplication', function() { + var expression = new Expression('1.0 * 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 * 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary division', function() { + var expression = new Expression('1.0 / 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 / 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary modulus', function() { + var expression = new Expression('1.0 % 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = 'mod(1.0, 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary equals strict', function() { + var expression = new Expression('1.0 === 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 == 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary equals', function() { + var expression = new Expression('1.0 == 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 == 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary not equals strict', function() { + var expression = new Expression('1.0 !== 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 != 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary not equals', function() { + var expression = new Expression('1.0 != 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 != 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary less than', function() { + var expression = new Expression('1.0 < 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 < 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary less than or equals', function() { + var expression = new Expression('1.0 <= 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 <= 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary greater than', function() { + var expression = new Expression('1.0 > 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 > 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for binary greater than or equals', function() { + var expression = new Expression('1.0 >= 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(1.0 >= 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for logical and', function() { + var expression = new Expression('true && false'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(true && false)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for logical or', function() { + var expression = new Expression('true || false'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(true || false)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for ternary conditional', function() { + var expression = new Expression('true ? 1.0 : 2.0'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(true ? 1.0 : 2.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for array indexing', function() { + var expression = new Expression('${property[0]}'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = 'property[int(0.0)]'; + expect(shaderExpression).toEqual(expected); + + expression = new Expression('rgb(0,0,0)[1]'); + shaderExpression = expression.getShaderExpression('', {}); + expected = 'vec4(0.0, 0.0, 0.0, 1.0)[int(1.0)]'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for array', function() { + var expression = new Expression('[1.0, 2.0]'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = 'vec2(1.0, 2.0)'; + expect(shaderExpression).toEqual(expected); + + expression = new Expression('[1.0, 2.0, 3.0]'); + shaderExpression = expression.getShaderExpression('', {}); + expected = 'vec3(1.0, 2.0, 3.0)'; + expect(shaderExpression).toEqual(expected); + + expression = new Expression('[1.0, 2.0, 3.0, 4.0]'); + shaderExpression = expression.getShaderExpression('', {}); + expected = 'vec4(1.0, 2.0, 3.0, 4.0)'; + expect(shaderExpression).toEqual(expected); + }); + + it('throws when getting shader expression for array of invalid length', function() { + var expression = new Expression('[]'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + + expression = new Expression('[1.0]'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + + expression = new Expression('[1.0, 2.0, 3.0, 4.0, 5.0]'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('gets shader expression for boolean', function() { + var expression = new Expression('true || false'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '(true || false)'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for integer', function() { + var expression = new Expression('1'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '1.0'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for float', function() { + var expression = new Expression('1.02'); + var shaderExpression = expression.getShaderExpression('', {}); + var expected = '1.02'; + expect(shaderExpression).toEqual(expected); + }); + + it('gets shader expression for color', function() { + var shaderState = {translucent : false}; + var expression = new Expression('color()'); + var shaderExpression = expression.getShaderExpression('', shaderState); + var expected = 'vec4(1.0)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(false); + + shaderState = {translucent : false}; + expression = new Expression('color("red")'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(vec3(1.0, 0.0, 0.0), 1.0)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(false); + + shaderState = {translucent : false}; + expression = new Expression('color("#FFF")'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(vec3(1.0, 1.0, 1.0), 1.0)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(false); + + shaderState = {translucent : false}; + expression = new Expression('color("#FF0000")'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(vec3(1.0, 0.0, 0.0), 1.0)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(false); + + shaderState = {translucent : false}; + expression = new Expression('color("rgb(255, 0, 0)")'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(vec3(1.0, 0.0, 0.0), 1.0)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(false); + + shaderState = {translucent : false}; + expression = new Expression('color("red", 0.5)'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(vec3(1.0, 0.0, 0.0), 0.5)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(true); + + shaderState = {translucent : false}; + expression = new Expression('rgb(255, 0, 0)'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(1.0, 0.0, 0.0, 1.0)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(false); + + shaderState = {translucent : false}; + expression = new Expression('rgb(255, ${property}, 0)'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(255.0 / 255.0, property / 255.0, 0.0 / 255.0, 1.0)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(false); + + shaderState = {translucent : false}; + expression = new Expression('rgba(255, 0, 0, 0.5)'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(1.0, 0.0, 0.0, 0.5)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(true); + + shaderState = {translucent : false}; + expression = new Expression('rgba(255, ${property}, 0, 0.5)'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(255.0 / 255.0, property / 255.0, 0.0 / 255.0, 0.5)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(true); + + shaderState = {translucent : false}; + expression = new Expression('hsl(1.0, 0.5, 0.5)'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(0.75, 0.25, 0.25, 1.0)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(false); + + shaderState = {translucent : false}; + expression = new Expression('hsla(1.0, 0.5, 0.5, 0.5)'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(0.75, 0.25, 0.25, 0.5)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(true); + + shaderState = {translucent : false}; + expression = new Expression('hsl(1.0, ${property}, 0.5)'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(czm_HSLToRGB(vec3(1.0, property, 0.5)), 1.0)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(false); + + shaderState = {translucent : false}; + expression = new Expression('hsla(1.0, ${property}, 0.5, 0.5)'); + shaderExpression = expression.getShaderExpression('', shaderState); + expected = 'vec4(czm_HSLToRGB(vec3(1.0, property, 0.5)), 0.5)'; + expect(shaderExpression).toEqual(expected); + expect(shaderState.translucent).toBe(true); + }); + + it('throws when getting shader expression for regex', function() { + var expression = new Expression('regExp("a").test("abc")'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + + expression = new Expression('regExp("a(.)", "i").exec("Abc")'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + + expression = new Expression('regExp("a") =~ "abc"'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + + expression = new Expression('regExp("a") !~ "abc"'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for member expression with dot', function() { + var expression = new Expression('${property.name}'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for string member expression with brackets', function() { + var expression = new Expression('${property["name"]}'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for String', function() { + var expression = new Expression('String(1.0)'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for toString', function() { + var expression = new Expression('color("red").toString()'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for literal string', function() { + var expression = new Expression('"name"'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for variable in string', function() { + var expression = new Expression('"${property}"'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for literal undefined', function() { + var expression = new Expression('undefined'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for literal null', function() { + var expression = new Expression('null'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for isNaN', function() { + var expression = new Expression('isNaN(1.0)'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); + + it('throws when getting shader expression for isFinite', function() { + var expression = new Expression('isFinite(1.0)'); + expect(function() { + return expression.getShaderExpression('', {}); + }).toThrowDeveloperError(); + }); }); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index 70c00cfc486f..dec0f6ba5b87 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -4,8 +4,10 @@ defineSuite([ 'Core/Cartesian3', 'Core/Color', 'Core/ComponentDatatype', + 'Core/defined', 'Core/HeadingPitchRange', 'Core/Transforms', + 'Scene/Cesium3DTileStyle', 'Specs/Cesium3DTilesTester', 'Specs/createScene' ], function( @@ -13,8 +15,10 @@ defineSuite([ Cartesian3, Color, ComponentDatatype, + defined, HeadingPitchRange, Transforms, + Cesium3DTileStyle, Cesium3DTilesTester, createScene) { 'use strict'; @@ -331,6 +335,203 @@ defineSuite([ }); }); + it('Supports back face culling when there are per-point normals', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudBatchedUrl).then(function(tileset) { + var content = tileset._root.content; + + // Get the number of picked sections with back face culling on + content.backFaceCulling = true; + var numberPickedCulling = 0; + var picked = scene.pickForSpecs(); + while (defined(picked)) { + picked.show = false; + picked = scene.pickForSpecs(); + ++numberPickedCulling; + } + content.backFaceCulling = false; + + // Set the shows back to true + var length = content.featuresLength; + for (var i = 0; i < length; ++i) { + var feature = content.getFeature(i); + feature.show = true; + } + + // Get the number of picked sections with back face culling off + var numberPicked = 0; + picked = scene.pickForSpecs(); + while (defined(picked)) { + picked.show = false; + picked = scene.pickForSpecs(); + ++numberPicked; + } + expect(numberPicked).toBeGreaterThan(numberPickedCulling); + }); + }); + + it('applies shader style', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudWithPerPointPropertiesUrl).then(function(tileset) { + var content = tileset._root.content; + + // Solid red color + tileset.style = new Cesium3DTileStyle({ + color : 'color("red")' + }); + expect(scene.renderForSpecs()).toEqual([255, 0, 0, 255]); + expect(content._styleTranslucent).toBe(false); + + // Applies translucency + tileset.style = new Cesium3DTileStyle({ + color : 'rgba(255, 0, 0, 0.005)' + }); + var pixelColor = scene.renderForSpecs(); + expect(pixelColor[0]).toBeLessThan(255); + expect(pixelColor[1]).toBe(0); + expect(pixelColor[2]).toBe(0); + expect(pixelColor[3]).toBe(255); + expect(content._styleTranslucent).toBe(true); + + // Style with property + tileset.style = new Cesium3DTileStyle({ + color : 'color() * ${temperature}' + }); + pixelColor = scene.renderForSpecs(); // Pixel color is some shade of gray + expect(pixelColor[0]).toBe(pixelColor[1]); + expect(pixelColor[0]).toBe(pixelColor[2]); + expect(pixelColor[0]).toBeGreaterThan(0); + expect(pixelColor[0]).toBeLessThan(255); + + // When no conditions are met the default color is white + tileset.style = new Cesium3DTileStyle({ + color : { + conditions : [ + ['${secondaryColor}[0] > 1.0', 'color("red")'] // This condition will not be met + ] + } + }); + expect(scene.renderForSpecs()).toEqual([255, 255, 255, 255]); + + // Apply style with conditions + tileset.style = new Cesium3DTileStyle({ + color : { + conditions : [ + ['${temperature} < 0.1', 'color("#000099")'], + ['${temperature} < 0.2', 'color("#00cc99", 1.0)'], + ['${temperature} < 0.3', 'color("#66ff33", 0.5)'], + ['${temperature} < 0.4', 'rgba(255, 255, 0, 0.1)'], + ['${temperature} < 0.5', 'rgb(255, 128, 0)'], + ['${temperature} < 0.6', 'color("red")'], + ['${temperature} < 0.7', 'color("rgb(255, 102, 102)")'], + ['${temperature} < 0.8', 'hsl(0.875, 1.0, 0.6)'], + ['${temperature} < 0.9', 'hsla(0.83, 1.0, 0.5, 0.1)'], + ['true', 'color("#FFFFFF", 1.0)'] + ] + } + }); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + + // Apply show style + tileset.style = new Cesium3DTileStyle({ + show : true + }); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + + // Apply show style that hides all points + tileset.style = new Cesium3DTileStyle({ + show : false + }); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + + // Apply show style with property + tileset.style = new Cesium3DTileStyle({ + show : '${temperature} > 0.1' + }); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + tileset.style = new Cesium3DTileStyle({ + show : '${temperature} > 0.9' + }); + expect(scene.renderForSpecs()).toEqual([0, 0, 0, 255]); + + // Apply style with point cloud semantics + tileset.style = new Cesium3DTileStyle({ + color : '${COLOR} / 2.0', + show : '${POSITION}[0] > 0.5' + }); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + + // Apply pointSize style + tileset.style = new Cesium3DTileStyle({ + pointSize : 5.0 + }); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + }); + }); + + it('applies shader style to point cloud with normals', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudQuantizedOctEncodedUrl).then(function(tileset) { + tileset.style = new Cesium3DTileStyle({ + color : 'color("red")' + }); + var red = scene.renderForSpecs()[0]; + expect(red).toBeGreaterThan(0); + expect(red).toBeLessThan(255); + }); + }); + + it('applies shader style to point cloud with normals', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudQuantizedOctEncodedUrl).then(function(tileset) { + tileset.style = new Cesium3DTileStyle({ + color : 'color("red")' + }); + expect(scene.renderForSpecs()[0]).toBeGreaterThan(0); + }); + }); + + it('applies shader style to point cloud without colors', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + tileset.style = new Cesium3DTileStyle({ + color : 'color("red")' + }); + expect(scene.renderForSpecs()).toEqual([255, 0, 0, 255]); + }); + }); + + it('throws if style references the NORMAL semantic but the point cloud does not have per-point normals', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudRGBUrl).then(function(tileset) { + var content = tileset._root.content; + expect(function() { + content.applyStyleWithShader(scene.frameState, new Cesium3DTileStyle({ + color : '${NORMAL}[0] > 0.5' + })); + }).toThrowDeveloperError(); + }); + }); + + it('throws when shader style reference a non-existent property', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudWithPerPointPropertiesUrl).then(function(tileset) { + var content = tileset._root.content; + expect(function() { + content.applyStyleWithShader(scene.frameState, new Cesium3DTileStyle({ + color : 'color() * ${non_existent_property}' + })); + }).toThrowDeveloperError(); + }); + }); + + it('does not apply shader style if the point cloud has a batch table', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudBatchedUrl).then(function(tileset) { + var content = tileset._root.content; + var shaderProgram = content._drawCommand.shaderProgram; + tileset.style = new Cesium3DTileStyle({ + color:'color("red")' + }); + expect(content._drawCommand.shaderProgram).toBe(shaderProgram); + + // Point cloud is styled through the batch table + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + }); + }); + it('destroys', function() { return Cesium3DTilesTester.tileDestroys(scene, pointCloudRGBUrl); });