From 41fc01482bcceb77e429d4a50dbf869482f36dac Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Sun, 18 Sep 2016 21:12:36 -0400 Subject: [PATCH 01/27] pnts shader styling --- Apps/Sandcastle/gallery/3D Tiles.html | 2 +- Source/Scene/Cesium3DTileStyle.js | 73 ++- Source/Scene/Cesium3DTileStyleEngine.js | 21 +- Source/Scene/ConditionsExpression.js | 49 +- Source/Scene/Expression.js | 225 +++++++- Source/Scene/PointCloud3DTileContent.js | 536 +++++++++++------- .../Shaders/Builtin/Functions/HSBToRGB.glsl | 24 + .../Shaders/Builtin/Functions/HSLToRGB.glsl | 31 + .../Shaders/Builtin/Functions/RGBToHSB.glsl | 27 + .../Shaders/Builtin/Functions/RGBToHSL.glsl | 34 ++ Source/Shaders/SkyAtmosphereFS.glsl | 24 +- .../pointCloudWithPerPointProperties.pnts | Bin 31324 -> 35392 bytes 12 files changed, 820 insertions(+), 226 deletions(-) create mode 100644 Source/Shaders/Builtin/Functions/HSBToRGB.glsl create mode 100644 Source/Shaders/Builtin/Functions/HSLToRGB.glsl create mode 100644 Source/Shaders/Builtin/Functions/RGBToHSB.glsl create mode 100644 Source/Shaders/Builtin/Functions/RGBToHSL.glsl diff --git a/Apps/Sandcastle/gallery/3D Tiles.html b/Apps/Sandcastle/gallery/3D Tiles.html index 7dc2ff59c820..e8bda0c717fc 100644 --- a/Apps/Sandcastle/gallery/3D Tiles.html +++ b/Apps/Sandcastle/gallery/3D Tiles.html @@ -97,7 +97,7 @@ "true" : "color('blue')" } }, -// "show": false +// "show": false, // "show" : "${Height} >= 0", "meta" : { "description" : "'Building id ${id} has height ${Height}.'" diff --git a/Source/Scene/Cesium3DTileStyle.js b/Source/Scene/Cesium3DTileStyle.js index 644cdda0b9a3..e70c3bc7aeca 100644 --- a/Source/Scene/Cesium3DTileStyle.js +++ b/Source/Scene/Cesium3DTileStyle.js @@ -62,6 +62,11 @@ define([ this._show = undefined; this._meta = undefined; + this._colorShaderFunction = undefined; + this._showShaderFunction = undefined; + this._colorShaderFunctionReady = false; + this._showShaderFunctionReady = false; + var style = this; if (typeof data === 'string') { RequestScheduler.request(data, loadJson).then(function(styleJson) { @@ -80,6 +85,17 @@ 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; + } + var colorExpression = defaultValue(styleJson.color, DEFAULT_JSON_COLOR_EXPRESSION); var showExpression = defaultValue(styleJson.show, DEFAULT_JSON_BOOLEAN_EXPRESSION); @@ -294,9 +310,62 @@ define([ set : function(value) { this._meta = value; } - }, - + } }); + /** + * Gets the color shader function for this style. + * + * @param {String} name Name to give to the generated function. + * @param {String} variablePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} info Stores information about the generated shader function. + * + * @returns {String} The shader function. + * + * @private + */ + Cesium3DTileStyle.prototype.getColorShaderFunction = function(name, variablePrefix, info) { + if (this._colorShaderFunctionReady) { + // Return the cached result, may be undefined + return this._colorShaderFunction; + } + + this._colorShaderFunctionReady = true; + this._colorShaderFunction = this.color.getShaderFunction(name, variablePrefix, 'vec4', info); + //>>includeStart('debug', pragmas.debug); + if (!defined(this._colorShaderFunction)) { + throw new DeveloperError('Could not generate valid shader code for the color style.'); + } + //>>includeEnd('debug'); + return this._colorShaderFunction; + }; + + /** + * Gets the show shader function for this style. + * + * @param {String} name Name to give to the generated function. + * @param {String} variablePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} info Stores information about the generated shader function. + * + * @returns {String} The shader function. + * + * @private + */ + Cesium3DTileStyle.prototype.getShowShaderFunction = function(name, variablePrefix, info) { + if (this._showShaderFunctionReady) { + // Return the cached result, may be undefined + return this._showShaderFunction; + } + + this._showShaderFunctionReady = true; + this._showShaderFunction = this.show.getShaderFunction(name, variablePrefix, 'bool', info); + //>>includeStart('debug', pragmas.debug); + if (!defined(this._showShaderFunction)) { + throw new DeveloperError('Could not generate valid shader code for the show style.'); + } + //>>includeEnd('debug'); + return this._showShaderFunction; + }; + return Cesium3DTileStyle; }); diff --git a/Source/Scene/Cesium3DTileStyleEngine.js b/Source/Scene/Cesium3DTileStyleEngine.js index ad29ebabd14d..a8606fd02274 100644 --- a/Source/Scene/Cesium3DTileStyleEngine.js +++ b/Source/Scene/Cesium3DTileStyleEngine.js @@ -2,11 +2,13 @@ define([ '../Core/Color', '../Core/defined', - '../Core/defineProperties' + '../Core/defineProperties', + './PointCloud3DTileContent' ], function( Color, defined, - defineProperties) { + defineProperties, + PointCloud3DTileContent) { 'use strict'; /** @@ -74,7 +76,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,28 +84,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) { + function styleContent(styleEngine, frameState, content, stats) { var length = content.featuresLength; var style = styleEngine._style; stats.numberOfFeaturesStyled += length; + // Apply style to point cloud. Only apply style if the point cloud is not backed by a batch table. + if ((content instanceof PointCloud3DTileContent) && (length === 0)) { + content.applyStyle(frameState, style); + } + if (!defined(style)) { clearStyle(content); return; diff --git a/Source/Scene/ConditionsExpression.js b/Source/Scene/ConditionsExpression.js index 3a6e85f7b8cb..5d23e1909d5b 100644 --- a/Source/Scene/ConditionsExpression.js +++ b/Source/Scene/ConditionsExpression.js @@ -134,12 +134,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} name Name to give to the generated function. + * @param {String} variablePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {String} returnType The return type of the generated function. + * @param {Object} info Stores information about the generated shader function. + * + * @returns {String} The shader function. + * + * @private + */ + ConditionsExpression.prototype.getShaderFunction = function(name, variablePrefix, returnType, info) { + 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(variablePrefix, info); + var expression = statement.expression.getShaderExpression(variablePrefix, info); + + 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 + ' ' + name + '() \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/Expression.js b/Source/Scene/Expression.js index 84db81ac906d..9f149ecb2162 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,6 +12,7 @@ define([ defined, defineProperties, DeveloperError, + isArray, jsep, ExpressionNodeType) { "use strict"; @@ -117,6 +119,48 @@ define([ return this._runtimeAst.evaluate(feature, result); }; + /** + * Gets the shader function for this expression. + * Returns undefined if the shader function can't be generated from this expression. + * + * @param {String} name Name to give to the generated function. + * @param {String} variablePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {String} returnType The return type of the generated function. + * @param {Object} info Stores information about the generated shader function. + * + * @returns {String} The shader function. + * + * @private + */ + Expression.prototype.getShaderFunction = function(name, variablePrefix, returnType, info) { + var shaderExpression = this.getShaderExpression(variablePrefix, info); + if (!defined(shaderExpression)) { + return undefined; + } + + shaderExpression = returnType + ' ' + name + '() \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} variablePrefix Prefix that is added to any variable names to access vertex attributes. + * @param {Object} info Stores information about the generated shader expression. + * + * @returns {String} The shader expression. + * + * @private + */ + Expression.prototype.getShaderExpression = function(variablePrefix, info) { + return this._runtimeAst.getShaderExpression(variablePrefix, info); + }; + function Node(type, value, left, right, test) { this._type = type; this._value = value; @@ -542,7 +586,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( @@ -629,7 +673,7 @@ define([ Node.prototype._evaluateArray = function(feature, result) { var array = []; - for (var i = 0; i>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 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 + ')'; + } + + Node.prototype.getShaderExpression = function(variablePrefix, info) { + var color; + var left; + var right; + var test; + + var type = this._type; + var value = this._value; + + if (defined(this._left)) { + if (isArray(this._left)) { + // Left can be an array if the type is LITERAL_COLOR + var length = this._left.length; + left = new Array(length); + for (var i = 0; i < length; ++i) { + var shader = this._left[i].getShaderExpression(variablePrefix, info); + if (!defined(shader)) { + // If the left side is not valid shader code, then the expression is not valid + return undefined; + } + left[i] = shader; + } + } else { + left = this._left.getShaderExpression(variablePrefix, info); + 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(variablePrefix, info); + 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(variablePrefix, info); + if (!defined(test)) { + // If the test is not valid shader code, then the expression is not valid + return undefined; + } + } + + switch (type) { + case ExpressionNodeType.VARIABLE: + return variablePrefix + value; + case ExpressionNodeType.UNARY: + // Supported types: +, -, !, Boolean, Number + if ((value === 'isNan') || (value === 'isFinite') || (value === 'String')) { + return undefined; + } else if (value === 'Boolean') { + return 'bool(' + left + ')'; + } else if (value === 'Number') { + return 'float(' + left + ')'; + } + 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.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); + } + return undefined; + 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') { + info.translucent = true; + } + return 'vec4(' + rgb + ', ' + alpha + ')'; + } else { + return 'vec4(' + args[0] + ', 1.0)'; + } + } else if (value === 'rgb') { + 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') { + info.translucent = true; + } + 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) { + info.translucent = true; + } + return colorToVec4(color); + } else { + if (args[3] !== '1.0') { + info.translucent = true; + } + return 'vec4(czm_HSLToRGB(vec3(' + args[0] + ', ' + args[1] + ', ' + args[2] + ')), ' + args[3] + ')'; + } + } + break; + default: + // Not supported: FUNCTION_CALL, ARRAY, REGEX, VARIABLE_IN_STRING, LITERAL_NULL, LITERAL_REGEX, LITERAL_UNDEFINED + return undefined; + } + }; + return Expression; }); diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 33f266e52a28..b31fa2c9dbd8 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -91,9 +91,22 @@ 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._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._opaqueRenderState = undefined; this._translucentRenderState = undefined; @@ -420,15 +433,25 @@ define([ colors : colors, normals : normals, batchIds : batchIds, - isQuantized : isQuantized, - isOctEncoded16P : isOctEncoded16P, styleableProperties : styleableProperties }; + this._isQuantized = isQuantized; + this._isOctEncoded16P = isOctEncoded16P; + 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; @@ -437,57 +460,36 @@ define([ var colors = parsedContent.colors; var normals = parsedContent.normals; var batchIds = parsedContent.batchIds; - var isQuantized = parsedContent.isQuantized; - var isOctEncoded16P = parsedContent.isOctEncoded16P; var styleableProperties = parsedContent.styleableProperties; + var hasStyleableProperties = defined(styleableProperties); + var isQuantized = content._isQuantized; + var isOctEncoded16P = content._isOctEncoded16P; 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 = []; if (hasStyleableProperties) { - styleableShaderAttributes = ''; - styleableVertexAttributes = []; - styleableVertexAttributeLocations = {}; - + var styleableShaderAttributes = {}; 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 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; + //>>includeStart('debug', pragmas.debug); + if (componentDatatype === ComponentDatatype.UNSIGNED_INT) { + throw new DeveloperError('Property "' + name + '" has a component type of UNSIGNED_INT which is not a valid WebGL vertex attribute type.'); } - styleableShaderAttributes += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; + //>>includeEnd('debug'); var vertexBuffer = Buffer.createVertexBuffer({ context : context, @@ -506,93 +508,17 @@ 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 { - 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 { - 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'; + content._styleableShaderAttributes = styleableShaderAttributes; } - 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; @@ -646,7 +572,7 @@ define([ var attributes = []; if (isQuantized) { attributes.push({ - index : positionAttributeLocation, + index : positionLocation, vertexBuffer : positionsVertexBuffer, componentsPerAttribute : 3, componentDatatype : ComponentDatatype.UNSIGNED_SHORT, @@ -656,7 +582,7 @@ define([ }); } else { attributes.push({ - index : positionAttributeLocation, + index : positionLocation, vertexBuffer : positionsVertexBuffer, componentsPerAttribute : 3, componentDatatype : ComponentDatatype.FLOAT, @@ -669,7 +595,7 @@ define([ if (hasColors) { var colorComponentsPerAttribute = isTranslucent ? 4 : 3; attributes.push({ - index : colorAttributeLocation, + index : colorLocation, vertexBuffer : colorsVertexBuffer, componentsPerAttribute : colorComponentsPerAttribute, componentDatatype : ComponentDatatype.UNSIGNED_BYTE, @@ -682,7 +608,7 @@ define([ if (hasNormals) { if (isOctEncoded16P) { attributes.push({ - index : normalAttributeLocation, + index : normalLocation, vertexBuffer : normalsVertexBuffer, componentsPerAttribute : 2, componentDatatype : ComponentDatatype.UNSIGNED_BYTE, @@ -692,7 +618,7 @@ define([ }); } else { attributes.push({ - index : normalAttributeLocation, + index : normalLocation, vertexBuffer : normalsVertexBuffer, componentsPerAttribute : 3, componentDatatype : ComponentDatatype.FLOAT, @@ -705,7 +631,7 @@ define([ if (hasBatchIds) { attributes.push({ - index : batchIdAttributeLocation, + index : batchIdLocation, vertexBuffer : batchIdsVertexBuffer, componentsPerAttribute : 1, componentDatatype : ComponentDatatype.fromTypedArray(batchIds), @@ -724,49 +650,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 @@ -788,43 +693,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 @@ -832,7 +707,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, @@ -840,6 +715,272 @@ define([ }); } + 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 (properties.indexOf(name) === -1) { + properties.push(name); + } + matches = regex.exec(source); + } + } + + 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 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 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 styleTranslucent = isTranslucent; + + if (hasBatchTable) { + // Styling is handled in the batch table + hasStyle = false; + } + + if (hasStyle) { + var info = { + translucent : false + }; + colorStyleFunction = style.getColorShaderFunction('getColorFromStyle', 'czm_tiles3d_style_', info); + showStyleFunction = style.getShowShaderFunction('getShowFromStyle', 'czm_tiles3d_style_', info); + styleTranslucent = info.translucent; + } + + content._styleTranslucent = styleTranslucent; + + var hasColorStyle = defined(colorStyleFunction); + var hasShowStyle = defined(showStyleFunction); + + // Get the properties in use by the style + var styleableProperties = []; + if (hasColorStyle) { + getStyleableProperties(colorStyleFunction, styleableProperties); + } + if (hasShowStyle) { + getStyleableProperties(showStyleFunction, styleableProperties); + } + + // Disable vertex attributes that aren't used in the style, enable attributes that are + var styleableShaderAttributes = content._styleableShaderAttributes; + if (defined(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; + } + } + } + + if (hasColorStyle) { + // Disable the color vertex attribute when a color style is used + var colorVertexAttribute = getVertexAttribute(vertexArray, colorLocation); + colorVertexAttribute.enabled = false; + } + + var attributeLocations = { + a_position : positionLocation + }; + if (hasColors && !hasColorStyle) { + attributeLocations.a_color = colorLocation; + } + if (hasNormals) { + attributeLocations.a_normal = normalLocation; + } + if (hasBatchIds) { + attributeLocations.a_batchId = batchIdLocation; + } + + var attributeIncludes = ''; + 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 reference 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; + } + + attributeIncludes += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; + attributeLocations[attributeName] = attribute.location; + } + + var vs = 'attribute vec3 a_position; \n' + + 'varying vec4 v_color; \n' + + 'uniform float u_pointSize; \n' + + 'uniform vec4 u_highlightColor; \n' + + attributeIncludes; + + if (hasColors && !hasColorStyle) { + if (isTranslucent) { + vs += 'attribute vec4 a_color; \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; + } + + vs += 'void main() \n' + + '{ \n'; + + if (hasColorStyle) { + vs += ' vec4 color = getColorFromStyle() * u_highlightColor; \n'; + } else if (hasColors) { + if (isTranslucent) { + vs += ' vec4 color = a_color * u_highlightColor; \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'; + } + + if (hasShowStyle) { + vs += ' float show = float(getShowFromStyle()); \n' + + ' 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. */ @@ -847,6 +988,18 @@ define([ this._highlightColor = enabled ? color : this._constantColor; }; + /** + * Apply a style to the point cloud. + * + * @param {FrameSate} frameState The frame state. + * @param {Cesium3DTileStyle} style The style. + * + * @private + */ + PointCloud3DTileContent.prototype.applyStyle = function(frameState, style) { + createShaders(this, frameState, style); + }; + /** * Part of the {@link Cesium3DTileContent} interface. */ @@ -855,6 +1008,7 @@ define([ if (!defined(this._drawCommand)) { createResources(this, frameState); + createShaders(this, frameState, tileset.style); updateModelMatrix = true; // Set state to ready @@ -875,7 +1029,7 @@ define([ } // Update the render state - var isTranslucent = (this._highlightColor.alpha < 1.0) || this._isTranslucent; + var isTranslucent = (this._highlightColor.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/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 39d2bb7135870a6227fdb71597c36b2f758b7668..1e95fa4edd600c65074718003ad19d0dbde389c9 100644 GIT binary patch literal 35392 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 literal 31324 zcmai+c|2Cn`~PpGWT~X24Ov}wl1L=uV9$s!~FpPY~(b+($do0XxIq1AU6-c5gx;goW>2X8enTVz{+L%xx?k2H08JT3A^Pu(GqXu?jTjQxEqV z;WgCH$Z3&LNc7y$p;M;J37u=?WEsahcn&FJcp2a@uf`sWVEe_Nj=p`Nm(Lh-WPO^G4Z83@uf_BDGUANbJ6}v z6fEaU$|RODDS>$e<`@%SsuN$z!gc1`6EDG!uL55M%lUe;5nU!?l7not9WW=z|Kdd~ z_|kY679+IhD@+D=Nh>1pZ;!9cZSn%T* z6JM$mU&_RnGVztswjw6=R3{~6iI{my&^gA$m+HiqGV!HMd?kD?;haS*Cz?VRVrh&> z=OZ9S!Z9Ykv@P+aEL>;OUaq*mJc6%+<)mK0g0F&w9F&Cs{tr37ll)|p^L-Mr;H%ID zUj-ZTaY7qXidgU^?Rib`Rj}YonXiVnM5FN{7JO;E3yUGXGOCN1_)?wtQYOB@d>UXr z2W3)HmWx=pzx*-j3-Kj&gvm~s_);dml!-4eX+qobC1sh2iAHteOPTlz*PWCiCcacB zzLbeCFpt1|e#*p`vf$&wAIlY*f{8EL1s@TUW2zHh%ET8~B4XZ)>cp2a@uf`8g)%86 z{7!u-(_>P~_?_w!G4nF$e16J8f93yj3jI|uTmwFbA*~nXBVOX2_|jvEn2D9*Si&(T zzEmf^l!-58;wz(VMNI0cPD;uWG4sBlbBu{E)rl`<;!BzMO88uIeka$7vT&@>Wu%VA zh?$o`=NJ=TsuN$z!gc1`%lTdK<*Nt_z6utO6)Yol3YLiZpZu>a_wRtT6MPl%f-i0B z!eRtpzK!4`=XC`O#|oD5xu`A?GfAU}Athy*h~**{`m2a1z9f$NQYOcgi7#d13(Q*q z^EoJ!lCoUH!u{osNp9jx>IjqTOqm>0mWfy*X3~V?cV3f_Qo%A23z|YFzBER-&g8h1 zB;j`j3&#qUkvauS#LUZ}^SLP#U&_RXGASt&U$P6iMNE9DPU7Zzj4Ys7=*rc6r8q>i#g%p?tM z%P}VXr8@DYOs}zwZ$o@ZNp?72o@p$tqoqU(s!Ql0oxCcc!3uY}J*j)^a2 zIXPD7LLH3}>3jslNI1sCm$oIol!-58!IwW2a?5$(H^Op$r!3Sdbcsm+7oliN^79*s z6?_#e_$pZNRj}a8w-Iv7c~QY~zCC53j>d>|J_2G$EX0dgE@Hu#KPLHzuS~(@I#VXT zlt~iBr#OPTmmCb=jRUtrRN`{yO$OUg146OHP` zmoo7c&ZCqhVN86fPJAg7Utk`AImX17>codKDJc_Q!JFh0G4Z83@uf_Bf%zuD9An~3 zb>d5zcv2Qh-cRTUDHTk7DHC7H5Jxn!$@!fy$C&t1o%m7~`YY#;`FKOVl=CIcN12qA zWg?b{nQum8h%aU0OPTmmCcc!3uZ+(nA-jl4J=IA`St4e>3FsVS;!AbnOPStVl7sk? zQrJZ-ry5_%L=3S+BO9&b7!zNr6JN^0byma+zI>HXCnvyfe4U)slZ`KBA|}VeMoV%) zm=n~0@gf#{X^abt5qwD;X)EV<146ETUSI^TjZ$55!6JN^27nnz2K0jrlzj9K^OCnvk2690b`~?poh>s(A z`Hhs6Wg?b{SrI2R;6(8vCcZQe@uh4i=bt@3m#~SL)Ki_5lqF*38-dO-CjF&4@uf_B zDHC4_pG(-}{GSSzlTyJl5eu3^7urzfMDZdPt}~5sVKIWQkbwU~PJvwyq6ogUisqol zv?N}Njr5mpf-gOAp`duaXq=qa6)Z1hEcntGkuGGRIw=(__$pZNr7%}@MPFS6{O74)pcp2a@uf_BC44RkUoTt_1x8qYDE_(F^eW5Jg{1YZTqNe%_e zL@eY`=>G!vKS^$~lZ`KBB9@C-xW9@R!I#vLU9Kp37JL=D;7emlSqx9ilh*U4;Hx+m zd}*BvizmJiCt~7Db>d4|B4$1f=zMOZ`Y>cp2a@dcKMnD?SO$wis?QYOBXNh#rX>PwmUQYOBXi7zmZz5z#Lm}4yTfsZ34@r4){#)2<@D8$Nn;5Q+jZ%0`s zVv>XE|47l6-Abxkwl8uR<4mX{?+V6)Z1hEcntGkos^U%V&;88=NJ=TsuN$z!oB6&6OH(i zT!hKBp)BV$$}$lXjp{rxChhq;!AHbGUWG0^R|*z<`6l42U^!n;HX)CQ1z&|O1W@Mw z9f7Yx7xK~=7ZxM<@@>FZ!Gf=XWu<&PjS(|RLv_BSEEBO@#6o`+@x+(J3H2f-zEmf^ zlqF*3tw85z+B5)l($suN$z#21(};oFg2q9_T=L`;t9PJAf~ z*I98)e4(zCG4Z83@df4)n9om{_);c5lu1dM_)>4;OIgn6r%Ze)6JKCH4KSadGV!HM zd?^!8%0kKe3Fkse1ruM&#FsMh1?CZ$&rezCube-2;U$p{9=vYI*9#szlXjFzNm(Xh ziI_xbQp4 zPc|36Pa+n46}k|oV4=Sxj*lnTS!mC*;H%IDUm8=&Vt9ghp&gAUrGf=t1;hEd{Ohlb z>T<=o@;dRQI`O3}5i_42bUp`VQc{+S7_NoFm(&zchU!f%Re16Krmoo7M<`J0BPnq~qratueU->9>+r7c5OKZN|Zti|$=dX+R zes$iOWNPua?+|ypbaSU?&0jP3O!DfaF}X41lSgfj&u>Z|k8<1GMtkE8b)D{`5t9^R{K963evZamxR(HMUQ`CMm z_*VV)o9NcTG<^8dv+Iu+J+9Bb8`aR!Br~@;{jJ4@OKM(A+kR>EyyKofTCGd#!&ytS zy@KKjj@`K7lK#moqKunjd%U>zzI1>ldfBgJjoCMx-($%+GmWcJE3=;4u2s|YYK)Fw zbJ4ptG1ArVKz5taOT8zqzGkoXvv6lb+>=k|RUN+lZtU6TbeP`5Bk%hSo3j5wxbaL! zy_Q7|pPqM~D-iEqK%w$1|UY_SpZ0Ykc1+ zc2#6AHSWUt)Lf4P`M>tfjmtip5t|VI`0~INIWJb}&(Tm*D!uu(*R}UC zk5d*54OpD4t#8tCQqvmk2i8|jM#WpGxF^4>Rd>2wJ8w+(d8b=TZCgn3*YDvSY~{a( z3_W_K{?mNx_dRkyExEb(p5$$8s7C)6lb+Rfh%k7NH@%nD@&`{!mzwyFc{IYtS`rwU z)+;=s&sS%^@whbdQa|rkYxW%*rv8d)^{L}vK?zgY?$q%)ez3$$^owcuHs;V}tv=146Y^vJio3a=4%(6EQLTfHe zti0Ie@rcmqqo2k_eVyN_`ehqwLru-ogF(}0I;(i(%)I#MQ+|wo!Kx*%TK=iOcVp!K zS@EBYcW3&g)*SZ8yIbU5lV*QP`@wwAIlf^72c6XmSvP3T{%-^EtF1el>XSD$#s9YH z*gJ08tem{Qi>%jWwwu?xKA^VPz47U_!R``^Lj^`UQ}shsCmb2L=*5E4H&3Hpsz#)G zZg5R%a31kk?d-|lJ(Et{v1qR{!^p|>;CzPFBZ)XHQ7;k-WdDJDn z@o`Jt0$(n+u|K^0XlA9xil*&Nb}bTbIGwrcwK@9o%@X-T!3@EO1D2 z?yHw8?fJ#}#;W1^jxp9M$IR7AZuy`4lM+6>DmQDdKwB^$= z@^J+xpXWqL+C4Wb+tzXY(hniYhc9${{vdI~kF1kwNIf+pOndnQ-SwV1S}w&7lN<** z=7hGbG@Kr9Rad-X?3?=HUsq~=p87B8jWu$4`r?wQb=kqut2$k|Z)}b$e>|P5XV_mi z@%4ko6)kb*v-Kab(awNLbq)`U^KacOt8~mNiQU?=f7Y26sa;p*MQpt`)iivNX28^T zq3*v{85C5gHjlhnkC$h0R-Jw`{48=&d$pB z(R5kIfuju;j&Rv@%&GcS!horc>V2mfPIWmn@yiwzG30Jx`n#8T^}FS*)}&2rIWkn& zE$Pmr2Pb>ns9bz8-tfgrBh}%T+73?s*(bdsq*A5oh0<*EX}TvyHnj)FPF4BZq(Mbbo#NobmyOx=u85=$w6_?W6L+bLW+P$*p|z7hD@%i;iep z>L2d?ZJcENr&!MLdkA_Vw8ns@xIL#u-;*NiFT=bLWErSb&YR)gv>*{Yd>)O#* zd#(-eU0eS1hQ0B&hwHc9sq1b~UQwEKEG(dQ``|gQx0{deGw_#$cf0p`+Q`t`VF^=h z|68|pknYT=mv{3o&hoR2>Yu9>Z)tP&obD@|=`~}P{aKQ@rpP@YUS3yxuwUNd5lSce zs`=MRN;E%pPqqxTOzSv95?D7}3EzL0YjU}9cjm?|>8+nCKh?r_zpr#@zIA-Jm&)2@ zPI;C~Oe1#e{GEEb*4H5RqO#`MuARf|xA*Ve%Qhr)v5(44M|U&NBwe+~I|Aw(y|;hz zGRlbCdDuo*v*Pfi_GKrw_&u%6IT;Z+Z;O+@_MmvfNz#pvnmo5uZ%ZEFy`ZxA<0!{7 z&%bc!r+P-2!}1m$Yx_n!jA>)mFX!2P&s!Vx z-0fo*cGR_+bKG~q=HOL1wa1R0ad>m<>wtnMkIcMYg$(>}Lhy$9%Bjz#E2ir!yK2;a z_B#=8w4m;SdfL)OUA;cOEiE-lu@2t;Iwdjhs7A+WnKHTFnAGlFdiZnS;JuGrr-u&vFd@X|ZGQSE=@=cGF%{Jj8>I=h%ND9% z_)=2XYEF>arKqlft&X@i9Ixu46Y|re>Rg{Q>eIHRe0KV57B>5<%Aqg*mfy!m-W`7N zockW__=^{t9_P>L*JUOuoaNTA&VTov)Ul^`J{WoE&GhL8PZNyq-8@z6dg1ZOp5xU{ zJgt1VwKKkUWKNfK&93gQgBv#KCPX#%=+OU)+kF$mD<{7er*Ek>T=w5RW3xv;H;&o( zDb{4J=YE5&#zpO1G6y_~7{6ft_fXZ}`1rR7C7*Z8oI4Lxy{x`B`N`>8PCMa9WP5q# zQ~TJ8^V0qS1*SbJJpAf@y;PfNo_Mx!qMfnv%hfFo&05&f;QL+;)#au(!<9b$$h&FP zH!XVakC;t^X4Y8uU*3Aq{ptBJJ2JRc+aL89XlG`iveVfwH}&4e{qr_?{F&fmJ*1-i zzvC&{8WwH!E6g0VOwa1y{QhQ$&s_CYYhL#;&7PO^r(^oyJyTwG%N}>Iy<5O6mCL)H zT7_E$>-=1KKS5d*XSZ_bzPOL}S3ZpDqB$%2^t7t!QRfaBJk<6xsdKvWX5jfjtJ3zT z?P${^iJw`!N-1m2LiOmYcR^qVRuftxo$!1e>)2%IXBYnD$nQ-IRrbz)!NzW1-+v(gcPv7}^ zdx#sbz=L9=Vtv|x4kyNU%g+!&q>Ex zM9wwqJ!y%~Yu~fyt~}lT+;gyl`d;5(6V$)1tv_se?Azs@{o0LJxjg%;ad>=qmw^j< zFM27>Xza#iMC4k8)s_F!eV)C?VxraWBn#uhgTvn2H_dq;klWvdgvNSuMgHRAD;w*J^o^)h<%beYrhxZ~0m?=MOrwhLZESa5f(NGA zp@_G$k?!edNM&*!((P3&{c~3nO}lXb-5IZpo4y=Gi%nIe@;MFYZ`wROW?~k;lsp}u zOq1Zbsj1S~75mXlvkrJp*Ip=m;W=d8D;?YDKE$muzvF%L?&Ah79lw8g6PbOoN5Rbo z*dThHw5Mq&oY(3oo=~L8RWuoMyY)J90rDGouDu_=aPqa(yO%otwo%8~t8-VpEZ`^} z9@~!l{j?wFqiMo@{_+)XAG{Qo1eT!V9ao{|=rdA}s&8m_YzE#L_8z~i)8VXpYjYb{ zJi|k@QgFiIDwI*3=WOKoAF2q-Mj`!;u#fj@yyyD`+$FdY=f~{A6RU^d3tM&|--Qua z|A!2_pNhu!+PUDn53|wPP8tX&=cA&}322n7+_~&j5z+Ztr>=O8MINpk+W_8i)7)fK-i)yDea=TP_Nz0x7wD$$~bbx75x zS*mg06OB}}#FrPW#Rswv;aKOLc#QH?eChmgw6NoO)VXF8^6vFsx-X>%PW!YQ*Ecs{ z4JR#b$qi-BVVN9{v*hsV=guhW<6-1cCq-K_eevW;mvPpjj@Yh zicfrcB`v+Y7j3(3fc%?mu)gC-JoH!_Ze*z$x9gP==a>Es_jsRyuPSdw6OX!}m8tKg z>y4ix^YQcX)p3*eg`bX+Rc>V^(4D?=+^xZzXv zOL25`I`#@1j?GKnqt}7=q`HUt;*&`j_X?9@qk)F_j^9Icbu5Qct(QCh4o{R`toD)q z9+M*7yG#uSb{K%iTe)J1W*@w5!J58Br>S#UTk5o0V5e=A;g6?#RM|Km zc*FHSsPF2NNPS6+^M%GaD?_iZt@Te}a-y2x;QM>&$= z9(YBY)o5IsQz)QM48EgZiH&Br;+}8T<-#?!xPquE{Oa6zobmN2+P1h5ZQbw=`4?=) zkxPGLnRXxUtqtOKRSe*Kywo|r>aDo@Xt{Izy&QDW@j41>pM~XTzu^AoOt{}`oH+TG z{@f(DR$QL>4t(&|GBmeg6q?gvAu7ppz%lW=@wN01c-lF2&bdj2Q!}c-mjWl_I)@mv z?P{fTzQ+OQ|I82y8Qy@}8xF(%zA1Rnmn7WYc@R#U{urf>Y>+lITt*`&+&~d2^=S0$ zPWaj*OPsdY8i&=i$4hVRMMEm8q@zznNjb-5$ZXFoRMMv%-kfNLN9j7?pf`^A?(ClU z^0PXW_hK_z_w6XEt(lF=t&Snb&q~<8NC&s6*1;=(wZQ{Tm2sPgg2LRNy@qUcHoTH9m6xv>_fJRRVY_yDT+BCiyb}g;W=S# zxgOgLxj9ohag&bM;#EUd;(Na@A=UM(k!yaUv@&HHa@XpPaa;nv8<>rAJMi~aeKFQ; z?12}lZ$M=U$55ZwGSs8;HgX=~i;E1_W3&GG*tT*Ve)xmK=kP}K`NuZtmXl*p%zqnD z-lcYUO!rwh(qcDm99e=#zCD2V1<0^rRd?)h>>G;so{dhOJCDK^55Pw&lW@??J=pl= zQLJQ`haVWF;P|7%aZpku_8Dl0orcz+fH*b0^{os3A)SsVPl&_muOje=hJm=-3SS)R zx&x2;y9SRAeT*`PFPGMDo#HflLzO-G;f-wbXSb2QoN^Wgb=!icW*)&yyjP>e{u)>{ zJQhdI+JbWv_X_1Qiv_rOd=Wa7>xDa-9K_p9W=jw1q~q<=-{a07w7IBR-8fQC9{V1@ zN?nS-AMAk+ZcW3#&D2rcXNr-s z+7WcAwIgnD+mE-UG{X3-R3gW&BSQG`uoYf8PYVal(81f6B;w=&*YK$&Y}_~+S&b7K zHeu~&33%Sl6o5FzN-Hk)dNY1O-oIW6*mQ9y z`gwE|?y&8E)Thb`mz5vDgGbk3!$u|UQ174kivC61uPFwHd&rT~iob}TFVH(9ne@@v zt7y|z7aY)L3I26(1rE%P!fV63;hQ7ZpmukR@bBc^Xz-B^xG&DZhL?WeEzz1>pKo7r zOu;6M)IOm_c{LGPyFI}>wo#H|&$ptl^yd1-RC z>n*vv<(S)ZeE|0?SBpEOeHr`O?8HZpea7217U7IT#ki!b_J5vdSz#?c+w(9!U$+O_ zm9^r?eIfUU+$Zt-p_CGbRUdj|ES-bRRlj1NJxTb5^Kj_8DvZQYjo?Ss9B@&k4tEEo||Jj@OWDzwgd1bENp**f3Pa z&zpWXN8nwV{Pk7N#x~bB;;gy|9QvRc9oiw2E=xOrA}i*ih&h{_pA`n617mB@PDx)} z80UhG?;GHXMcdKKRr{P;9oNP|pCZtwyRXrt&=okV<8$nK{x5#^^$|X$lZK00mLs1B zy3z*&Zla39HOSAqH}2W;0CrfW%w-Pj#0_Yx!I^idz>8~!<1Qn1pj)LnxaB52%=u^G zk;-53Og&@H!o-eq4d}z^n=5g*e{R7eO?u+oFX?z~(GvXL?Ho3qsKa$HbmsgleK@5^ zDfjiR4wv}pEOzsX!e1*Zaa7s?e8Z&@%jz|`@^li8KH@>w$;cCQuJ_hU0IoOl^;xVH?KfA`0m+n3?{5vjQE4Gy;(;flRp zhvN>9WAXVT5yH6XI=mgWN>RaW`o&^0&M!y~Kp!KvI}h(0X1B=O&B3#WfwNoVEa{=; zrYLRGPLy`{8M;_C2X%T-Dzy#YjXq|*K;7?3@YD&PQPh)-C^vJRv(k?Y)SQwp9XtM= zbo-P=&h%;O<{_<}k?cokQ zbn6McL$(c1SLN^#=S67fHg)Ge*0a$>cXd=^bpu(rMd2~qPho?z5AiSMax9sdic3&S ztefqLB0lFK`%4$l`1|AW&5|=%PgTNQIH$>(9Q=!S>y+S!-ovn_Z!sG8zCU&==Wx#B zY~1M5jN@tzIE_>@F5z1juE)eWtQEfkcb2!n3pXX>g>4gY(DFmr{)dD+7uJKDRMnUJ zxT8DQ^J61^Sg{E!>2=2EJyzoRE=f4{*m}HA{~EU3E8*PEwB{WC{=xB&FX8&}OYp-l z?eMsnQ}L&)!T8KYcYJKzLX4ko=8yN`W81glF9(rrYmFR)4$N$;3ufVfPAF3 z`ZT(!`31eZp@J{3{fTM~pP_AbPte1^-_SUDxzzQD7RuEdB8^{KRI4^fnl&{I zZS@P5%7Qhdfl1n^FXT|zQ!xSu-`Iqg3^;-}bUuVP*k<8_ zsm{3H{X^*XFLNaO`vygw?Tx?uT8_#4wPMa2oE`fNFAh6}HK!-yQ|nc5m#LcQ`SR{K zv)mQ8UZ064F1&!xTfM#*zCH4qqKo%`?jy`&y$)@}T|jJ%81M3tgUK2KKrr>LQy1`6xE(s}>J z&C)|TYUuUmK4{!j4RpSBhIDx+nN<0gE%K~9jy5$OKx0+y(3Ml8rPtGvrTSw&NFB}@ zNJpK1D~;=Cff7G$lumoUTv{|4lfMrXHv7xP{Qq(5-?sf?AJE^)>p=IZUC;QwE5s}^ zFa>={!7<>z?{lRzXS3ZRh<})C2fXFv0^ot>5@37%Gmr-r6d;<%>O%y?_&E=Sm?P6i zf!=dR7tlM+^#t9gf%WCj`fm_ZbE_M~Wc8W_+~I5qJO4KppnKVS!ubTIvVIku$b_8T z=5Gf6WWe%oFE)WZpZoQIm_6z7pyvg50B+Hw1)PxY3*5x7#ppG8GkZGd&f8l7ckgx( z=E$AXoNMOD5lc4R73;LjY#Xbc6*|0I7 zTlQr4?s#`I(7*VtfOrG_`@kzSE(2dbJqhABN=$**ZEFuPD!n>^zW9Dm;IQw;z-xP0 zLVUX3O5oKV?Ag0l6b*epmo*h)_GrxpUNdt!@Y3nOfCuhl_jk_DVc_fkHiofBJ;Yzl z83DSMY6ke$%Q(>cWwU3gZQB;0cd26cVzFc{=njjnK%Pgp)j>}m&hB;T_789lx7^sf z>{rkF@@4M`h{?!s0p2}gJMevPcgR2aC>uAlY^H!7*KQ}o7cNTx{`_Sy#H=da2Kr{3 z4ZwqyLm{R){3pDRxsPr_9|Ql~g7`^;4}czf=?Uls4j#{d%uRBKM3)zOZ_3n zzdw6^?A$QKOnboI_x#uF8IBL@3vCmg=0W_%BelTU*$04wE7|+5UoaEedXzkdm}?_C zgWh|?Lg2oOHGqp!djs3vZ~>ludMfaTL^eNeJQx6a!bu-sjU((jr{7}F|4II5F?ww` z9yKyw_!^l=H+a54)YWFb-7}Dca_aG;eWlL z?f0c@%#Y}O1>%#;)&f8J!R|{=2U}=cQ>_a8Ys>`T7X5a@__Op*5}b4Uoi8Ehwc=Hv z&vs|`Zh6Wph{65X^X6I}1^G|3&xe@B`z%49{)pZ8msiyx20Pb)*U)Qh{@u4g584*{ zusJ%**_r>ptNz=vr`SWx>}PD;voD?q`d!cYz^Z#2!1q-IyFT98EYF~JnQ%Uh+5Mnx zzs_tfo}qdP^7LN8-fN`*c3&iZ|3TXV<>inwA%KmgGlSSQ)QEJ3_y;SRA-`{X))$L< zc5PKXze7y)9rnx&={g+p%ywn-(YQNotg}6F722L~Gl2MP-9X@u?GeQM*S{U`ic{=; z{L^j>=(jh5RHH7$tjlBL=Y&TQkkk1GTYIM! zOoaF@3$DTYRdJfFuX^n_ff#@D#lX|M9tG~A&DKjV$BqWCjZ#nG-}Z43-(2|#SSOY} z+e3DAh4`{yHcr0!&AyL|X3KXJh*YZ}xqsQOUk{q^%>tYo2)tk0r+7PoTh+XreTiINovVh&w zzu|1GzPpZ%ExTT9fjl>@*?OsAF&ldW?kVGZ|bc3_(z0j)(gTA!d7XdM4 zU9y3@_G5beB=!#0M6&arf!Mmj-D?r#Z1;RD{DzS8Cl0R3;~@5}T@=dZ?%O?PK+L(h zZ0@)}lZ`X^)9v8gPWS`>JFH~SXX0k|jQOip!}<7n--L1X?PK=t^e0teZ#IqSD6>({nTHV|W{&b~*o3fUN#Ql>D*DzgHUi&4KiKy|g$Db!vYGY}uGOwg z_MM`;gMB|9k7Ccm_OsfMbJP&Fe!et;#Sb=M*JoDGiH`I6&(>)hEc(FtukCyc;`57qL7y0M z3C?+POayHdF?*JNIk5Mx@IfKOFKEyDmDiS?v;I0Z*YtdM5PVg9*t+`da`vs~ zpMxQ1$nnfAkr)PIw$S4&n{RJHj~xZXXZ)b;nnTvGJY@Jjih!aJ#!~ z9rCpQFlhU1^D~G!@s8c!tt#w!vuj}Yv}hE&?;YNSK%SDL_aTp7+b_WH4zT;8RLADf zR)Oq1U!}3D3Si_#HaLjT}xW#fhTxWAoVg;Mm?v>TRxph9n z`nx2Vjp3`(k|C!0n=<&Ov}fbYg|2_#Jh#1N^GidnKg74|8wULKIRakS%1^UiMU zUAsNs9>!S96)fiQRdzmo?AZ56=ZGNaV^}Twt+iic8#uQwZ!Lh`&i04+5w!)Nf7rq1 zkyrWbn{Is01Bm(gI0D+XESn5`htmTtQDS;s_6=y;rwd!N**;;<=f&sjJbztf*Xr*( zc0Nz^*T8+RuVCLVU)#Qc{INaRIrRED3C^c7bU4I3sblj+*;n>^@1CZu5cBY4C*ZU{ zY_9iIX5R~oUd@O6vL?1p-ky;MZD;Q93@kNe-%!7wM1$^Q#>U2cXLfC~PaK698_o>8 zLizv3hR%->7Slk_3SskpU@|+;k`@ag#_cMbLo;`>vHI8KVURP;hs{SL4l{n0>j1y= z2q1j5)-exw*;+Tf?a2l}-J-+8+1Tju)$_B=?H*m!6ZngG{9 zN7WYcoJ^h#?9qe$ZfSIRJmgne8w)Y52mb`Vb&&m*(re6J&<{Lg^Xd8x!H{S53=d!p z|Lzcf!zTyMZP918W+?yW4KY948baGUQ`xshC+|U^9}XD+Y_uyJ+K!6o3cB5g8{m~N zYbV59{mbS@({T1%$LA|-Z0vZ=6!OTWY~NKiEr%rsZ3?Q^V_wB{yN+Whxne8a=|M>W(G0C z^%xzq;A7@wTjjjrr%NGX1n@?Kh~+V91|wb~^NHU<3PY z^Wm(a;I(`ddk!1+usIZEv%ep)S7LpM-^#8}^w>hkvs#tSFD{6!8?DE&@v4=StqCm; zkAv%$yW0@*EW6CUk^e@qaX$SqTPx;_sDOTTyx<4@-B8KK3Ry=swj_tLap3e2_ICjX z|6}(eJ$D@Bkts3$R?X&}UlnXV9oQ)j;x~;-g7deU&c?S+dF(w6C}3k+Q7d*m$4_DN z-NWT0AZG@e3)jb_R~2yg`|KTg^iCCgYlGOjdU7i($m4m$82DJ{2XNg^rt5(IW1A-A zxjKf;m6f;H_pjL<_MNrjE?dt}8`=xnMh|6c^4r(hm^}IfTPLWFW_|41_!Zi!-e=Ek znJt?`{l~N4Er--3!*!^&Wox946WKGY)~gwEuF7TOn8RkazDiSNb6?D_HgFwwbY^2( zhz`3)%hK6ebcu!+#Gl!J8@!V4vU%i74qFejKEs~9xf?8@ZDUtmVCU$S(61+p+3!x< zyx4oBF|-uCwhw0Wsg>*n=ytZ1z|(iJ-^R-(RDkX|i{0OC2ifn^(^|7Je4G-yUm1by z*|_zRQoTXExXLgT5rLl0eQu{-Z#b%&Y>vCYs&T>RGI=GD2V+%Y{xboA%06$HL$YzI$-1d?Av6?+-$fGXI8Rt zyMH6glUv8;^gb=vIGq1tBjlWz&H?+bcniE`{3GDwXIa~6Hte_43)A*O%woHAV2czp z;2$A(fSozS>;5}}XAz1X*B+fnR!+clfbYmzpHz}MEB{pLBgC!6m^2X29w@VUo< zU(R6PAjSIS;2XbZ2gJCPv+qjVy>p?zdLisxI}jcUdgu-xV55^wkjJr!%_&cAyacZ; zk6FC&q3NJUrtAkc>cRedU8{#|KJr>)25TxCj*ZFZM9=EEH*8&+XVnwVZMP$PPh~CG zm=kAY0WoI=TfzCq4NeCBd#WYG1jMlG_Sua+t83P^+L=`WEg>=4^JJmR9U~y2#nMQI^;UzP*dt`r-cp D^A_)V From 8e98836ae8166550284f121d2e2b4e94bb10c2f9 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 20 Sep 2016 12:13:58 -0400 Subject: [PATCH 02/27] Organization --- Source/Scene/Batched3DModel3DTileContent.js | 7 +++++++ Source/Scene/Cesium3DTileContent.js | 19 +++++++++++++++++++ Source/Scene/Cesium3DTileStyle.js | 2 ++ Source/Scene/Cesium3DTileStyleEngine.js | 5 ++--- Source/Scene/ConditionsExpression.js | 7 +++++-- Source/Scene/Empty3DTileContent.js | 7 +++++++ Source/Scene/Instanced3DModel3DTileContent.js | 7 +++++++ Source/Scene/PointCloud3DTileContent.js | 15 +++++++-------- Source/Scene/Tileset3DTileContent.js | 7 +++++++ 9 files changed, 63 insertions(+), 13 deletions(-) diff --git a/Source/Scene/Batched3DModel3DTileContent.js b/Source/Scene/Batched3DModel3DTileContent.js index 881326e54a6c..858693c35f4f 100644 --- a/Source/Scene/Batched3DModel3DTileContent.js +++ b/Source/Scene/Batched3DModel3DTileContent.js @@ -289,6 +289,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 e70c3bc7aeca..09aaf90b6efc 100644 --- a/Source/Scene/Cesium3DTileStyle.js +++ b/Source/Scene/Cesium3DTileStyle.js @@ -113,6 +113,8 @@ 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; diff --git a/Source/Scene/Cesium3DTileStyleEngine.js b/Source/Scene/Cesium3DTileStyleEngine.js index a8606fd02274..88b23a01a8d1 100644 --- a/Source/Scene/Cesium3DTileStyleEngine.js +++ b/Source/Scene/Cesium3DTileStyleEngine.js @@ -106,9 +106,8 @@ define([ stats.numberOfFeaturesStyled += length; - // Apply style to point cloud. Only apply style if the point cloud is not backed by a batch table. - if ((content instanceof PointCloud3DTileContent) && (length === 0)) { - content.applyStyle(frameState, style); + if (content.applyStyleWithShader(frameState, style)) { + return; } if (!defined(style)) { diff --git a/Source/Scene/ConditionsExpression.js b/Source/Scene/ConditionsExpression.js index 5d23e1909d5b..b60ebaa78384 100644 --- a/Source/Scene/ConditionsExpression.js +++ b/Source/Scene/ConditionsExpression.js @@ -81,15 +81,18 @@ define([ var exp = expression._expression; for (var cond in conditions) { if (conditions.hasOwnProperty(cond)) { - var colorExpression = conditions[cond]; + cond = String(cond); + var condExpression = String(conditions[cond]); 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) )); } } 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/Instanced3DModel3DTileContent.js b/Source/Scene/Instanced3DModel3DTileContent.js index 6df988d56792..9f9a3c583637 100644 --- a/Source/Scene/Instanced3DModel3DTileContent.js +++ b/Source/Scene/Instanced3DModel3DTileContent.js @@ -463,6 +463,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 ea5786feef38..8beb3e10a12c 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -1026,15 +1026,14 @@ define([ }; /** - * Apply a style to the point cloud. - * - * @param {FrameSate} frameState The frame state. - * @param {Cesium3DTileStyle} style The style. - * - * @private + * Part of the {@link Cesium3DTileContent} interface. */ - PointCloud3DTileContent.prototype.applyStyle = function(frameState, style) { - createShaders(this, frameState, style); + PointCloud3DTileContent.prototype.applyStyleWithShader = function(frameState, style) { + if (!defined(this.batchTable)) { + createShaders(this, frameState, style); + return true; + } + return false; }; /** 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. */ From b7c2dfddb58a1477e6023f32e314668541106957 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 20 Sep 2016 12:21:25 -0400 Subject: [PATCH 03/27] Rearrange shader construction --- Source/Scene/PointCloud3DTileContent.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 8beb3e10a12c..9ab20054f425 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -840,7 +840,11 @@ define([ attributeLocations.a_batchId = batchIdLocation; } - var attributeIncludes = ''; + var vs = 'attribute vec3 a_position; \n' + + 'varying vec4 v_color; \n' + + 'uniform float u_pointSize; \n' + + 'uniform vec4 u_highlightColor; \n'; + var length = styleableProperties.length; for (i = 0; i < length; ++i) { name = styleableProperties[i]; @@ -861,16 +865,10 @@ define([ attributeType = 'vec' + componentCount; } - attributeIncludes += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; + vs += 'attribute ' + attributeType + ' ' + attributeName + '; \n'; attributeLocations[attributeName] = attribute.location; } - var vs = 'attribute vec3 a_position; \n' + - 'varying vec4 v_color; \n' + - 'uniform float u_pointSize; \n' + - 'uniform vec4 u_highlightColor; \n' + - attributeIncludes; - if (hasColors && !hasColorStyle) { if (isTranslucent) { vs += 'attribute vec4 a_color; \n'; From cc1c4e63d1f36bd32cc8f26c5365ae15355ae83b Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 27 Sep 2016 14:44:23 -0400 Subject: [PATCH 04/27] Reorganize parameters and support ARRAY type --- Source/Scene/Cesium3DTileStyle.js | 20 +++--- Source/Scene/ConditionsExpression.js | 14 ++-- Source/Scene/Expression.js | 91 ++++++++++++++++--------- Source/Scene/PointCloud3DTileContent.js | 8 +-- 4 files changed, 78 insertions(+), 55 deletions(-) diff --git a/Source/Scene/Cesium3DTileStyle.js b/Source/Scene/Cesium3DTileStyle.js index 09aaf90b6efc..ebe58488f23a 100644 --- a/Source/Scene/Cesium3DTileStyle.js +++ b/Source/Scene/Cesium3DTileStyle.js @@ -318,22 +318,22 @@ define([ /** * Gets the color shader function for this style. * - * @param {String} name Name to give to the generated function. - * @param {String} variablePrefix Prefix that is added to any variable names to access vertex attributes. - * @param {Object} info Stores information about the generated shader function. + * @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(name, variablePrefix, info) { + 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(name, variablePrefix, 'vec4', info); + this._colorShaderFunction = this.color.getShaderFunction(functionName, attributePrefix, shaderState, 'vec4'); //>>includeStart('debug', pragmas.debug); if (!defined(this._colorShaderFunction)) { throw new DeveloperError('Could not generate valid shader code for the color style.'); @@ -345,22 +345,22 @@ define([ /** * Gets the show shader function for this style. * - * @param {String} name Name to give to the generated function. - * @param {String} variablePrefix Prefix that is added to any variable names to access vertex attributes. - * @param {Object} info Stores information about the generated shader function. + * @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(name, variablePrefix, info) { + 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(name, variablePrefix, 'bool', info); + this._showShaderFunction = this.show.getShaderFunction(functionName, attributePrefix, shaderState, 'bool'); //>>includeStart('debug', pragmas.debug); if (!defined(this._showShaderFunction)) { throw new DeveloperError('Could not generate valid shader code for the show style.'); diff --git a/Source/Scene/ConditionsExpression.js b/Source/Scene/ConditionsExpression.js index b60ebaa78384..70be56e9a854 100644 --- a/Source/Scene/ConditionsExpression.js +++ b/Source/Scene/ConditionsExpression.js @@ -148,16 +148,16 @@ define([ * Gets the shader function for this expression. * Returns undefined if the shader function can't be generated from this expression. * - * @param {String} name Name to give to the generated function. - * @param {String} variablePrefix Prefix that is added to any variable names to access vertex attributes. + * @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. - * @param {Object} info Stores information about the generated shader function. * * @returns {String} The shader function. * * @private */ - ConditionsExpression.prototype.getShaderFunction = function(name, variablePrefix, returnType, info) { + ConditionsExpression.prototype.getShaderFunction = function(functionName, attributePrefix, shaderState, returnType) { var conditions = this._runtimeConditions; if (!defined(conditions) || conditions.length === 0) { return undefined; @@ -167,8 +167,8 @@ define([ var length = conditions.length; for (var i = 0; i < length; ++i) { var statement = conditions[i]; - var condition = statement.condition.getShaderExpression(variablePrefix, info); - var expression = statement.expression.getShaderExpression(variablePrefix, info); + var condition = statement.condition.getShaderExpression(attributePrefix, shaderState); + var expression = statement.expression.getShaderExpression(attributePrefix, shaderState); if (!defined(condition) || !defined(expression)) { return undefined; @@ -182,7 +182,7 @@ define([ ' } \n'; } - shaderFunction = returnType + ' ' + name + '() \n' + + shaderFunction = returnType + ' ' + functionName + '() \n' + '{ \n' + shaderFunction + ' return ' + returnType + '(1.0); \n' + // Return a default value if no conditions are met diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index 9f149ecb2162..d75c4289f592 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -123,22 +123,22 @@ define([ * Gets the shader function for this expression. * Returns undefined if the shader function can't be generated from this expression. * - * @param {String} name Name to give to the generated function. - * @param {String} variablePrefix Prefix that is added to any variable names to access vertex attributes. + * @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. - * @param {Object} info Stores information about the generated shader function. * * @returns {String} The shader function. * * @private */ - Expression.prototype.getShaderFunction = function(name, variablePrefix, returnType, info) { - var shaderExpression = this.getShaderExpression(variablePrefix, info); + Expression.prototype.getShaderFunction = function(functionName, attributePrefix, shaderState, returnType) { + var shaderExpression = this.getShaderExpression(attributePrefix, shaderState); if (!defined(shaderExpression)) { return undefined; } - shaderExpression = returnType + ' ' + name + '() \n' + + shaderExpression = returnType + ' ' + functionName + '() \n' + '{ \n' + ' return ' + shaderExpression + '; \n' + '} \n'; @@ -150,15 +150,15 @@ define([ * Gets the shader expression for this expression. * Returns undefined if the shader expression can't be generated from this expression. * - * @param {String} variablePrefix Prefix that is added to any variable names to access vertex attributes. - * @param {Object} info Stores information about the generated shader 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(variablePrefix, info) { - return this._runtimeAst.getShaderExpression(variablePrefix, info); + Expression.prototype.getShaderExpression = function(attributePrefix, shaderState) { + return this._runtimeAst.getShaderExpression(attributePrefix, shaderState); }; function Node(type, value, left, right, test) { @@ -966,7 +966,21 @@ define([ return 'vec4(' + r + ', ' + g + ', ' + b + ', ' + a + ')'; } - Node.prototype.getShaderExpression = function(variablePrefix, info) { + 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; @@ -978,27 +992,18 @@ define([ if (defined(this._left)) { if (isArray(this._left)) { // Left can be an array if the type is LITERAL_COLOR - var length = this._left.length; - left = new Array(length); - for (var i = 0; i < length; ++i) { - var shader = this._left[i].getShaderExpression(variablePrefix, info); - if (!defined(shader)) { - // If the left side is not valid shader code, then the expression is not valid - return undefined; - } - left[i] = shader; - } + left = getExpressionArray(this._left, attributePrefix, shaderState); } else { - left = this._left.getShaderExpression(variablePrefix, info); - if (!defined(left)) { - // If the left side is not valid shader code, then the expression is not valid - return undefined; - } + 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(variablePrefix, info); + 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; @@ -1006,16 +1011,25 @@ define([ } if (defined(this._test)) { - test = this._test.getShaderExpression(variablePrefix, info); + 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 variablePrefix + value; + return attributePrefix + value; case ExpressionNodeType.UNARY: // Supported types: +, -, !, Boolean, Number if ((value === 'isNan') || (value === 'isFinite') || (value === 'String')) { @@ -1041,6 +1055,15 @@ define([ 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.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] + ')'; + } + break; case ExpressionNodeType.LITERAL_BOOLEAN: return value ? 'true' : 'false'; case ExpressionNodeType.LITERAL_NUMBER: @@ -1061,7 +1084,7 @@ define([ var rgb = args[0]; var alpha = args[1]; if (alpha !== '1.0') { - info.translucent = true; + shaderState.translucent = true; } return 'vec4(' + rgb + ', ' + alpha + ')'; } else { @@ -1071,7 +1094,7 @@ define([ 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') { - info.translucent = true; + shaderState.translucent = true; } return 'vec4(' + args[0] + ' / 255.0, ' + args[1] + ' / 255.0, ' + args[2] + ' / 255.0, ' + args[3] + ')'; } else if (value === 'hsl') { @@ -1085,19 +1108,19 @@ define([ color = convertHSLToRGB(this); if (defined(color)) { if (color.alpha !== 1.0) { - info.translucent = true; + shaderState.translucent = true; } return colorToVec4(color); } else { if (args[3] !== '1.0') { - info.translucent = true; + shaderState.translucent = true; } return 'vec4(czm_HSLToRGB(vec3(' + args[0] + ', ' + args[1] + ', ' + args[2] + ')), ' + args[3] + ')'; } } break; default: - // Not supported: FUNCTION_CALL, ARRAY, REGEX, VARIABLE_IN_STRING, LITERAL_NULL, LITERAL_REGEX, LITERAL_UNDEFINED + // Not supported: FUNCTION_CALL, REGEX, VARIABLE_IN_STRING, LITERAL_NULL, LITERAL_REGEX, LITERAL_UNDEFINED return undefined; } }; diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 9ab20054f425..e4b239368e22 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -786,12 +786,12 @@ define([ } if (hasStyle) { - var info = { + var shaderState = { translucent : false }; - colorStyleFunction = style.getColorShaderFunction('getColorFromStyle', 'czm_tiles3d_style_', info); - showStyleFunction = style.getShowShaderFunction('getShowFromStyle', 'czm_tiles3d_style_', info); - styleTranslucent = info.translucent; + colorStyleFunction = style.getColorShaderFunction('getColorFromStyle', 'czm_tiles3d_style_', shaderState); + showStyleFunction = style.getShowShaderFunction('getShowFromStyle', 'czm_tiles3d_style_', shaderState); + styleTranslucent = shaderState.translucent; } content._styleTranslucent = styleTranslucent; From 5cb1088d0dbc8b01d2eb46b289e1ab77150c01b1 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 27 Sep 2016 14:57:59 -0400 Subject: [PATCH 05/27] Change conditions from an object to an array --- Apps/Sandcastle/gallery/3D Tiles.html | 24 ++++++++++----------- Source/Scene/ConditionsExpression.js | 30 +++++++++++++-------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles.html b/Apps/Sandcastle/gallery/3D Tiles.html index e8bda0c717fc..2c0558216d34 100644 --- a/Apps/Sandcastle/gallery/3D Tiles.html +++ b/Apps/Sandcastle/gallery/3D Tiles.html @@ -87,15 +87,15 @@ // "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" : "${Height} >= 0", @@ -410,13 +410,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 : { diff --git a/Source/Scene/ConditionsExpression.js b/Source/Scene/ConditionsExpression.js index 70be56e9a854..986467e1e23d 100644 --- a/Source/Scene/ConditionsExpression.js +++ b/Source/Scene/ConditionsExpression.js @@ -79,22 +79,22 @@ define([ var runtimeConditions = []; var conditions = expression._conditions; var exp = expression._expression; - for (var cond in conditions) { - if (conditions.hasOwnProperty(cond)) { - cond = String(cond); - var condExpression = String(conditions[cond]); - 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(condExpression) - )); + 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(condExpression) + )); } expression._runtimeConditions = runtimeConditions; From 758ebb200ee9f644c14f976f76b4a917225e9512 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 27 Sep 2016 15:47:00 -0400 Subject: [PATCH 06/27] Change how result params are used --- Source/Scene/Expression.js | 202 ++++++++++++++++++++----------------- 1 file changed, 107 insertions(+), 95 deletions(-) diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index d75c4289f592..c0d9dd027df8 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -27,6 +27,18 @@ define([ var scratchColor = new Color(); + var scratchColorIndex = 0; + var scratchColors = [new Color()]; + + function getScratchColor() { + if (scratchColorIndex >= scratchColors.length) { + scratchColors.push(new Color()); + } + var scratchColor = scratchColors[scratchColorIndex]; + ++scratchColorIndex; + return scratchColor; + } + /** * Evaluates an expression defined using the * {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language}. @@ -116,7 +128,9 @@ 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); + scratchColorIndex = 0; + var color = this._runtimeAst.evaluate(feature); + return Color.clone(color, result); }; /** @@ -574,10 +588,8 @@ define([ return this._value; }; - Node.prototype._evaluateLiteralColor = function(feature, result) { - if (!defined(result)) { - result = new Color(); - } + Node.prototype._evaluateLiteralColor = function(feature) { + var result = getScratchColor(); var args = this._left; if (this._value === 'color') { if (!defined(args)) { @@ -649,32 +661,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 < this._value.length; i++) { - array[i] = this._value[i].evaluate(feature, result); + array[i] = this._value[i].evaluate(feature); } return array; }; @@ -682,44 +694,44 @@ define([ // PERFORMANCE_IDEA: Have "fast path" functions that deal only with specific types // that we can assign if we know the types before runtime - Node.prototype._evaluateNot = function(feature, result) { - return !(this._left.evaluate(feature, result)); + Node.prototype._evaluateNot = function(feature) { + return !(this._left.evaluate(feature)); }; - Node.prototype._evaluateNegative = function(feature, result) { - return -(this._left.evaluate(feature, result)); + Node.prototype._evaluateNegative = function(feature) { + return -(this._left.evaluate(feature)); }; - Node.prototype._evaluatePositive = function(feature, result) { - return +(this._left.evaluate(feature, result)); + Node.prototype._evaluatePositive = function(feature) { + return +(this._left.evaluate(feature)); }; - Node.prototype._evaluateLessThan = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateLessThan = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); return left < right; }; - Node.prototype._evaluateLessThanOrEquals = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateLessThanOrEquals = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); return left <= right; }; - Node.prototype._evaluateGreaterThan = function(feature, result) { - var left = this._left.evaluate(feature, result); - var right = this._right.evaluate(feature, result); + Node.prototype._evaluateGreaterThan = function(feature) { + var left = this._left.evaluate(feature); + var right = this._right.evaluate(feature); return left > 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.'); @@ -731,7 +743,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.'); @@ -740,8 +752,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.'); @@ -753,7 +765,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.'); @@ -762,108 +774,108 @@ 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, getScratchColor()); } 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, getScratchColor()); } 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, getScratchColor()); } else if ((right instanceof Color) && (typeof(left) === 'number')) { - return Color.multiplyByScalar(right, left, scratchColor); + return Color.multiplyByScalar(right, left, getScratchColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { - return Color.multiplyByScalar(left, right, scratchColor); + return Color.multiplyByScalar(left, right, getScratchColor()); } 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, getScratchColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { - return Color.divideByScalar(left, right, scratchColor); + return Color.divideByScalar(left, right, getScratchColor()); } 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, getScratchColor()); } 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._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); } 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._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); } return left !== right; }; - Node.prototype._evaluateConditional = function(feature, result) { - if (this._test.evaluate(feature, result)) { - return this._left.evaluate(feature, result); + 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; @@ -877,13 +889,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) { @@ -893,9 +905,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) { @@ -905,16 +917,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); } From 9d2838e339cc755433469bfbee789905df2b48ab Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 27 Sep 2016 18:05:52 -0400 Subject: [PATCH 07/27] Stricter type comparisons --- Source/Scene/Expression.js | 117 ++++++++++++++++++++++- Specs/Scene/ExpressionSpec.js | 171 ++++++++++++++++++---------------- 2 files changed, 202 insertions(+), 86 deletions(-) diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index c0d9dd027df8..40c18c8a2e38 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -695,38 +695,90 @@ define([ // that we can assign if we know the types before runtime Node.prototype._evaluateNot = function(feature) { - return !(this._left.evaluate(feature)); + var left = this._left.evaluate(feature); + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'boolean') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + + return !left; }; Node.prototype._evaluateNegative = function(feature) { - return -(this._left.evaluate(feature)); + var left = this._left.evaluate(feature); + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + + return -left; }; Node.prototype._evaluatePositive = function(feature) { - return +(this._left.evaluate(feature)); + var left = this._left.evaluate(feature); + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + + return left; }; Node.prototype._evaluateLessThan = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number' || typeof(right) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + return left < right; }; Node.prototype._evaluateLessThanOrEquals = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number' || typeof(right) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + return left <= right; }; Node.prototype._evaluateGreaterThan = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number' || typeof(right) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + return left > right; }; Node.prototype._evaluateGreaterThanOrEquals = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number' || typeof(right) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + return left >= right; }; @@ -777,18 +829,36 @@ define([ 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, getScratchColor()); } + + //>>includeStart('debug', pragmas.debug); + var leftType = typeof(left); + var rightType = typeof(right); + if (leftType !== rightType || (leftType !== 'number' && leftType !== 'string')) { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + return left + right; }; 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, getScratchColor()); } + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number' || typeof(right) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + return left - right; }; @@ -802,6 +872,13 @@ define([ } else if ((left instanceof Color) && (typeof(right) === 'number')) { return Color.multiplyByScalar(left, right, getScratchColor()); } + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number' || typeof(right) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + return left * right; }; @@ -813,6 +890,13 @@ define([ } else if ((left instanceof Color) && (typeof(right) === 'number')) { return Color.divideByScalar(left, right, getScratchColor()); } + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number' || typeof(right) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + return left / right; }; @@ -822,6 +906,13 @@ define([ if ((right instanceof Color) && (left instanceof Color)) { return Color.mod(left, right, getScratchColor()); } + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number' || typeof(right) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + return left % right; }; @@ -851,11 +942,27 @@ define([ }; Node.prototype._evaluateNaN = function(feature) { - return isNaN(this._left.evaluate(feature)); + var left = this._left.evaluate(feature); + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + + return isNaN(left); }; Node.prototype._evaluateIsFinite = function(feature) { - return isFinite(this._left.evaluate(feature)); + var left = this._left.evaluate(feature); + + //>>includeStart('debug', pragmas.debug); + if (typeof(left) !== 'number') { + throw new DeveloperError('Error: Operation is undefined.'); + } + //>>includeEnd('debug'); + + return isFinite(left); }; Node.prototype._evaluateBooleanConversion = function(feature) { diff --git a/Specs/Scene/ExpressionSpec.js b/Specs/Scene/ExpressionSpec.js index 15159c631546..c88fa019d86b 100644 --- a/Specs/Scene/ExpressionSpec.js +++ b/Specs/Scene/ExpressionSpec.js @@ -476,6 +476,11 @@ defineSuite([ expression = new Expression('!!true'); expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('!"true"'); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates unary negative', function() { @@ -484,6 +489,11 @@ defineSuite([ expression = new Expression('-(-5)'); expect(expression.evaluate(undefined)).toEqual(5); + + expression = new Expression('-"5"'); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates unary positive', function() { @@ -491,13 +501,9 @@ defineSuite([ expect(expression.evaluate(undefined)).toEqual(5); expression = new Expression('+"5"'); - expect(expression.evaluate(undefined)).toEqual(5); - - expression = new Expression('+true'); - expect(expression.evaluate(undefined)).toEqual(1); - - expression = new Expression('+null'); - expect(expression.evaluate(undefined)).toEqual(0); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates binary addition', function() { @@ -506,6 +512,11 @@ defineSuite([ expression = new Expression('1 + 2 + 3 + 4'); expect(expression.evaluate(undefined)).toEqual(10); + + expression = new Expression('1 + "2"'); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates binary subtraction', function() { @@ -514,6 +525,11 @@ defineSuite([ expression = new Expression('4 - 3 - 2 - 1'); expect(expression.evaluate(undefined)).toEqual(-2); + + expression = new Expression('1 - "2"'); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates binary multiplication', function() { @@ -522,6 +538,11 @@ defineSuite([ expression = new Expression('1 * 2 * 3 * 4'); expect(expression.evaluate(undefined)).toEqual(24); + + expression = new Expression('1 * "2"'); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates binary division', function() { @@ -533,6 +554,11 @@ defineSuite([ expression = new Expression('24 / -4 / 2'); expect(expression.evaluate(undefined)).toEqual(-3); + + expression = new Expression('1 / "2"'); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates binary modulus', function() { @@ -541,6 +567,11 @@ defineSuite([ expression = new Expression('6 % 4 % 3'); expect(expression.evaluate(undefined)).toEqual(2); + + expression = new Expression('1 % "2"'); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates binary equals', function() { @@ -552,6 +583,12 @@ defineSuite([ expression = new Expression('false === true === false'); expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('undefined === null'); + expect(expression.evaluate(undefined)).toEqual(false); + + expression = new Expression('1 === "1"'); + expect(expression.evaluate(undefined)).toEqual(false); }); it('evaluates binary not equals', function() { @@ -563,6 +600,12 @@ defineSuite([ expression = new Expression('false !== true !== false'); expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('undefined !== null'); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('1 !== "1"'); + expect(expression.evaluate(undefined)).toEqual(true); }); it('evaluates binary less than', function() { @@ -575,11 +618,10 @@ defineSuite([ expression = new Expression('3 < 2'); expect(expression.evaluate(undefined)).toEqual(false); - expression = new Expression('true < false'); - expect(expression.evaluate(undefined)).toEqual(false); - expression = new Expression('color(\'blue\') < 10'); - expect(expression.evaluate(undefined)).toEqual(false); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates binary less than or equals', function() { @@ -592,11 +634,10 @@ defineSuite([ expression = new Expression('3 <= 2'); expect(expression.evaluate(undefined)).toEqual(false); - expression = new Expression('true <= false'); - expect(expression.evaluate(undefined)).toEqual(false); - expression = new Expression('color(\'blue\') <= 10'); - expect(expression.evaluate(undefined)).toEqual(false); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates binary greater than', function() { @@ -609,11 +650,10 @@ defineSuite([ expression = new Expression('3 > 2'); expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('true > false'); - expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('color(\'blue\') > 10'); - expect(expression.evaluate(undefined)).toEqual(false); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates binary greater than or equals', function() { @@ -626,11 +666,10 @@ defineSuite([ expression = new Expression('3 >= 2'); expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('true >= false'); - expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('color(\'blue\') >= 10'); - expect(expression.evaluate(undefined)).toEqual(false); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates logical and', function() { @@ -643,18 +682,6 @@ defineSuite([ expression = new Expression('true && true'); expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('2 && color(\'red\')'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); - }); - - it('throws with invalid and operands', function() { - var expression = new Expression('2 && true'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); - expression = new Expression('true && color(\'red\')'); expect(function() { expression.evaluate(undefined); @@ -670,15 +697,16 @@ defineSuite([ expression = new Expression('true || true'); expect(expression.evaluate(undefined)).toEqual(true); - }); - it('throws with invalid or operands', function() { - var expression = new Expression('2 || false'); + expression = new Expression('true || color(\'red\')'); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('false || color(\'red\')'); expect(function() { expression.evaluate(undefined); }).toThrowDeveloperError(); - expression = new Expression('false || color(\'red\')'); + expression = new Expression('2 || false'); expect(function() { expression.evaluate(undefined); }).toThrowDeveloperError(); @@ -715,9 +743,6 @@ defineSuite([ expression = new Expression('color() === color()'); expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('!!color() === true'); - expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('color(\'green\') !== color(\'green\')'); expect(expression.evaluate(undefined)).toEqual(false); }); @@ -737,10 +762,7 @@ defineSuite([ }); it('evaluates isNaN function', function() { - var expression = new Expression('isNaN()'); - expect(expression.evaluate(undefined)).toEqual(true); - - expression = new Expression('isNaN(NaN)'); + var expression = new Expression('isNaN(NaN)'); expect(expression.evaluate(undefined)).toEqual(true); expression = new Expression('isNaN(1)'); @@ -750,23 +772,13 @@ defineSuite([ expect(expression.evaluate(undefined)).toEqual(false); expression = new Expression('isNaN(null)'); - expect(expression.evaluate(undefined)).toEqual(false); - - expression = new Expression('isNaN(true)'); - expect(expression.evaluate(undefined)).toEqual(false); - - expression = new Expression('isNaN("hello")'); - expect(expression.evaluate(undefined)).toEqual(true); - - expression = new Expression('isNaN(color("white"))'); - expect(expression.evaluate(undefined)).toEqual(true); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates isFinite function', function() { - var expression = new Expression('isFinite()'); - expect(expression.evaluate(undefined)).toEqual(false); - - expression = new Expression('isFinite(NaN)'); + var expression = new Expression('isFinite(NaN)'); expect(expression.evaluate(undefined)).toEqual(false); expression = new Expression('isFinite(1)'); @@ -776,16 +788,9 @@ defineSuite([ expect(expression.evaluate(undefined)).toEqual(false); expression = new Expression('isFinite(null)'); - expect(expression.evaluate(undefined)).toEqual(true); - - expression = new Expression('isFinite(true)'); - expect(expression.evaluate(undefined)).toEqual(true); - - expression = new Expression('isFinite("hello")'); - expect(expression.evaluate(undefined)).toEqual(false); - - expression = new Expression('isFinite(color("white"))'); - expect(expression.evaluate(undefined)).toEqual(false); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); }); it('evaluates ternary conditional', function() { @@ -1163,11 +1168,11 @@ defineSuite([ feature.addProperty('property', 'value'); feature.addProperty('array', [Color.GREEN, Color.PURPLE, Color.YELLOW]); feature.addProperty('complicatedArray', [{ - 'subproperty' : Color.ORANGE, - 'anotherproperty' : Color.RED - }, { - 'subproperty' : Color.BLUE, - 'anotherproperty' : Color.WHITE + 'subproperty' : Color.ORANGE, + 'anotherproperty' : Color.RED + }, { + 'subproperty' : Color.BLUE, + 'anotherproperty' : Color.WHITE }]); feature.addProperty('temperatures', { "scale" : "fahrenheit", @@ -1180,12 +1185,6 @@ defineSuite([ expression = new Expression('[1+2, "hello", 2 < 3, color("blue"), ${property}]'); expect(expression.evaluate(feature)).toEqual([3, 'hello', true, Color.BLUE, 'value']); - expression = new Expression('[1, 2, 3] * 4'); - expect(expression.evaluate(undefined)).toEqual(NaN); - - expression = new Expression('-[1, 2, 3]'); - expect(expression.evaluate(undefined)).toEqual(NaN); - expression = new Expression('${array[1]}'); expect(expression.evaluate(feature)).toEqual(Color.PURPLE); @@ -1203,5 +1202,15 @@ defineSuite([ expression = new Expression('${temperatures["values"][0]}'); expect(expression.evaluate(feature)).toEqual(70); + + expression = new Expression('[1, 2, 3] * 4'); + expect(function() { + return expression.evaluate(feature); + }).toThrowDeveloperError(); + + expression = new Expression('-[1, 2, 3]'); + expect(function() { + return expression.evaluate(feature); + }).toThrowDeveloperError(); }); }); From 096b15bd88a6a46b717d38cd57759bff57db5fec Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 28 Sep 2016 11:22:42 -0400 Subject: [PATCH 08/27] Reset scratch index for Expression.evaluate --- Source/Scene/Expression.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index 40c18c8a2e38..1867181bce2c 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -117,6 +117,7 @@ define([ * @returns {Boolean|Number|String|Color|RegExp} The result of evaluating the expression. */ Expression.prototype.evaluate = function(feature) { + scratchColorIndex = 0; return this._runtimeAst.evaluate(feature); }; From 976f32ff6f42ac2759939759b94e0ee71289b07c Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 28 Sep 2016 12:08:51 -0400 Subject: [PATCH 09/27] Fix tests --- Source/Scene/Cesium3DTileStyle.js | 10 +-- Source/Scene/Cesium3DTileset.js | 10 +-- Source/Scene/ConditionsExpression.js | 44 ++++++------ Source/Scene/Expression.js | 6 +- Specs/Scene/Cesium3DTileStyleSpec.js | 50 ++++++------- Specs/Scene/Cesium3DTilesetSpec.js | 16 ++--- Specs/Scene/ConditionsExpressionSpec.js | 94 ++++++++++++------------- 7 files changed, 118 insertions(+), 112 deletions(-) diff --git a/Source/Scene/Cesium3DTileStyle.js b/Source/Scene/Cesium3DTileStyle.js index ebe58488f23a..c4987a1fff86 100644 --- a/Source/Scene/Cesium3DTileStyle.js +++ b/Source/Scene/Cesium3DTileStyle.js @@ -40,11 +40,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/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index e80c8f1d8a8a..fcc16a681918 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -500,11 +500,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 986467e1e23d..c8c6138dfb90 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,23 +78,25 @@ define([ function setRuntime(expression) { var runtimeConditions = []; var conditions = expression._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'); + 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(condExpression) + )); } - runtimeConditions.push(new Statement( - new Expression(cond), - new Expression(condExpression) - )); } expression._runtimeConditions = runtimeConditions; diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index 1867181bce2c..9ba8dc1607c4 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -118,7 +118,11 @@ define([ */ Expression.prototype.evaluate = function(feature) { scratchColorIndex = 0; - return this._runtimeAst.evaluate(feature); + var result = this._runtimeAst.evaluate(feature); + if (result instanceof Color) { + return Color.clone(result); + } + return result; }; /** diff --git a/Specs/Scene/Cesium3DTileStyleSpec.js b/Specs/Scene/Cesium3DTileStyleSpec.js index 3509c71f332c..34c479572ac5 100644 --- a/Specs/Scene/Cesium3DTileStyleSpec.js +++ b/Specs/Scene/Cesium3DTileStyleSpec.js @@ -148,10 +148,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({ @@ -295,11 +295,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 +311,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,14 +329,14 @@ 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); diff --git a/Specs/Scene/Cesium3DTilesetSpec.js b/Specs/Scene/Cesium3DTilesetSpec.js index 64986b4f83c4..974047751629 100644 --- a/Specs/Scene/Cesium3DTilesetSpec.js +++ b/Specs/Scene/Cesium3DTilesetSpec.js @@ -1177,10 +1177,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(); @@ -1192,10 +1192,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..e7100876dccd 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("green")'] + ] }; 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("green")'] + ] }; 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("green")'] + ] }; var jsonExpWithUndefinedExpression = { - conditions : { - '${expression} === undefined' : 'color("blue")', - 'true' : 'color("green")' - } + conditions : [ + ['${expression} === undefined', 'color("blue")'], + ['true', 'color("green")'] + ] }; @@ -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("green")'] + ]); }); 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("green")'] + ]); }); 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.GREEN); }); 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.GREEN); + expect(expression.evaluate(new MockFeature(3))).toEqual(Color.GREEN); }); 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.GREEN); }); it('evaluates undefined conditional expression', function() { From 1fd4b27e7c2fcee4988f89f74ce4c2a4b576e2a9 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 28 Sep 2016 13:07:49 -0400 Subject: [PATCH 10/27] Calculate literal colors right away --- Source/Scene/Expression.js | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index 9ba8dc1607c4..9c5a76fdbee7 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -1066,6 +1066,24 @@ define([ 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 @@ -1215,12 +1233,22 @@ define([ return 'vec4(' + args[0] + ', 1.0)'; } } else if (value === 'rgb') { - return 'vec4(' + args[0] + ' / 255.0, ' + args[1] + ' / 255.0, ' + args[2] + ' / 255.0, 1.0)'; + 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; } - return 'vec4(' + args[0] + ' / 255.0, ' + args[1] + ' / 255.0, ' + args[2] + ' / 255.0, ' + args[3] + ')'; + 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)) { From dbd681d05ba92f1b9bc4427ffa5fd9dbf1f89fbc Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 28 Sep 2016 13:44:57 -0400 Subject: [PATCH 11/27] Limit array length --- Source/Scene/Expression.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index 9c5a76fdbee7..eda80f600f65 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -1198,6 +1198,11 @@ define([ // This is intended for accessing the components of vec2, vec3, and vec4 properties. String members aren't supported. return left + '[int(' + right + ')]'; case ExpressionNodeType.ARRAY: + //>>includeStart('debug', pragmas.debug); + if (value.length < 2 || value.length > 4) { + throw new DeveloperError('Invalid array length. Array length should be between 2 and 4'); + } + //>>includeEnd('debug'); if (value.length === 4) { return 'vec4(' + value[0] + ', ' + value[1] + ', ' + value[2] + ', ' + value[3] + ')'; } else if (value.length === 3) { From 75beb232afe29b723b25e336811c785941c8ad0e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 28 Sep 2016 15:04:15 -0400 Subject: [PATCH 12/27] Added Point Cloud Styling sandcastle --- .../gallery/3D Tiles Point Cloud Styling.html | 153 ++++++++++++++++++ .../gallery/3D Tiles Point Cloud Styling.jpg | Bin 0 -> 10361 bytes Apps/Sandcastle/gallery/3D Tiles.jpg | Bin 0 -> 11623 bytes 3 files changed, 153 insertions(+) create mode 100644 Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html create mode 100644 Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.jpg create mode 100644 Apps/Sandcastle/gallery/3D Tiles.jpg 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..da5194f0bd08 --- /dev/null +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html @@ -0,0 +1,153 @@ + + + + + + + + + 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 0000000000000000000000000000000000000000..11512b80b98d10915d6ec86d85485f470f5c3a94 GIT binary patch literal 10361 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 Date: Wed, 28 Sep 2016 15:36:09 -0400 Subject: [PATCH 13/27] Add back TODO --- Source/Scene/PointCloud3DTileContent.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index e4b239368e22..e45d010ff965 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -487,6 +487,7 @@ define([ for (var name in styleableProperties) { if (styleableProperties.hasOwnProperty(name)) { + // TODO : this will not handle matrix types currently var property = styleableProperties[name]; var typedArray = property.typedArray; var componentCount = property.componentCount; From 88903d5694054c4b531bee9ae51f1ea076769c4e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 29 Sep 2016 14:55:23 -0400 Subject: [PATCH 14/27] More detailed shader error messages, removed type-checking errors in evaluate, added ScratchStorage class --- Source/Scene/Cesium3DTileStyle.js | 10 -- Source/Scene/Cesium3DTileset.js | 2 +- Source/Scene/Expression.js | 211 ++++++++++-------------------- 3 files changed, 68 insertions(+), 155 deletions(-) diff --git a/Source/Scene/Cesium3DTileStyle.js b/Source/Scene/Cesium3DTileStyle.js index c4987a1fff86..513b466562e9 100644 --- a/Source/Scene/Cesium3DTileStyle.js +++ b/Source/Scene/Cesium3DTileStyle.js @@ -334,11 +334,6 @@ define([ this._colorShaderFunctionReady = true; this._colorShaderFunction = this.color.getShaderFunction(functionName, attributePrefix, shaderState, 'vec4'); - //>>includeStart('debug', pragmas.debug); - if (!defined(this._colorShaderFunction)) { - throw new DeveloperError('Could not generate valid shader code for the color style.'); - } - //>>includeEnd('debug'); return this._colorShaderFunction; }; @@ -361,11 +356,6 @@ define([ this._showShaderFunctionReady = true; this._showShaderFunction = this.show.getShaderFunction(functionName, attributePrefix, shaderState, 'bool'); - //>>includeStart('debug', pragmas.debug); - if (!defined(this._showShaderFunction)) { - throw new DeveloperError('Could not generate valid shader code for the show style.'); - } - //>>includeEnd('debug'); return this._showShaderFunction; }; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index fcc16a681918..25398f6fd33a 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -503,7 +503,7 @@ define([ * conditions : [ * ['${Height} >= 100', 'color("purple", 0.5)'], * ['${Height} >= 50', 'color("red")'], - * ['true' : 'color("blue")'] + * ['true', 'color("blue")'] * ] * }, * show : '${Height} > 0', diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index eda80f600f65..fe57e6a000c1 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -27,17 +27,21 @@ define([ var scratchColor = new Color(); - var scratchColorIndex = 0; - var scratchColors = [new Color()]; - - function getScratchColor() { - if (scratchColorIndex >= scratchColors.length) { - scratchColors.push(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; } - var scratchColor = scratchColors[scratchColorIndex]; - ++scratchColorIndex; - return scratchColor; - } + }; /** * Evaluates an expression defined using the @@ -117,7 +121,7 @@ define([ * @returns {Boolean|Number|String|Color|RegExp} The result of evaluating the expression. */ Expression.prototype.evaluate = function(feature) { - scratchColorIndex = 0; + ScratchStorage.reset(); var result = this._runtimeAst.evaluate(feature); if (result instanceof Color) { return Color.clone(result); @@ -133,7 +137,7 @@ define([ * @returns {Color} The modified result parameter or a new Color instance if one was not provided. */ Expression.prototype.evaluateColor = function(feature, result) { - scratchColorIndex = 0; + ScratchStorage.reset(); var color = this._runtimeAst.evaluate(feature); return Color.clone(color, result); }; @@ -594,7 +598,7 @@ define([ }; Node.prototype._evaluateLiteralColor = function(feature) { - var result = getScratchColor(); + var result = ScratchStorage.getColor(); var args = this._left; if (this._value === 'color') { if (!defined(args)) { @@ -700,90 +704,38 @@ define([ // that we can assign if we know the types before runtime Node.prototype._evaluateNot = function(feature) { - var left = this._left.evaluate(feature); - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'boolean') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - - return !left; + return !(this._left.evaluate(feature)); }; Node.prototype._evaluateNegative = function(feature) { - var left = this._left.evaluate(feature); - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - - return -left; + return -(this._left.evaluate(feature)); }; Node.prototype._evaluatePositive = function(feature) { - var left = this._left.evaluate(feature); - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - - return left; + return +(this._left.evaluate(feature)); }; Node.prototype._evaluateLessThan = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number' || typeof(right) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - return left < right; }; Node.prototype._evaluateLessThanOrEquals = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number' || typeof(right) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - return left <= right; }; Node.prototype._evaluateGreaterThan = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number' || typeof(right) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - return left > right; }; Node.prototype._evaluateGreaterThanOrEquals = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number' || typeof(right) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - return left >= right; }; @@ -834,36 +786,18 @@ define([ 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, getScratchColor()); + return Color.add(left, right, ScratchStorage.getColor()); } - - //>>includeStart('debug', pragmas.debug); - var leftType = typeof(left); - var rightType = typeof(right); - if (leftType !== rightType || (leftType !== 'number' && leftType !== 'string')) { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - return left + right; }; 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, getScratchColor()); + return Color.subtract(left, right, ScratchStorage.getColor()); } - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number' || typeof(right) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - return left - right; }; @@ -871,19 +805,12 @@ define([ var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { - return Color.multiply(left, right, getScratchColor()); + return Color.multiply(left, right, ScratchStorage.getColor()); } else if ((right instanceof Color) && (typeof(left) === 'number')) { - return Color.multiplyByScalar(right, left, getScratchColor()); + return Color.multiplyByScalar(right, left, ScratchStorage.getColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { - return Color.multiplyByScalar(left, right, getScratchColor()); - } - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number' || typeof(right) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); + return Color.multiplyByScalar(left, right, ScratchStorage.getColor()); } - //>>includeEnd('debug'); - return left * right; }; @@ -891,17 +818,10 @@ define([ var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { - return Color.divide(left, right, getScratchColor()); + return Color.divide(left, right, ScratchStorage.getColor()); } else if ((left instanceof Color) && (typeof(right) === 'number')) { - return Color.divideByScalar(left, right, getScratchColor()); + return Color.divideByScalar(left, right, ScratchStorage.getColor()); } - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number' || typeof(right) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - return left / right; }; @@ -909,15 +829,8 @@ define([ var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { - return Color.mod(left, right, getScratchColor()); - } - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number' || typeof(right) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); + return Color.mod(left, right, ScratchStorage.getColor()); } - //>>includeEnd('debug'); - return left % right; }; @@ -947,27 +860,11 @@ define([ }; Node.prototype._evaluateNaN = function(feature) { - var left = this._left.evaluate(feature); - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - - return isNaN(left); + return isNaN(this._left.evaluate(feature)); }; Node.prototype._evaluateIsFinite = function(feature) { - var left = this._left.evaluate(feature); - - //>>includeStart('debug', pragmas.debug); - if (typeof(left) !== 'number') { - throw new DeveloperError('Error: Operation is undefined.'); - } - //>>includeEnd('debug'); - - return isFinite(left); + return isFinite(this._left.evaluate(feature)); }; Node.prototype._evaluateBooleanConversion = function(feature) { @@ -1174,13 +1071,16 @@ define([ return attributePrefix + value; case ExpressionNodeType.UNARY: // Supported types: +, -, !, Boolean, Number - if ((value === 'isNan') || (value === 'isFinite') || (value === 'String')) { - return undefined; - } else if (value === 'Boolean') { + 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: ||, &&, ===, !==, <, >, <=, >=, +, -, *, /, % @@ -1197,12 +1097,11 @@ define([ 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.ARRAY: + case ExpressionNodeType.FUNCTION_CALL: //>>includeStart('debug', pragmas.debug); - if (value.length < 2 || value.length > 4) { - throw new DeveloperError('Invalid array length. Array length should be between 2 and 4'); - } + 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) { @@ -1210,7 +1109,24 @@ define([ } 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: @@ -1221,7 +1137,9 @@ define([ if (defined(color)) { return colorToVec3(color); } - return undefined; + //>>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') { @@ -1276,9 +1194,14 @@ define([ } } break; - default: - // Not supported: FUNCTION_CALL, REGEX, VARIABLE_IN_STRING, LITERAL_NULL, LITERAL_REGEX, LITERAL_UNDEFINED - return undefined; + 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'); } }; From 62ecd5002b7a5670057981016200e166780c0646 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 29 Sep 2016 16:58:22 -0400 Subject: [PATCH 15/27] Revert ExpressionSpec --- Specs/Scene/ExpressionSpec.js | 171 ++++++++++++++++------------------ 1 file changed, 81 insertions(+), 90 deletions(-) diff --git a/Specs/Scene/ExpressionSpec.js b/Specs/Scene/ExpressionSpec.js index c88fa019d86b..15159c631546 100644 --- a/Specs/Scene/ExpressionSpec.js +++ b/Specs/Scene/ExpressionSpec.js @@ -476,11 +476,6 @@ defineSuite([ expression = new Expression('!!true'); expect(expression.evaluate(undefined)).toEqual(true); - - expression = new Expression('!"true"'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); }); it('evaluates unary negative', function() { @@ -489,11 +484,6 @@ defineSuite([ expression = new Expression('-(-5)'); expect(expression.evaluate(undefined)).toEqual(5); - - expression = new Expression('-"5"'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); }); it('evaluates unary positive', function() { @@ -501,9 +491,13 @@ defineSuite([ expect(expression.evaluate(undefined)).toEqual(5); expression = new Expression('+"5"'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); + expect(expression.evaluate(undefined)).toEqual(5); + + expression = new Expression('+true'); + expect(expression.evaluate(undefined)).toEqual(1); + + expression = new Expression('+null'); + expect(expression.evaluate(undefined)).toEqual(0); }); it('evaluates binary addition', function() { @@ -512,11 +506,6 @@ defineSuite([ expression = new Expression('1 + 2 + 3 + 4'); expect(expression.evaluate(undefined)).toEqual(10); - - expression = new Expression('1 + "2"'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); }); it('evaluates binary subtraction', function() { @@ -525,11 +514,6 @@ defineSuite([ expression = new Expression('4 - 3 - 2 - 1'); expect(expression.evaluate(undefined)).toEqual(-2); - - expression = new Expression('1 - "2"'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); }); it('evaluates binary multiplication', function() { @@ -538,11 +522,6 @@ defineSuite([ expression = new Expression('1 * 2 * 3 * 4'); expect(expression.evaluate(undefined)).toEqual(24); - - expression = new Expression('1 * "2"'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); }); it('evaluates binary division', function() { @@ -554,11 +533,6 @@ defineSuite([ expression = new Expression('24 / -4 / 2'); expect(expression.evaluate(undefined)).toEqual(-3); - - expression = new Expression('1 / "2"'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); }); it('evaluates binary modulus', function() { @@ -567,11 +541,6 @@ defineSuite([ expression = new Expression('6 % 4 % 3'); expect(expression.evaluate(undefined)).toEqual(2); - - expression = new Expression('1 % "2"'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); }); it('evaluates binary equals', function() { @@ -583,12 +552,6 @@ defineSuite([ expression = new Expression('false === true === false'); expect(expression.evaluate(undefined)).toEqual(true); - - expression = new Expression('undefined === null'); - expect(expression.evaluate(undefined)).toEqual(false); - - expression = new Expression('1 === "1"'); - expect(expression.evaluate(undefined)).toEqual(false); }); it('evaluates binary not equals', function() { @@ -600,12 +563,6 @@ defineSuite([ expression = new Expression('false !== true !== false'); expect(expression.evaluate(undefined)).toEqual(true); - - expression = new Expression('undefined !== null'); - expect(expression.evaluate(undefined)).toEqual(true); - - expression = new Expression('1 !== "1"'); - expect(expression.evaluate(undefined)).toEqual(true); }); it('evaluates binary less than', function() { @@ -618,10 +575,11 @@ defineSuite([ expression = new Expression('3 < 2'); expect(expression.evaluate(undefined)).toEqual(false); + expression = new Expression('true < false'); + expect(expression.evaluate(undefined)).toEqual(false); + expression = new Expression('color(\'blue\') < 10'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); + expect(expression.evaluate(undefined)).toEqual(false); }); it('evaluates binary less than or equals', function() { @@ -634,10 +592,11 @@ defineSuite([ expression = new Expression('3 <= 2'); expect(expression.evaluate(undefined)).toEqual(false); + expression = new Expression('true <= false'); + expect(expression.evaluate(undefined)).toEqual(false); + expression = new Expression('color(\'blue\') <= 10'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); + expect(expression.evaluate(undefined)).toEqual(false); }); it('evaluates binary greater than', function() { @@ -650,10 +609,11 @@ defineSuite([ expression = new Expression('3 > 2'); expect(expression.evaluate(undefined)).toEqual(true); + expression = new Expression('true > false'); + expect(expression.evaluate(undefined)).toEqual(true); + expression = new Expression('color(\'blue\') > 10'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); + expect(expression.evaluate(undefined)).toEqual(false); }); it('evaluates binary greater than or equals', function() { @@ -666,10 +626,11 @@ defineSuite([ expression = new Expression('3 >= 2'); expect(expression.evaluate(undefined)).toEqual(true); + expression = new Expression('true >= false'); + expect(expression.evaluate(undefined)).toEqual(true); + expression = new Expression('color(\'blue\') >= 10'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); + expect(expression.evaluate(undefined)).toEqual(false); }); it('evaluates logical and', function() { @@ -682,6 +643,18 @@ defineSuite([ expression = new Expression('true && true'); expect(expression.evaluate(undefined)).toEqual(true); + expression = new Expression('2 && color(\'red\')'); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); + }); + + it('throws with invalid and operands', function() { + var expression = new Expression('2 && true'); + expect(function() { + expression.evaluate(undefined); + }).toThrowDeveloperError(); + expression = new Expression('true && color(\'red\')'); expect(function() { expression.evaluate(undefined); @@ -697,16 +670,15 @@ defineSuite([ expression = new Expression('true || true'); expect(expression.evaluate(undefined)).toEqual(true); + }); - expression = new Expression('true || color(\'red\')'); - expect(expression.evaluate(undefined)).toEqual(true); - - expression = new Expression('false || color(\'red\')'); + it('throws with invalid or operands', function() { + var expression = new Expression('2 || false'); expect(function() { expression.evaluate(undefined); }).toThrowDeveloperError(); - expression = new Expression('2 || false'); + expression = new Expression('false || color(\'red\')'); expect(function() { expression.evaluate(undefined); }).toThrowDeveloperError(); @@ -743,6 +715,9 @@ defineSuite([ expression = new Expression('color() === color()'); expect(expression.evaluate(undefined)).toEqual(true); + expression = new Expression('!!color() === true'); + expect(expression.evaluate(undefined)).toEqual(true); + expression = new Expression('color(\'green\') !== color(\'green\')'); expect(expression.evaluate(undefined)).toEqual(false); }); @@ -762,7 +737,10 @@ defineSuite([ }); it('evaluates isNaN function', function() { - var expression = new Expression('isNaN(NaN)'); + var expression = new Expression('isNaN()'); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('isNaN(NaN)'); expect(expression.evaluate(undefined)).toEqual(true); expression = new Expression('isNaN(1)'); @@ -772,13 +750,23 @@ defineSuite([ expect(expression.evaluate(undefined)).toEqual(false); expression = new Expression('isNaN(null)'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); + expect(expression.evaluate(undefined)).toEqual(false); + + expression = new Expression('isNaN(true)'); + expect(expression.evaluate(undefined)).toEqual(false); + + expression = new Expression('isNaN("hello")'); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('isNaN(color("white"))'); + expect(expression.evaluate(undefined)).toEqual(true); }); it('evaluates isFinite function', function() { - var expression = new Expression('isFinite(NaN)'); + var expression = new Expression('isFinite()'); + expect(expression.evaluate(undefined)).toEqual(false); + + expression = new Expression('isFinite(NaN)'); expect(expression.evaluate(undefined)).toEqual(false); expression = new Expression('isFinite(1)'); @@ -788,9 +776,16 @@ defineSuite([ expect(expression.evaluate(undefined)).toEqual(false); expression = new Expression('isFinite(null)'); - expect(function() { - expression.evaluate(undefined); - }).toThrowDeveloperError(); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('isFinite(true)'); + expect(expression.evaluate(undefined)).toEqual(true); + + expression = new Expression('isFinite("hello")'); + expect(expression.evaluate(undefined)).toEqual(false); + + expression = new Expression('isFinite(color("white"))'); + expect(expression.evaluate(undefined)).toEqual(false); }); it('evaluates ternary conditional', function() { @@ -1168,11 +1163,11 @@ defineSuite([ feature.addProperty('property', 'value'); feature.addProperty('array', [Color.GREEN, Color.PURPLE, Color.YELLOW]); feature.addProperty('complicatedArray', [{ - 'subproperty' : Color.ORANGE, - 'anotherproperty' : Color.RED - }, { - 'subproperty' : Color.BLUE, - 'anotherproperty' : Color.WHITE + 'subproperty' : Color.ORANGE, + 'anotherproperty' : Color.RED + }, { + 'subproperty' : Color.BLUE, + 'anotherproperty' : Color.WHITE }]); feature.addProperty('temperatures', { "scale" : "fahrenheit", @@ -1185,6 +1180,12 @@ defineSuite([ expression = new Expression('[1+2, "hello", 2 < 3, color("blue"), ${property}]'); expect(expression.evaluate(feature)).toEqual([3, 'hello', true, Color.BLUE, 'value']); + expression = new Expression('[1, 2, 3] * 4'); + expect(expression.evaluate(undefined)).toEqual(NaN); + + expression = new Expression('-[1, 2, 3]'); + expect(expression.evaluate(undefined)).toEqual(NaN); + expression = new Expression('${array[1]}'); expect(expression.evaluate(feature)).toEqual(Color.PURPLE); @@ -1202,15 +1203,5 @@ defineSuite([ expression = new Expression('${temperatures["values"][0]}'); expect(expression.evaluate(feature)).toEqual(70); - - expression = new Expression('[1, 2, 3] * 4'); - expect(function() { - return expression.evaluate(feature); - }).toThrowDeveloperError(); - - expression = new Expression('-[1, 2, 3]'); - expect(function() { - return expression.evaluate(feature); - }).toThrowDeveloperError(); }); }); From 7754b66a192bffcfd69973a57aee97ced70aa7b9 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 29 Sep 2016 17:13:27 -0400 Subject: [PATCH 16/27] Reorganzation of styleContent --- Source/Scene/Cesium3DTileStyleEngine.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Source/Scene/Cesium3DTileStyleEngine.js b/Source/Scene/Cesium3DTileStyleEngine.js index 88b23a01a8d1..def8a89080f5 100644 --- a/Source/Scene/Cesium3DTileStyleEngine.js +++ b/Source/Scene/Cesium3DTileStyleEngine.js @@ -101,15 +101,18 @@ define([ var scratchColor = new Color(); function styleContent(styleEngine, frameState, content, stats) { - var length = content.featuresLength; var style = styleEngine._style; - stats.numberOfFeaturesStyled += length; - - if (content.applyStyleWithShader(frameState, style)) { - return; + if (!content.applyStyleWithShader(frameState, style)) { + applyStyleWithBatchTable(content, stats, style); } + } + + function applyStyleWithBatchTable(content, stats, style) { + var length = content.featuresLength; + stats.numberOfFeaturesStyled += length; + if (!defined(style)) { clearStyle(content); return; From cab37050c38a142a17e22267c5de40b990f1e704 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 30 Sep 2016 11:11:09 -0400 Subject: [PATCH 17/27] Add BATCH_ID comment --- Source/Scene/PointCloud3DTileContent.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index e45d010ff965..4a06df288026 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -398,7 +398,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; @@ -426,7 +426,7 @@ 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); From 464cee85d7516447f10cb4cfa61b3d432aecc928 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 30 Sep 2016 14:37:51 -0400 Subject: [PATCH 18/27] Added ability to style based on POSITION, COLOR, and NORMAL semantics --- .../gallery/3D Tiles Point Cloud Styling.html | 12 +++ Source/Scene/PointCloud3DTileContent.js | 102 ++++++++++++++---- 2 files changed, 92 insertions(+), 22 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html index da5194f0bd08..7e0edf46c5d6 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html @@ -117,6 +117,18 @@ } }); +addStyle('Use point colors', { + color : "${COLOR} * ${temperature} + rgb(128,128,128)" +}); + +addStyle('Use point positions', { + show : "${POSITION}[0] > 0.5 || ${POSITION}[1] > 0.5 || ${POSITION}[2] > 0.5" +}); + +addStyle('Color based on position', { + color : "rgb(${POSITION}[0] * 255, ${POSITION}[1] * 255, ${POSITION}[2] * 255)" +}); + function setStyle(style) { tileset.style = new Cesium.Cesium3DTileStyle(style); } diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 4a06df288026..1585b7141db3 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -735,19 +735,33 @@ 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 (properties.indexOf(name) === -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) { @@ -758,6 +772,20 @@ define([ } } + 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; @@ -802,13 +830,31 @@ define([ // 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); } + var usesColorSemantic = styleableSemantics.indexOf('COLOR') >= 0; + var usesNormalSemantic = styleableSemantics.indexOf('NORMAL') >= 0; + + //>>includeStart('debug', pragmas.debug); + if (usesColorSemantic && !hasColors) { + throw new DeveloperError('Style references the COLOR semantic but the point cloud does not have colors'); + } + 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; if (defined(styleableShaderAttributes)) { @@ -822,16 +868,17 @@ define([ } } - if (hasColorStyle) { - // Disable the color vertex attribute when a color style is used + 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 = false; + colorVertexAttribute.enabled = usesColors; } var attributeLocations = { a_position : positionLocation }; - if (hasColors && !hasColorStyle) { + if (usesColors) { attributeLocations.a_color = colorLocation; } if (hasNormals) { @@ -853,7 +900,7 @@ define([ //>>includeStart('debug', pragmas.debug); if (!defined(attribute)) { - throw new DeveloperError('Style reference a property "' + name + '" that does not exist or is not styleable.'); + throw new DeveloperError('Style references a property "' + name + '" that does not exist or is not styleable.'); } //>>includeEnd('debug'); @@ -870,7 +917,7 @@ define([ attributeLocations[attributeName] = attribute.location; } - if (hasColors && !hasColorStyle) { + if (usesColors) { if (isTranslucent) { vs += 'attribute vec4 a_color; \n'; } else if (isRGB565) { @@ -912,11 +959,9 @@ define([ vs += 'void main() \n' + '{ \n'; - if (hasColorStyle) { - vs += ' vec4 color = getColorFromStyle() * u_highlightColor; \n'; - } else if (hasColors) { + if (usesColors) { if (isTranslucent) { - vs += ' vec4 color = a_color * u_highlightColor; \n'; + vs += ' vec4 color = a_color; \n'; } else if (isRGB565) { vs += ' float compressed = a_color; \n' + ' float r = floor(compressed * SHIFT_RIGHT_11); \n' + @@ -925,12 +970,18 @@ define([ ' 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'; + ' vec4 color = vec4(rgb, 1.0); \n'; } else { - vs += ' vec4 color = vec4(a_color * u_highlightColor.rgb, u_highlightColor.a); \n'; + vs += ' vec4 color = vec4(a_color, 1.0); \n'; } } else { - vs += ' vec4 color = u_highlightColor; \n'; + vs += ' vec4 color = vec4(1.0); \n'; + } + + if (isQuantized) { + vs += ' vec3 position = a_position * u_quantizedVolumeScale; \n'; + } else { + vs += ' vec3 position = a_position; \n'; } if (hasNormals) { @@ -939,19 +990,27 @@ define([ } 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'; + } + + 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'; } - 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'; @@ -962,8 +1021,7 @@ define([ } if (hasShowStyle) { - vs += ' float show = float(getShowFromStyle()); \n' + - ' gl_Position *= show; \n'; + vs += ' gl_Position *= show; \n'; } vs += '} \n'; From 18be9d6b21ef87771bdd143bd79f02b3b23b6daf Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Fri, 30 Sep 2016 15:13:28 -0400 Subject: [PATCH 19/27] Support DOUBLE, INT, and UNSIGNED_INT point cloud style properties --- Source/Scene/PointCloud3DTileContent.js | 18 ++++++++++++------ .../pointCloudWithPerPointProperties.pnts | Bin 35392 -> 35396 bytes 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 1585b7141db3..92ee03e66278 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -430,6 +430,18 @@ define([ 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) { + property.typedArray = new Float32Array(typedArray); + } + } + } } this._parsedContent = { @@ -493,12 +505,6 @@ define([ var componentCount = property.componentCount; var componentDatatype = ComponentDatatype.fromTypedArray(typedArray); - //>>includeStart('debug', pragmas.debug); - if (componentDatatype === ComponentDatatype.UNSIGNED_INT) { - throw new DeveloperError('Property "' + name + '" has a component type of UNSIGNED_INT which is not a valid WebGL vertex attribute type.'); - } - //>>includeEnd('debug'); - var vertexBuffer = Buffer.createVertexBuffer({ context : context, typedArray : property.typedArray, diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/pointCloudWithPerPointProperties.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/pointCloudWithPerPointProperties.pnts index 1e95fa4edd600c65074718003ad19d0dbde389c9..77fb95a57485d9d68941fee449f7fe7a3d518f20 100644 GIT binary patch literal 35396 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�uS^%RoI=Tg7Z*wDm@mY)TMbE{mPtAt?oKXU`y}sYwMb}G zJxgfmGF?7o`d-1ocMy5`DSY(q2%nY<>bug$K-Zt zeesTMJ<)E=O)}qb0eLvQFjA==oqj~j7(_JB zB@lOwJ>=ZcH1cluNV4~2zOZtBf%Abu4#Fg_oz6a|R*-d?N#wz-O~m-y4&r6HfV{DM zB@}O{C;aGJk6iaNAkBVc3)9Neh*#GXa_rwya_?0*(XabgpwCw;LtV-Bm?UDXwUC6s zbLpvLv1E4hFmkZci5MTbFW8lyk~g~TLe|CXB7NtbBI^fiA)ZY{^3h9|+}x={j%a5I z6YjTmK4h#xHnv+zOg(eS<1e}7XL&M7Flt4o`PB;qo?^+R)AM=RC zp>^cXO$9OAWJGT5n=MS6KgX%vvS4BN@j1e`oa6EyMiYb;d0NCLt_3kn$PpTRv2z{~ z*;dGUbd;2B@+Avyz7VSJ>5><3=90TEIb=oHX)^N07V_iC1afn@Kz=P)K&Bb%lH=NL z!ip(vgj16b!2Lp-Bp0?J9U~f%gWWYrT!0l>(xMSDy`L=1@7Gx<`Pfh}YJXLJ$HY@U z$oq!#xXnIt?~lR4`^T+_zw0HTL-$2OoyNx9knnNJLbkHslMuQLmTN8>7; zj#%FozB&FBj80q?+C-faQoF|sE)BLgn+Cn*0+5rzw}m%EPh@k zBuC{4pWn6>rlbrM=jNIw=f0b3}MNIYbEGKgr>3#0kNw zfitnM6Hfa68$sk(_24tD5I($oApaUQKp60{R&F`|h}=Kon$W}DhJ3g;n7p>@LSlNI z6XIPP3&!ilI-5=N5J-c8!s**h<#ubP3mYtI1x54+LI3NTf!65H z^N*fbk39AXz3qkz^8Y$yvZqFOu%1@b2fbdr9sRqd^b`EU8hym$4D2cUJQ3@ei<~gG zvc?U2b|*xmr{`v)r})Rc2+$2k)UI%0kJv|^lR{e%%%H>^46%(nSJ zWi0mmd-@suoz7W{-?5&!c{KcAfcx*qX;T8$LpHdeyDbmIvF>}$;`Y~`t->C;@+A7)+ws_= z)Sr%Hw^;C8`EUcQKXPu2?y=-H&U5V71N2zm1L*T_yutZB*PG$k+@OKj)A{rl^rk}_ zVbAaRFR}h_?Ms~J^Id1`ac|7ef2|yEtbZrFzZU&a zK0i0a3}39bsyT{YyP1FPj(qaLy6XylopI3O$ESesXKlAW=mmad*fZAK0qZ8tqwx5f z#du&(XJ0GyIKO)6=j;7JA7L~Wee2B-ob%W2+2|VQzG6>grZ(0e_TitGVG4d7Xd4)V z{e^{*xW8M54#TnAd}P>T<>P|&9RJDa73SU0!!=%Gf7s$EtZ&ojpRc+pzc5Shw+HrU zolnQ(GsC+C=V`TC#QuW)Iap7g&p+dX1Ni6YkjxtUGhguS^ggO~&FP%)^T53!yj2slqc z{xjT{w>JD<)^V6S_Gr}d&!e4T2dwwjU50bkpKXlwo_BoF>x(_H=VM?FW;w(T#-5t- z!Ps-(FC6Pz*IS}%$ME~wv;5{*A3U9(10$O7?d&*cH_j=q8-clBWja{5GUD58U+@#_ z9Xj*R-LHfEn*YyP6Lb5n^2h$~{(Rd?Z22`@JFo(KE;o0?u@k%Udwt@A2H11y1)sle zcWvBe-(}NqY{0#pSdUfmb9hUlJI<4;eF#0UF8?g2>}-ScI6motzGCu5?B6(?eZV1pE*%Ks&ukm~vT)nNbol!c`h?GKXvyE-j<-hO zevO@^i=IB>JNm)#&#xPAF7kWg>?3^JUhn45iUn0~aIA;(R`iJmiRg!$ z@b~d~SN=IFIXVQ7Vda`d*nfKV3-r8-IPCd7PL4i0o?jD|-{8*%GXvdlzYc#|kMp#j z#IOHxy8PPP{~CWUYBE-0zsC9V=v$svqW>xRfF5rUi&@1s7PxI8xA=7+GdBaTtGS(f zVb8xbet$nZxh2;3_ccZLsN&bI^6h*(o0P1^ZD`}me~)O=aVFMV>c(PjYTE*=+ZfKm z`A_D)#qF^WWc5zfEHqC3{VcH`$jy9rKM@3X}g-SPZD+~1bI+i;#IllbTNYAU~12K(^) z#qXQ3IFFeMp0A9|CAL(x~<;>Y2j4Sxo{Wh%q0!tMMS%w+07teb9Kiu0UtyNLck zs~nGE>m#Aqe=GA0_6!c<=Z|Y|{#n>^gWrP&c9?);y>|1@)TJr>JkcKC8s`kkJcXVW zz6brPCciJo{b`1E{Tp-92gU1PRN#AWzi*Vf_JmfZ_BVgKM=eEUr++hIMjtr2d+3*w9Oc*pVcd_>3B zSl?7K2ghDJ`40WS6@D+cQ^T((8BYChe!qeI9ud-*-?Nh2O~D?o>@b|O>4Wj;L&pt3 z{~5-glfSQPiFNZd{$2-q@;PM_`FpzGP{jVshpw1Ak?`Ly;!;bnM>dQL zkABQt^oaP)xc$nBG5CFmYmh$nWES)9bBMQLx8Y^~2%P6# zQ-03(-FXpvQZCQIo+-Wfd*{(266^j``E^yXJq5FJ#x%vATOIiCzhkTTxm}&w0sD7P zEkd`-Z-!%^+`Wu-!+;IAZIj>eXZ|!_{yTW&+4eZLwZ;haf745G?5?;4!bBadGn6z=1(r+Xv5 z&DDPV`nd*wrW{(JrVyZqU*peH|8AvyeeoA7Y{ ze3j(IuMg#cgK#@ncjKSC%Mbap*{pQ_xidS+pO-o(F2`*?yLt~E&$wH0Sl7959nbRy zVpFVtcxHgP$7k?s{ilc^-)I@IKOoSzdoN0Th=y4Z}qAP z?n{@EqtOS)KgS-^P=4R}yqv!md8_#E8Z(mlcTG>j`E%Ed3VyF&Z z^?$nj+S|P`zaN#ZiO2b6hWtB(6qji@c2N`lEGnMo+rOwd0LOmn%&#p=cNgGs^K=-9 zJ?k&?`^&VjSvY^_q-yNh`L+pq>L5P9PCtJBOsTz!Jvw^)+52#uCeB&9p8t;d*q`4c znl~50?^|x2YK3FVoUY^it8dEDbJy^Ftn5?|`_HBi#-7mO{9d5hxgmb0*4*oX zJwvoEk6?ZMf|Z!{F@#@}b6@l4q7DiC zT&lfbg=769_k8s zE7tqoa>0I|WqzA#{IYwU0G&=C8Ne3^uE&VKX&>kAwA#D4cAe*J%9 z#GmtxwE6qh{RV&DFlfkg>jm;>m5Zpr}vaW{UySn}x=_G{JfbAFNy&kgOwf2StP`1@No zU^|{aIq$dOJlEa%weD``5Zs1p{}4R3rKOQrw?E9!r9PAR^>c|OAKpU=<2M&tI>yz)Tr5|D&rV^VniazQZm9GlPI>zk{; zVSkm+IXouQkMZwz=4A1EU*`Uec&s|=tVaJnl|RFHy72pTWW-UNU)$gs&i{Ko|9(~H z=WXn1bA>;129-9)`kpRpaje}M{@Ed&(r_Eb7V_uJN?-n+%>G>dxtRKL9v%nf5q{km zsms4_nY7OabDc)|WB-hi{BMgI&G`A>%BCOocWUE=zOEy`&-QQ1{~lz%mEX%66!O0h zUhd4#^GkCG<~CTvk58Xu{(Q1Bo&R1_p~26?_}6kATTo?)ekOo_cQMbCKmV_N!uS1x z4!?KLZZ!;#xw$sKk7T{zpQ%@o{62ehY9;P_h!#I?4+jaj4Qpodza=I4@#mAjXKnF) z9G}ncO?5}|Ih9#gu%}rU{v1+Q?e{+a3VuvN)|cY;#Ej<8T^kyj;r6r}#gFZ|v;3S1 z>cqdFDHwO3&(o1#2U?Hi&rXwj^K_v>=hqpH5fRwGJ)WNf8_V)=+Zpt(Xv})mp8t+~`3=8z z`PuX1PvT;58wNb#*T&gdr*WRRjwyd$HAv#`Z|J1S*du)9=Xt$!etteYH4^9C zvX|dC^-a6s*j1lwu;<0&p;#a3%CAw~()sW2d%E*$i{d=LFFQ2hfA>89l7EM``yxMY z-5c_2`>JdFy`DME4A1BH$A9AXochS`U)^T&&+Nz7{2AlmbpHNs=-Lanf1&O+oaaIk zzjlTGYJpj+_w)N;%URqf->ZZ3?5@wx=V6xja8CU<@6hWH=AWYke*>(CpE1FChUW11 zrF9X%*IX#Ohud?}B^Spou;B0e%pm?5X?1Hj_TTs9*HwoTZSi{BGnoHwWwhlc_MdLW zzpK%4BqrX-;vMnI}7vXV7<6Ke=k04l+gIdGbWYe|Ii1(Zrs2Q~9&vl#%?LtgjV>J;EV= zZA_Ztz_S+d?@8YNT!{S-wfVW)$k89`jSuy~u`A!d#Qpl2@df=^2ER`3I>mo~%=*B; z6TbYK-y;TR^WVW|`1A8;XY6Fm)tb!j?-3vP->0rs@Za_7_qD-od)+S@eQLe+_?c~; zc^&5rPv+O#N7ol)eRu^wPtqpvbJZuu5$7yftb_f@u6)0K+ONiXysIR%6h!7xVMyy!R^Hw#$S0 z`B2%Ne@{L~!OtJ(*8DR#)|lT<=j6xW@%%ZKZ-0CE_fYIQV3RflJ#Q<&#|5tDfA5Z8 z-Wq%Q zJww0SmcQ>>3;1X7#|Ufe>Eg<>4E%av?(mymuqWrsQ}n%ZFYM`^%kRI!aC5BZB=Ya` zJKy8y=cy(9UhFWCUw_^|`-}5CZR5vt(~WV?UTJ(AN+R-btam7Xf8+n~XOFG6+So(x^Jkj_ zE%@InFFxhhC8H($yc#>f3UkLb+sDn z{QhoS&EM;`24T4GPmN``4c9O6_abTcUEHszz&6<9R96G*5r6o-tg^*nO^+*kdk>A>mSv=Q!=nxi}fq6{Q5Kd4?nhhzaPS$yL)5Mr)p2cTt`cO z4v*Nv&*52R6S3d=1>YXeqw{d=jN|-X<4|@5uU%eY+wmCwZpyELJIj{goQHSM!k#%3 z528=cG{>H(>8o+h75dxpd%`Yht#BK(0{MO9>zB@0-*buoE-+}NA=Z6+rQ`f=NBFgx zUSH||k7^B|E}#i$0rdcFKnJJ~=mHIZhCm~rG0+5P3g`jNfaX98pe3LWv;qtOL!dR# z1~3BJ0_}kIfHBYkFab<~j({1^2`~pb16_cwKsUewumrjTR)96o1F!*l0=9r1U=Q>H z8~{gvez8jrBt5Y7pwojv4~`4a8|VY{1zdrCfE(Zr^aln21A#$+2QV0b{};$#!2F?IyfqXy-oCD4S z7k~obB5(<~3|s*Ufg<24a1FQ)6azPao4_sLHgE^H3zPt*z&)T0xDPx49s-Ym$G{Vy z9C!*;0F}Tq;5kqQyZ~MT)j$pK3V02?0p0?&z&qeQ@B#P;d;&fLUx2T`H{d()1NaI2 z0)7L3fWN>$fPVPv02)ADKoigc>H*q-4p1M^1sVVifkr@Mpb5|v&;yzQ&4Cs`OF$oJ z1sDK^Kx?24U<9-U+5znWW1s_I0+<3F0W+WzU=DN!x&U2)Zh!?~33Lan0BfKJU<337 zYymsK9_R%)0FHnY;0(wC0U&?~xB$I@K0sf<73c@J0q#J5U;r=>7zB6#gMlHyP+%C~ z33vhCz;M6^@C8NyetqKU*ZunE`24}nL(W8eu; z4m<@afJ)#Q@EoWDUH~tFYM=&q1-u5{0B?a>;2rQD_yBwaJ^`PBFThve8}J?Y0sI7h z0l$Giz+d1WKyUnY01conpb2OJ^#E-^2dEF|0u6wMKqH_r&;)1-=mE`u=0FReC7=(q z0t^5{pf%72Fap{F?SS@xG0*`p0Zf68fEmyUFb6sVU4X7YH^2h01iAxOfHlwqumO4k zwtyXA5A*^Y07t+Ha0cXn01!X~T!7v{AD}Pb3iJcq0C%82FaQ_`3<5lW!N3q;C@>80 z1iS!mU^w6d_yQvUKfoUt35)_p17m=(KmZU3j0464LBIqc7?=o50z!aLAPkrcgacE6 zsXzn}2}}c`fayRqFawwg%mQWuF~A&PE)WaE0rP_fiu8aARkZy=YaFT z1)u=92wVa#16P1Tpa{4MTm!BH#lQ{VCU6V54cr0l0wq8xa1SU0?gI~ihrlD?G4KQ^ z2c7~IKqc@Dcn(wnFMyXoHBbY*0$u}efVV&`@D6wnd;mTIpMcN67vL-K4fqcH0Dc0$ zfZxC$;4knGpb!0Z01conpb2OJ^#E-^2dEF|0u6wMKqH_r&;)1-=mE`u=0FReC7=(q z0t^5{pf%72Fap{F?SS@xG0*`p0Zf68fEmyUFb6sVU4X7YH^2h01iAxOfHlwqumO4k zwtyXA5A*^Y07t+Ha0cXn01!X~T!7v{AD}Pb3iJcq0C%82FaQ_`3<5lW!N3q;C@>80 z1iS!mU^w6d_yQvUKfoUt35)_p17m=(KmZU3j0464LBIqc7?=o50z!aLAPkrcgacE6 zsXzn}2}}c`fayRqFawwg%mQWuF~A&PE)WaE0rP_fiu8aARkZy=YaFT z1)u=92wVa#16P1Tpa{4MTm!BH#lQ{VCU6V54cr0l0wq8xa1SU0?gI~ihrlD?G4KQ^ V2c7~IKqc@Dcn(wnFMyZ8{{drZ;Gh5i literal 35392 zcmai-d0b83_y0G^7?BJ`BxFjOXu4;gGG>YfV`fT-LWW3=DVai%nGix`4oSyUndd2U z=6TAT-oLevyVt(I&p+S$_&lxGUT5v~I`cXAdV72##zc-UK?w1hPslQeD?JFg1JT-- zkf}D^yLI#p3>es@oo|Og9~%!x2L}f`8~?yI1KYIo4Q%Ic;}PWOhW=bLU#^)i*UXn|YZWB)Vv*&zW|3>fKodjF zrtrD4(9DE$4X_xh94hd@(Nz&3t*D`Et#Cxz@H5dhzWgdaXj_ zn$>d6B5IJRWucib&of`Hwfzk9nV04Vi_llERankGEUy@7)`NZcJk+w5{#S3HHD6xu zC2F+!P=~&Ht@-M;=F4lcVJ_FK4c8F4Rt&UiptbGQ*K6CO*F=SF(`)9-HS^_~7;3P> zJj~;oMXpr?tsP$&GfxIv#v=3On)!0ge7R=6sM!=g7b4e+fo3_LXTDr(elX^~Y#Gzc zmuu$BHSb2H`YpnqOPyyDTeN@;c1FiY$^O~<- zTSJ{TheZRe`Lg+t(|q+>^W_>A<6M^G^#)q=<@H{ohWRQyZ=jhk&of`HnJ;Q+gBp5p z%_7&Tf!2;MjM=s@U$%^C2G`7&Yv#)}^F__3@VOAVRtz-D@jUb8n)zz`okau9e0iSv za?N~EgG3Gexn{mxYd&5uR`of(X1)x~$3U|&&of`HnJ;Q$puvmhnJ?GOmut2zT(d|3 z?#neFv#0<(PYgArF%SK@*0xvu*HhbGy~ce2J*@e1gMQ4*5a!Fr#85LU{g}u?Ghd!( zzFae3u9>gG=Nf3Xoab5Oniy*E#k?#u^W}Nw%Qf@mn)wpwr2=d}ajlK@d4(t z#(J%=WqM5vHKZ{Qy}4$-Tr(f8S>&4eGPK?Xn)&iPTgEl>MGb9GLw~NBFW1bMYv#!{ ziv-}lTr*#;`7$jUs<2=vE#1FdbZs_*Y5YOEp0Ja})eS>&26Q(1VScFV`v?>+{+& zUSp7l3alZr(9D<5WxiZ9U#>M@7;3#$5b$AI1-RCh>GQ-O|F1$nm-UAatJQq_tQPAHv}&L=Ul_A~%vaHCwx79XzFf09u8E-r zE6lSV48B~iRfD{CeD!(et1W|i=F2tn<(l>4n)#w;Q{;bM1R~dpfo3_LXTDrBUu`|I zSreg|FV8byu9+`tkf>#$nJ>>XAFf&Cn)zzpte=5qzC6!-xn{noVG3$lXy(iF%$IBC z$+Z^2PumU_^_ux|&3w7WI+kM}6<}Hxn)&iP^W|FGUKPerZw*m}$op~4BG-z6CWacO z@fzmKHS^_~`Et#Cxn{l!^dbxc&6e{#i(C^!4O1{L3(b6ap80akk1gxLd|A{m&??VC zR17rMvK;&HWwOxBm*<%;*V=y8*K5A8NL!{d0UubVvgPapQ8CbLtbKT71Eysy`>)UbVz2rUb(7-&|D^$C~7LA`-yzPu0f<=R?>a}RoH9|O&n^E``O6GIJ?FfR+uwwLFbFW1bMYvxO! zm-bQNPrX)I)N92+YdL*ho5QtiP;a2M{mg5;M2+UFHGn@$8Ps zzC2G1HMGGz%;TCxu2loA9bXu;5A$Wqw2y&i+spIJmuu#W8YZEJ9$YI1n&o(2JD*|9 zKHBm1(j*i>)NAI;HS^_~`Jx7in)UNy3H`QuY4V?^UTe$sn)zzpY@UH;zC6!-xn{no zVG3%P&o$d#uGzM7&3w7mBKQ$lPFQ4sX1-iAU#^)iYLKX54%f_=Yv#+fw!JEhA!iLy z^UyEnnnkV^15FGyYYAi4hrzXCpj89Se0dG?RkHPS4K!QM^DJ^r3^h!{yeu^H<$31I zHS^_~`4Z?wV7azG^jc+6ueDme)^c1!39r%S^Lkn2nJ?CO39b3U5PkJpWj*v-G0<8M zeg0no{KoxObXtf5K`SLvT z<(m1TCI%Y3c%Jp*n)!0ge7R&(5x5FGheRR z_Hxa93GZW|Rmkx?tL2(S)F4sILTlRqbu2Pptnm_B^M#>StAc=!RuA*IRtz-j!Snx= zelF_|A2wI(rPr!KUOT?}yynYmRY>Z!nk}^E%WDksP=WPq9-pfiXw^V#zA$FXwd1ST zL>24HeA#jh1I@OV=b0^PUi&{$H5hBhS3hRHd>QlQn)!0gwwG(>iyC}ULqDz+1I=&1+YVgIpEHv}wdFIQtc5Goj%Q0Wpi)prR zxK<&@wPK)Ij^{xM&E~^0&Bs7%ef4?mywYpU7p9=EUaPR2eY8FXTJzQCwF0i?ea^tsn)&iP^W~Zt zYOumQ^x&FBu2loA9bXu;9P?$7K@2qW<$31IHSzr zi_5ZwX1+Ynd{Ki$4gI-hzFadOu36-o`EqaO%e4yqxn{mxGhfuu1~v5On)!0ge7Ro@c&XGheQmFM(dT5A<4P zQLnXHUSp7l3alZr(9D<5WxiZ%9&Ems{y2j~U%gh@a=q4k^;+w}wN?Oscmb?G`*^`N z8EDN{pV#X2TH9V$2lZ?}Yx6;CzWTi8%WJYl4JfSF=J9$K^;+}QYg|9Cf7`3@ysBSU z$TMG_XTDq$Lk;aQ4?VbMk!#gJE8hUW87-*K`dFH{j zwx40leYMDzLw~NBFW1Z$HAvLZpKIpJHTU7;f3a=i&4V9Gbq!dvGWq3}<5j*c{a~B* ztjWV|u@#Pd`ntK^yXeAa4WECyvwmkm)QeQt-!{i5^c%hR`K)gd?HkB)?vj+5?~j)Y zd%AVh_+QqCLZ_cw-R;^d>$TG0sIrti%y_J$o@@N2y5*zaB9%7z}x zznPLUW$cC9-;O8rYd^7c6WlHPEI-@+!lv~Px+_khEjzU=FkSP(|HvkJO4mbe zAJwjR|H_d+En=6<*}Ur4pVr034X%{&zL+;@%hhECZ~GNixj*8Q@9_m4%6+y!v1;1n zk~WtjOW#~*xqIELE0rUD6`yNUzU1~Ilhp-5$;5m~_lr@c*Cy{>o!lg~{Wtkd&oMa< z&JT)R_jklosddEtvb`q0woN?0qe)FSvx7DLi!I^`r@l3MtH{4Beu zuEVyLm3JOoU-IwSjDTgs8%)1uaxp!-{iLNdXxd@tBYO|-ossw3Yi;uS;ikKz=lZ*z zv|m{>b4r8DYa4YpD>${In|G9@@2`^ET)R~7+3ldyu3lw5DnIIa`)*&CWrY#fmtPF} z;M3Y+Q{LJBZDyt3d(r)6W|?QzuDw5bqtlccbtbMamt(6dGj!~G^PW2u(`6?7QnQ^V2k%3=I{W|UV^lgo{>vznoH}B%DH*3P*4ST)#*V!W_e=n?OJ*mX(Kbwy? z_iy_xt@3hrle@dKSG}8IG%VgD!e!m>?Qx?&MqFs@f2@vg4>uCoa6nS)YL%e9AJC|=06i%0fP8>{kZrSl6qwRxRE7K=t)LaxZu3oXjuLgB?M_Z<+7C((KIo0~z zT$>G-OAhc@`T5LJztN}DqkqnxyZ=mg*Vi7uGkm7>s<>dw&MC9UmU?#Q@#@^g&1KJx zL#LK5zxMm@2EQA;IyLlKY_6C2_}i_cot!5h`#t5Z?dimfj}12+$=MdV_S!oK^Y)Eu zPuNq`=vQQZyK7d37cLhKu(q!5G;>7o^q6VO&2+bCJziCR)wU?_w$-}F%)9r_ZuHOC zJG;s``CaezWJgrHW=+(UYK!kieFsz+o6!FL={fGl*L}@FBwzheAJN49M$AQ(%Txvb;{^aJW{9t*clU46FMz_rp-`ihY z*PwF5`@O~iCznK}XPv*^t9N%3AM=iB9v>5q)jDm`w*C|O^MuE*w@gd4igPdukZx43 z_bIE-vy!DVntD}NUp98n3OjF9B`JSXS+|$pW=^R%{LGgZRdIET!=aGso9Y^ z)?QxgEiVMFd6)HP|Iar=;(RLme4lMOc$;tQ{X1>%A8jx=uim!vcMoqX_|))@&T-G9 zw4^cdJN#b#sk`&y(5how+2nhh3_H`LX;5P2IZJ2TMvXDEsyp5HP~w3;A5X>nxYqwd zajA867YEk;96LKC>e14N&GL4;D;@pb+B^tGT(fWB(!t3p>-^Zn;IY)gBJafEviQ(xs7Y=@DT>Q7Z z^Uq3cJpUeUe$t}WjI4xPCY9ITJ}~K8)rP%Fb+9?t;Ky?BOdF;F|W36M_0c9m$UPAH+E0Dku^Iv=gL)&#<7RJJePzG7+&>T z>WqG8+6CHNv2H%cvLM)J#xk$D78X%~#)Iy_&adyz z^_5mXyn8&sqN&_ZnX%w#UUtWGd6is~N=ijDF78fH^IJrIcoJ%RY@b<*sjlntN?mHi z9(vd?dd$p+gHL_@wfV}xYn5(%%iTTmZH!T~Khr&u%a8)o<*qsNh76|%?~bWF@odrJ zIj(aymO5VLbDEKhja_*63TZRb+S(bj(k-dTd-{pzZK(7PD7p_jQYP-%6!E-RrR`^VPkRTP|Pw z(mML02tYZItzDLi5EirZ8 zLPw9RQf0~0Ij%)#E<{}NEpp49>)R{RE@z6#h!*GWT7~8II29HBW_8o7P9tYHjx)bJ z+M-lKW>B&5*KzkcKY24VDd}&iX*b3iUv&RbeRG9^+TWwk&bOFb&B^KgG2*@W=LWl} zE6-J`d*{Q~=<`1tf2+CN$obpD;UymbdeQ9_Ij5R5@4I+&mpSiC_j++_*uCHGe;$YB z`d{hv;>P}^AtCo0w+tSJc^e@xO z`exNB)8^!?_*)GecinED&^EYVjKe|W;5|KWdFQ(X@2j(H?6qCp#?8ts*DmDN71Pa6 zj=0%Pz1QRWhC>OK(~nN`@R@UdcI>v8sg(!#v}x*^Kjut$+7w;*-qe8Y;TccfrZp(a zUbw>N)S>PtiqGuu9Xu(aR=97)QpNIyZOM*{q@uz7N4-i2yW8=`74Mx7F9dHq+{Ax( zM*DJ=osMJ;_L=v0W6F$0O&sprj=9yVdY5Z+hi-PP9+CB}u#J~dZn>n$Xv^ufj_z*# zeVAI9QFlV+)$v}hI;Hsf_m6&9-6xtxc;JBH*gJATO;-gj@*Y5NyJ{R%zGSN~OG z%(7DV&RtlQ6B75$JEZ(r@40o;b|t;Y^Yk0O-DXKtuU{uWmK;0j*^RrWlI!n0d^9EV z(VL|^Gj2ZLd%W4VFSX2W79ag_|HCMg8tXz9$42Z}?NNG!cTcyBrOLqYl!9R7V~>oN7u$kD_6JDS^fu(9amw;2sZr+o6Lo+g$nD%TiRnl2Knz$+K z$HK}(=4{^e^KbW*7mZ(jY+jr*t#8G&)!){1I&*Kl?W;X`W`V=1P5<(tU!`ET;*@c> zc4b$6Z2RuC_4nBMCog?jJh?;7ajV<<7*)9X{$9uK9rwnW2c@~*D2|vuJJ_M+qnmH4 zy@|dP>i2Qam^x|kpGp@wrtSH2E~(e^W)=a z@=srh%;{Fiv3KP@$#F&VG9sV!Iv?D*X!e29Q$ida{_gg!F}}>N7mw}NIZc1~=2xe_ zWVMf5-}1M@%SYZAdujHQu3?Rz&5!t1>1~)s1ghOZRhaa!QlX^_dq&=GjafxOLc6o5re(D2a_;bVfwL6g@$Ta7Zv+4Kb$sy9@G-*%kBe#( z9u_|Ce>=Ho{{r{_Xx6h`8)w#P#IXOJ4*v+3{R`gzV?JM*Y1Y=(CDu!lrO#+-lx-WS z#kTLd;*j#vS@WY(?4AI+Y13ZnRq_p85ch-De*H?S`u?=^)^xnIE$X>$fagWs@LF>u zV@q=y@^w6&dvrYwQn%9cub)fHoKvOlCF)40suX#Yn6*zgD_hl7eHPwoZxu(W^}0+c zxwsr{wYIf1rPx@i+vu7n{S+fbyeg7{5?!db>`2kHGam+8m-dGzIj4YYi^6{RzcXyyFR+BlzENn623KgSlVxPvWOI4l>k4w?X-}xh z`uVhF%4ex$bgH!QV!pKPpg)ZvyQoyq+Tvt?>DbNcQm{i0 zscz~QX-XKHGR!#pSB`q#NEnVhHxhvO8ZO_h;ra8MxfqSi_Z=s3O*dyk2U>l>l~jeEwRYAWuj3@$Fx?^5zj8}T+Im*9O;415q}P+e$Mn`s znR8b6uE>ZssnA~Waqpw()?>DGdzD4f`u$nD77aS-`bDgg?#-%4OTQjLCwk4I_M5}$#9UYU zxqnGI;^A%SSL6`M{`*&5Xq>OE$F((*(Jd3Y<5Lq_?RO1Y&;Gttx!hH$?||Ag#PpyP z`C65ZH{BxD@4iTKxREBMnv|!lZ?&fl(udI1v^zB&-;g?Q{wP%+eO)?bwnb{~vqf@F z`Ye^$=}Hf-7)Bf0_M-`n>}bIR6WYf8wB+qMQz|*j8qUEvQb)&{w3?4XLm#iF(aVx( z;=*xs#G!ulp}9^PTsT2DYKE88o@96?K7S!S{y2l4UAT{qZG4!u^oo-;X6%F&5NgXRKpaV{H zqFbj;l#2T8m+E?W(IArb{bF;54brwllh)U8t1!vjXkx z9!R|dhtO9Q9O%x=SER#bPD!ITxYC)cjp*vx*QM5JJEb2dE=k)zwxHXy<7rU-VR}C> zlQy(oM)!}?(Tvp(q<8)Yq!qJMq@6{dq#wGzw8O_#da~7dn&5YgK7AKQ2Of2!^<9#r z&!>&0E?Mt%a`aiLhuI`ruIWYUwBRrGs$EF?#O2b?pHk_%XRW2KO1N}7c8}Djs+;H4 zcsF{j^f@}Jqlp|h#a#ZKR8CH+UrN53*Gx*Zv7{@ePonqW^gbKf(8G9SG#%FW4ee02 zlI&5csywSiZMmR)2g%=k04?y&rMd0T(K~ad(dpYdcrLJ?L@(N&qjQ^mrrpj|lCS;r zmxAIa)4?%c=z$I$I1lJ39Dby;<09O zpD+{o;n)k*J+U?Id?nwrk>3?*l#eSNY0_4@4%fh)pUL#W)2DP~$8R*X&Lz6K!wfoO z_Bp9>(|Fw_*Cbu!*11y3Cl_5Eazwf`z9;Q^F^M+XyNaHv8A=OsN>Z0Cmn3Ir9X&Iv zH&ud5(`~-nr05exlIQQ4)M55ry6T{bd^xtVyjFTgZ(d$Qo6h|s&8?m!#ZOu&rBZ3}sF6<7y)pHT;oSY!VUH#<#_m`h;%(uUiV~5T3@02pKQl*K! z>rX@Z@$j;8@0YjfiXD5T66KSnO9wtkEgN+4_?tPBu59_21|&PkW9=n*ReHA%wfs;ICv8z0FrMvuJ!+Nx8lMou(rI7x} zETXK2{m94SpqKyA=7X!tXO}jXrSBHh)FGBGB-Q0U#tt&;!)v`0!e}pD22JYpmKKaC zl(yEIN`se~$v<2@$QK?LQCIs>bnZc`R*lP*r#9n;(qmQkN-0C?(YMld`mJ$Q zx#WFI*izT$o;vY2cPks6CqvmqK2y=ORvx{`& z=T0zn!*9B6qu?^`nvwgIJM@9MY40}2LLR0zh)Kc<}PuuDE=-1Lv<+^*j?eU%t zEz8qqMhEGWHPz);E|PqurKcR7QA5sp@QA8C>eJF~CrUwI|420wTD#5YHjOS-%F5H< zQ#cR1$nKNeEfanlDFkrIufo6wvV^Z+y?u*wtS(2EN5ECa&WU&aU>$UJp6#p=)I608g5Ld^}R2hxf(`OC)}o6YF3jwn>3K$ zdRLKa`&_2<$_(mtz5>m?kt*FDQJqG6ZlbfVl$7TiH;{9m*~r6Q{G~CA*3rad7dqMX zv(9=;IbHGlVyV^gRJf)q$|-q{a{B%jvRbpe96#2RZ$8V|j5=Re9px z%k;*H=hjGM!; zQ2L|cCK}|ilh#_gp5}}#mafN@mhYRn%kP~WFQtc_qwP}6Wt-}?GkUPuJ}LLi9qI9!;q<@`m3r3yPJQQprEc$ z)Z*>{so96Yx=|fZNK;zOq+QP3qoqU2%fs4~k>?*hM=frQq2D&npbMIZ=-k{kNk<1% zclS!s(Iz!6(C0Z7<(pm1$I~pYv*}-Iy05d zxb=v>j)~G;eVs>_Tb7q=TzEs9ABv{i!)ti9xnV~i7xtwgr#-1v+yUBb^>f|y@H2Ev zr7E%!c0sKXX-B1&C=a+zAHuBb)$YhbE$vSY?^w*gIapbpm&|>&<}L3GiDPu=ZYSU{G&Dgz&=JoDQYxX-LO}O5a4(-%lI#eyf^Pz87X@9g0J)&Dg&7;$4 z-qsYFTQHDb=#?$4{~16tKga9-mI{$FW1f1n>tCJ@UA>epi#$m~zpC`0;}ZIywJojd zzL|R2)RYD-E|N|Z+?AXQ9_!u}cc#bRZKoGKv*?zoYw71_zBK3c1zONANU|N;mgfC8 zk?vkyk>0rVRhNE~(h{&8qirTq-{gk0>VON>tCpWs_g)13L-x>^JImPL%?I!PBMnbRtZ=$jWirGFJ))4|_-> zR^&>Fd0Enrm>;@Kxd|Paw_9reC)V>z{%YMRUAk`Dn~lBlHeYnN z=fnH(yd9pGe>9Zt+)MV%d9pzoZd0G$?A(s7{b5a4R+=w8wI8mlm~>gXu_i;;;lI98 z!lgT&TbsR?%7hN4z9(X7{hh<8^K&B_p3_aTz1x9iotm#pX*5!*IHa1+`o~r2!SR07 zboqSR?$BhKP`?4SJD4fOjbBJdUJjSO2E|H)>W4^MR{qtEF)l-s?zW{9XSq}3}lMJFXCC_%E$UoTy(d{vTTze;DKThf@p*Cb=N-nz<9!=$Z_m!w}G zKS}rhD@Fa1tf&+r(SpuxX!EO%w97~@DlfFATWTGYBL1tb%kdj3O+NQX>VB>Xm5TdP zHK9LsywFy=C$neY{Qw2^kw!h(-!*EV)mKt5pSNkX#PjmJBbX;VtC~zOKQ% zUtM3!m#%Jv`DnbGpSGeL=6k)f!hGGVyO>{h%NFyOE^WsA&c@|1pL2Ku>bF^TxQ!0muVQ^v zxs$jLduC^0%{JGuSaWpkH0&d-cSIexy))|1Q%9hFH^&+0rtBJnJ^jx=!F)0?Lp|A4 z?4NGY_Sh#l_cG2M(7^`lUDn>i{0Q5nm>)bwY?uGJXIOKyY_=@Par-HVGRszs09N);l)|LftK<4(e&)olsxw zQUdkyKEkWfmHL<;V0IJxmwj{{b&V3ySkoq<8EV_gBT@J1Ru=2?V-Dax9Dhx0uTjJ> ztoN(X4A0dqv&6MvzeB9&+7DuTlh=L3{-NhYAO90#efmD~!~Uxp?8f?UQKpzb_9Xyy z@vC!Kv!;XgVyiI`ng??qkd*4bX@fdm=AdriF2*<#Qj-0B<}5_hlFBH<gxVRsQ21#$Mrl>EcV-n(rvM(=&pEWoDnP;W1OkNVRPah*2Yn}zxPfA-?GewKGnbKOE2jb;mnpQQve~ zhhB$%Pr>}XZbQ*`YnZrKH@YR(A^c_-)@NQEiS-*A+(8|DM!aK4>0&=e)q~&d^`BW+ zys^*1l8Kn#Z4`^^mU={7`$ZPw-5_h*Xsq`rEzZwF8^Sz!|0>A~uustNL0Ip)Ks@(j z;I|%Lzk2EetjP`*>tA)g;*2PKLNGbX)DyOd&K#$FKPhxnN=$swbNSh4t#XwJDh9mAnubx znu&L_eyhauDE3tktPkp54SQZca2U6xX}i;?1I~$efYw15Fh8?O8|*Vz7RP;0w0M`c zUM}v*ug969S7nFMs7XH`?DO%UcprGNs3F$8=_~HTCa&UMy1`96v%DN`VV`dc#Cxo< zgE$AwABp?b=X7zNmrnnIbDxb9?~1Q2#kt`fEuPn;jo5FSj);5yq?KaZ-yG|N`)Y^Z z0qp-JP29&E{hMLU>XS>bCOz2(w>@;b3u?bFJ@LFW5 zZ;W317m0VjPE*ADOe;ma2TuHX2kU=3`eL6hy0@sGnRUT+_*g?+FE=WtV$IWlaadD3 zR~*ObcH+J|5}yIebq_1nk)`)!!rPZxTNZQ1tjIo2${D(;`H9z4YSmsD|WzS;Z>*X_Z@=D45V z?G*1AHp^e&+<=$jecM@0S2f&N(Y$LcTki9aju|MMFfVgB8WW~h(1 z6W@cbjur2*9q)+y{F?kNSnqp9+|x2oxnVv$pb)pQWeIVPncfi3ILEi*-uS9lDV&=! zOx%0t?up0z!p*I5ySnsPjCm)=U#Oqfc0qkUQCx$KPL#p9YKtJO3GCJh^+QR#3nl*f zgzMiWL);(!1dqb!Z;O?^u&0B!xE|vi49?qMU9gW|C-Kax7UqL<%dHUC>0o2=or0Df zgf)es7N|S<*F-(JYz^$cuBXfIo}sb?t=wx5#r_rp&SRhGT^%t$Xi5V1={=?e<~y$`#QM^G z#Cz0&r{etolidsF+RI}9TjZ9-KBtm0u}|DR@!j*ox(=9cu|eFE$2faoe&&WIs4WY{ zeKpK(E$08LE3VUHt;S=%e6=jpLrugx{E)ulH>ECiX1Jbv%*1!?ki3C7_kF)tsFybu z=lN4x@vgP^P&@47oGz~4B}ug~Z_%&-_tl}&p_s38K-^=imAr^v-*d%#luIdnGSwF6{&~6p^P9qq zQA>NowG)!x0=My6Q}O(IR9|dMrQzbe`tPA2?0;fvKKA@CRh;LWCYs`X?AidaZ+}#J zg1p)pJ07tl32H`_B}8^%u1Z+=XY7+I>bH|=jVT-_=xuenl9d%%{qwtc89Ctemm~K5IK+&C5~ZJM6^_@qGE+w!&HT*X^{C?iy=}OezHsU;V-Yo8! zw!_8y$y%M*_DQ8H;asl^;@(?5OuU~gdKZrCd1ej!SVH@qPAjyYjdnJ{2~?`rmHi{7G#q-kmPh9*g~7 zz82rhA})*Pe$>p8*wfMOFV%JBH>gsawF8bF^yuS^; zFTPv7Sw087j@=XQej}Gz<95wnAg-4mFU5U6wMe`J9QGIIo6Q_iGy7m^>|f=YIB#D~ zZ;1WN{msU0-#MZm*1P6(!Me>)DxeSYw}A1^aBG{ZJ=n zQq;=}#B-!c8IhlJUF`EZ{yMz&b0gESzjvniyMXG^Lhr4)6l>}PiQ`pZD(;`{TZnz~ zI9?o=am(}2t69a0xSkGDbL=^DhBzlb-4wrLPm2}5lT@rF_QQ=2;+YujWsQA$cI=P+ z-&pL&V_`E&tXtW3;u>FkPrQ$fZkvSlZpq^BDNgSwgZ;Z#5Z86z+V?SEed`X?b)SlN zZ@Wt3-MB@(c(?yJ=O@-*2o(4Kdhs_fKmXBG)J6lvcelg_;yL)Bzc`;?)$D-ZUtFBU z^T;Kx1pGfX{rhyD?}OVoI7+-1tuhkdHO#}r?*mmybKIY${)%_bjlNAWKW2Cn)Ikv@ zxX#r#i+6zK!Qwa4rAalgzW1AvSnmb@jV)i#-5wrz&ip(seoO3n$`5P$-8+i9Tk%HR z_D3<|+$@Ei_Ze@4@A%cG_+tNl zQE^y5J82x|e~sUYc`HwG9W9f^z6#A5j`eXw1uR^S$HlCl2l|%onT)#TikYZy z`-peT=+yz3w@4JfWo>;_2lF*>3MZ_O6(PNpW}81Le|VvTs`FI*mgzMad9-^!jCy~aM@qs2SS+aBV4 z>-1A>*Cn@_Sf6%9-1A#?7w37Wev{Cv+X(SpETY#YoNHfJd~aQITD<>XX(aB?HAdQC zpP4s?qTVs8Hg2!cN%21O!r}_{SHmn&TTKx6_FgZ=y~t>)xL&4r6#F@^^KYDc@xNsB zTA+w~olUv+xIRIJby05&5ck!UJ4ODbsR`Cx%6^6Y?~E`;U2*Ol^a@$C9rHDJiEH82 zjtbbP(tl$Aco&br{FnDFaP9>UAIwJ^i|a1!gE`im@y|uQ=&5*~u74qZA85L|2ljbT zDhBoLAD?jUg=2P@cX}`G|7M58aqrN+gV@G5;vFDAxE}U-)=j*h{H=Zs^VR%zqxP## z&@1t9HO#*;c(2~`ieldG^K;ZChluxwq;zpV?{n1;dsb`v8SDS-JcK&*kQLU93ap2E z=Z`m7Gj(zT>d>m!-1+k{r_*T6w{9bj*Unm^=R6BjtjX~ajy=R@1pNkr7h|Wo1$>8Z(VV``u=BdPgper>%U!b z#QO6gV!I~qe2+CR>WTZOpG^kl$5g$J>!06JoHH+ai+8zRintFCdneAf(T)SrH*oGr z?Ad#WI0s56i*=RwC7?i`!f2s`#!{vw3Z-nUpN{^VrE#@mRQriFZPJv=!Dzt`qlR+f4BtXH*|?{jqFLrZ((+{+!s zHTbxyGv-TP-G=M7=d`%51_lP;vGbiJ_D@K{Dx6z!p!n~^2A34~?Zz|^d)oIAzo%vS zir;UiM7P9!7{6KU+u(xZ=-Vq-e8=>*pN938n?GRxeG?ooA8R3=aTPj>=WoHjCRmf> z+Zwf7dKdhjReRb5?AhpqI8Vmgh`;}BVLBM++Wiz>5rLO+o%jE}h<&QP6X!|rw~3ff zF}sYq;^v7s_mQ89`32KwqxOgu?@QNOHo|;IzX;Suj;W~U-WJ=KK3iNn4^zbZ^P0(G z{T~H2!0oyjC$776Uvb_(S|r{9KD@kzUe}w7Yw)zIxUV)b6W8OIyAScZp7U_=+fK=s z2k~55c}?tdmw^$uK3x`z=kL|c;ydy{Tk#Annu3~Jja;+`T4EtOQmAYk~DZGO!6q0k#6Ezz!e{*bVFf z_5lZgL%?C+C~yoo4yeFM;53i}ePy`eMqzxe@fRca_Pzo>xN&{tpvOqbYJWv5J0V)EO08^kc zPz5jpssiRfHK00B1E>jD0G2>4pf+Fy)B)-O)<8YL2B;4-02%_eKqH_r&;)1-Gz08_ z=72rW0&oBv0Vlv2Z~wm>_eJC?E_N4U7T8fe2tMFb)_GL;@3liNGXaG7tq!0j2`cz%(ERm<}jyhysuq z5CJk1B0y$A1juZN0GR_3Ah8euG8ZC1;vfQK9z=l5hX{}b5CO6fB0v^F1V}tYfGmax zkR=cSvJ@gf5+DL35h6gAK?KNhhyY1~2#^&J0kRSzKvqEn$ZCiHSpyLuYas$;9Ylbv zhX{}j5CM`55g;2O0%Q|JfNX{ckQ9gj*#Z$DTOk5u8$^JlLIlWmhyd9E5g8IRX(NM3oP-FFQxE}i8X`c>Km0C@!wAg>_;lW$fC8uhDFtId0aSn(!x&Hi6(FTy3@CsKkTNg^6hH+?Sr`KfpaP^E zi~$8u0a6~ufC8uhsQ_a@0aSpPz!*>f6(AL13@CsKkV-HH6hH-tDU1OHPytdI#()B- z0I33FKmk;Mn86rO02LrrVGJmM3J`M`0}7x5q#BF?1yBJ}9maqHr~s(}V?Y5^fYgLB zpa3dBEMN>MfC>;x7y}BR0;Cp<0R>P2QX9sA0;mA7f-#@~DnRPM7*GHeAa!93D1ZtO zYZwCxpaP^Gi~$8u0b&DVKmk;M)Q2&k04hKlz!*>f6(9{^3@CsK5L*}n3ZMd{5sU!^ zPyx~y#()B-0BHhaKmk;MG=(vs04hM5!5B~g6(Dvn1{6RANOKqi3ZMeS9>#zIr~qjJ zV?Y5^fH=SyPyiJmjxYukKm~{si~$8u0pbi}Kmk;MxWE`t02LrDVGJmM3XoPX1{6RA zh%1Z%1yBLv24g@0RDig{7*GHeARaIV6hH-tCyW6FPywQYF`xh{KqMFg3ZMdn!Wd8h z6(BN<0R>P2;ss+s0aSo^!x&Hi6(Fr)3@CsKkTx&|6hH+?TNnchpaP^Fi~$8u0n#4E zfC8uh=>TIu0aSqaz!*>f6(Aj93@CsKkWMfL6hH+?XBYztpaP@|i~$8u0n!!5fC8uh z@r5y<04hMb!5B~g6(HSV3@CsK5I-0L3ZMeSAI5+Jr~nCoF`xh{KmuV5D1Zu(9xw(J zKm|xo7y}BR0;Cs=0R>P2(i_Hr0;mA#17kn|RDkq_F`xh{K!RWlD1Zu(elP|UKm|yD z7y}BR0%QP;0R>P2G7!dq0;m8P1YP25&~mD0aSnthcTc4DnLSE3@CsKkP$Ej6hH;YNEibOpaNtRi~$8u0TKpd zKmk;MjD|6w04hMnz!*>f6(Hd-1{6RANCb=l1yBJp7RG=Ar~nxUV?Y5^fQ*MRpa3dB vB4G?DfC`WaFa{Jr1;|7g0}7x5WD<-41yBJp8ODGDr~rwAF`xiyRNMatWvQXK From 079d6f6eacc0c48c174ed3039c7694bb72596f68 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 4 Oct 2016 17:22:34 -0400 Subject: [PATCH 20/27] Added tests --- Source/Scene/Expression.js | 14 +- Source/Scene/PointCloud3DTileContent.js | 22 +- Specs/Scene/Cesium3DTileStyleSpec.js | 45 +++ Specs/Scene/ConditionsExpressionSpec.js | 48 ++- Specs/Scene/ExpressionSpec.js | 409 +++++++++++++++++++++ Specs/Scene/PointCloud3DTileContentSpec.js | 195 ++++++++++ 6 files changed, 714 insertions(+), 19 deletions(-) diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index fe57e6a000c1..0d0876ce40ab 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -1028,6 +1028,18 @@ define([ var type = this._type; var value = this._value; + // Right may be a string if it's a member variable: e.g. "${property.name}" + var stringMember = typeof(this._right) === 'string'; + if (stringMember) { + //>>includeStart('debug', pragmas.debug); + throw new DeveloperError('Error generating style shader: string members are not supported.'); + //>>includeEnd('debug'); + } + if (stringMember) { + // Avoid jsHint error by creating a new block. Want to return undefined in release. + return undefined; + } + if (defined(this._left)) { if (isArray(this._left)) { // Left can be an array if the type is LITERAL_COLOR @@ -1077,7 +1089,7 @@ define([ return 'float(' + left + ')'; } //>>includeStart('debug', pragmas.debug); - else if ((value === 'isNan') || (value === 'isFinite') || (value === 'String')) { + else if ((value === 'isNaN') || (value === 'isFinite') || (value === 'String')) { throw new DeveloperError('Error generating style shader: "' + value + '" is not supported.'); } //>>includeEnd('debug'); diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 92ee03e66278..6525a428b12e 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -106,12 +106,13 @@ define([ // 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; @@ -539,6 +540,9 @@ define([ }, u_highlightColor : function() { return content._highlightColor; + }, + u_constantColor : function() { + return content._constantColor; } }; @@ -853,9 +857,6 @@ define([ var usesNormalSemantic = styleableSemantics.indexOf('NORMAL') >= 0; //>>includeStart('debug', pragmas.debug); - if (usesColorSemantic && !hasColors) { - throw new DeveloperError('Style references the COLOR semantic but the point cloud does not have colors'); - } if (usesNormalSemantic && !hasNormals) { throw new DeveloperError('Style references the NORMAL semantic but the point cloud does not have normals'); } @@ -897,13 +898,13 @@ define([ 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.'); @@ -981,7 +982,7 @@ define([ vs += ' vec4 color = vec4(a_color, 1.0); \n'; } } else { - vs += ' vec4 color = vec4(1.0); \n'; + vs += ' vec4 color = u_constantColor; \n'; } if (isQuantized) { @@ -1085,7 +1086,7 @@ define([ * Part of the {@link Cesium3DTileContent} interface. */ PointCloud3DTileContent.prototype.applyDebugSettings = function(enabled, color) { - this._highlightColor = enabled ? color : this._constantColor; + this._highlightColor = enabled ? color : Color.WHITE; }; /** @@ -1127,8 +1128,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._styleTranslucent; + 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/Specs/Scene/Cesium3DTileStyleSpec.js b/Specs/Scene/Cesium3DTileStyleSpec.js index 34c479572ac5..270c679cb941 100644 --- a/Specs/Scene/Cesium3DTileStyleSpec.js +++ b/Specs/Scene/Cesium3DTileStyleSpec.js @@ -273,6 +273,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')" @@ -343,4 +378,14 @@ defineSuite([ expect(style.color.evaluate(feature1)).toEqual(Color.BLUE); expect(style.color.evaluate(feature2)).toEqual(Color.YELLOW); }); + + it('return undefined shader functions when the style is empty', function() { + // The default color style is white and the default show style is true, but the 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', '', {}); + expect(colorFunction).toBeUndefined(); + expect(showFunction).toBeUndefined(); + }); }); diff --git a/Specs/Scene/ConditionsExpressionSpec.js b/Specs/Scene/ConditionsExpressionSpec.js index e7100876dccd..bf1a7580b24d 100644 --- a/Specs/Scene/ConditionsExpressionSpec.js +++ b/Specs/Scene/ConditionsExpressionSpec.js @@ -21,7 +21,7 @@ defineSuite([ conditions : [ ['${Height} > 100', 'color("blue")'], ['${Height} > 50', 'color("red")'], - ['true', 'color("green")'] + ['true', 'color("lime")'] ] }; @@ -30,7 +30,7 @@ defineSuite([ conditions : [ ['${expression} > 50', 'color("blue")'], ['${expression} > 25', 'color("red")'], - ['true', 'color("green")'] + ['true', 'color("lime")'] ] }; @@ -39,7 +39,7 @@ defineSuite([ conditions : [ ['${expression} > 50 && ${expression} < 100', 'color("blue")'], ['${expression} > 25 && ${expression} < 26', 'color("red")'], - ['true', 'color("green")'] + ['true', 'color("lime")'] ] }; @@ -47,7 +47,7 @@ defineSuite([ var jsonExpWithUndefinedExpression = { conditions : [ ['${expression} === undefined', 'color("blue")'], - ['true', 'color("green")'] + ['true', 'color("lime")'] ] }; @@ -63,7 +63,7 @@ defineSuite([ expect(expression._conditions).toEqual([ ['${expression} > 50', 'color("blue")'], ['${expression} > 25', 'color("red")'], - ['true', 'color("green")'] + ['true', 'color("lime")'] ]); }); @@ -73,7 +73,7 @@ defineSuite([ expect(expression._conditions).toEqual([ ['${expression} > 50', 'color("blue")'], ['${expression} > 25', 'color("red")'], - ['true', 'color("green")'] + ['true', 'color("lime")'] ]); }); @@ -81,14 +81,14 @@ defineSuite([ 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(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(52))).toEqual(Color.LIME); + expect(expression.evaluate(new MockFeature(3))).toEqual(Color.LIME); }); it('constructs and evaluates empty conditional', function() { @@ -113,7 +113,7 @@ defineSuite([ 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(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..b746c637e0ab 100644 --- a/Specs/Scene/ExpressionSpec.js +++ b/Specs/Scene/ExpressionSpec.js @@ -1204,4 +1204,413 @@ 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', 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..c1047f1b2e4a 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,197 @@ 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.1)' + }); + 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]); + }); + }); + + 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); }); From 6d8a879f22c6861612c6f09b8fb7230521057d0c Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 10 Oct 2016 11:40:49 -0400 Subject: [PATCH 21/27] Tweak test for OIT --- Specs/Scene/PointCloud3DTileContentSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index c1047f1b2e4a..8aad36cab7c9 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -382,7 +382,7 @@ defineSuite([ // Applies translucency tileset.style = new Cesium3DTileStyle({ - color : 'rgba(255, 0, 0, 0.1)' + color : 'rgba(255, 0, 0, 0.005)' }); var pixelColor = scene.renderForSpecs(); expect(pixelColor[0]).toBeLessThan(255); From 84d8ff7c12901c7ac1d5d6c62f6036f0d0ebe172 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 10 Oct 2016 15:11:19 -0400 Subject: [PATCH 22/27] Log warning for float conversion --- Source/Scene/Expression.js | 5 ++--- Source/Scene/PointCloud3DTileContent.js | 3 +++ .../pointCloudWithPerPointProperties.pnts | Bin 35396 -> 35400 bytes 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index 0d0876ce40ab..110baeec5ee4 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -1030,13 +1030,12 @@ define([ // Right may be a string if it's a member variable: e.g. "${property.name}" var stringMember = typeof(this._right) === 'string'; + //>>includeStart('debug', pragmas.debug); if (stringMember) { - //>>includeStart('debug', pragmas.debug); throw new DeveloperError('Error generating style shader: string members are not supported.'); - //>>includeEnd('debug'); } + //>>includeEnd('debug'); if (stringMember) { - // Avoid jsHint error by creating a new block. Want to return undefined in release. return undefined; } diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 6525a428b12e..c8904c24773c 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, @@ -439,6 +441,7 @@ define([ 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); } } diff --git a/Specs/Data/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/pointCloudWithPerPointProperties.pnts b/Specs/Data/Cesium3DTiles/PointCloud/PointCloudWithPerPointProperties/pointCloudWithPerPointProperties.pnts index 77fb95a57485d9d68941fee449f7fe7a3d518f20..d2c056ec9f2314575c571984ba8f8c03c316af5f 100644 GIT binary patch literal 35400 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�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 V2c7~IKqc@Dcn(wnFMyZ8{{drZ;Gh5i From a21702419e3dc146f29e2b0247ee6113752f1630 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 10 Oct 2016 17:17:35 -0400 Subject: [PATCH 23/27] Style point size --- .../gallery/3D Tiles Point Cloud Styling.html | 5 + Source/Scene/Cesium3DTileStyle.js | 86 +++++++++++ Source/Scene/Cesium3DTileStyleEngine.js | 6 +- Source/Scene/PointCloud3DTileContent.js | 21 ++- Specs/Data/Cesium3DTiles/Style/style.json | 3 +- Specs/Scene/Cesium3DTileStyleSpec.js | 138 +++++++++++++++++- Specs/Scene/PointCloud3DTileContentSpec.js | 6 + 7 files changed, 253 insertions(+), 12 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html index 7e0edf46c5d6..9dcd2c5fb09d 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html @@ -129,6 +129,11 @@ color : "rgb(${POSITION}[0] * 255, ${POSITION}[1] * 255, ${POSITION}[2] * 255)" }); +addStyle('Style point size', { + color : "color('red')", + size : "${temperature} * 10" +}); + function setStyle(style) { tileset.style = new Cesium.Cesium3DTileStyle(style); } diff --git a/Source/Scene/Cesium3DTileStyle.js b/Source/Scene/Cesium3DTileStyle.js index 513b466562e9..187315f84ea5 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 @@ -60,12 +61,15 @@ define([ this._readyPromise = when.defer(); this._color = undefined; this._show = undefined; + this._size = undefined; this._meta = undefined; this._colorShaderFunction = undefined; this._showShaderFunction = undefined; + this._sizeShaderFunction = undefined; this._colorShaderFunctionReady = false; this._showShaderFunctionReady = false; + this._sizeShaderFunctionReady = false; var style = this; if (typeof data === 'string') { @@ -96,8 +100,13 @@ define([ that._showShaderFunctionReady = true; } + if (!defined(styleJson.size)) { + that._sizeShaderFunctionReady = true; + } + var colorExpression = defaultValue(styleJson.color, DEFAULT_JSON_COLOR_EXPRESSION); var showExpression = defaultValue(styleJson.show, DEFAULT_JSON_BOOLEAN_EXPRESSION); + var sizeExpression = defaultValue(styleJson.size, DEFAULT_JSON_NUMBER_EXPRESSION); var color; if (typeof(colorExpression) === 'string') { @@ -119,6 +128,17 @@ define([ that._show = show; + var size; + if (typeof(sizeExpression) === 'number') { + size = new Expression(String(sizeExpression)); + } else if (typeof(sizeExpression) === 'string') { + size = new Expression(sizeExpression); + } else if (defined(sizeExpression.conditions)) { + size = new ConditionsExpression(sizeExpression); + } + + that._size = size; + var meta = {}; if (defined(styleJson.meta)) { var metaJson = defaultValue(styleJson.meta, defaultValue.EMPTY_OBJECT); @@ -279,6 +299,50 @@ define([ } }, + /** + * Gets or sets the {@link StyleExpression} object used to evaluate the style's size 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({ + * size : '(${Temperature} > 90) ? 2.0 : 1.0' + * }); + * style.size.evaluate(feature); // returns a Number + * + * @example + * var style = new Cesium.Cesium3DTileStyle(); + * // Override size expression with a custom function + * style.size = { + * evaluate : function(feature) { + * return 1.0; + * } + * }; + * + * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language} + */ + size : { + 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._size; + }, + set : function(value) { + this._size = value; + } + }, + /** * Gets or sets the object containing application-specific expression that can be explicitly * evaluated, e.g., for display in a UI. @@ -359,5 +423,27 @@ define([ return this._showShaderFunction; }; + /** + * Gets the size 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.getSizeShaderFunction = function(functionName, attributePrefix, shaderState) { + if (this._sizeShaderFunctionReady) { + // Return the cached result, may be undefined + return this._sizeShaderFunction; + } + + this._sizeShaderFunctionReady = true; + this._sizeShaderFunction = this.size.getShaderFunction(functionName, attributePrefix, shaderState, 'float'); + return this._sizeShaderFunction; + }; + return Cesium3DTileStyle; }); diff --git a/Source/Scene/Cesium3DTileStyleEngine.js b/Source/Scene/Cesium3DTileStyleEngine.js index def8a89080f5..02e25862f186 100644 --- a/Source/Scene/Cesium3DTileStyleEngine.js +++ b/Source/Scene/Cesium3DTileStyleEngine.js @@ -2,13 +2,11 @@ define([ '../Core/Color', '../Core/defined', - '../Core/defineProperties', - './PointCloud3DTileContent' + '../Core/defineProperties' ], function( Color, defined, - defineProperties, - PointCloud3DTileContent) { + defineProperties) { 'use strict'; /** diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index c8904c24773c..48568e1bc2a0 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -820,6 +820,7 @@ define([ var colorStyleFunction; var showStyleFunction; + var sizeStyleFunction; var styleTranslucent = isTranslucent; if (hasBatchTable) { @@ -833,6 +834,7 @@ define([ }; colorStyleFunction = style.getColorShaderFunction('getColorFromStyle', 'czm_tiles3d_style_', shaderState); showStyleFunction = style.getShowShaderFunction('getShowFromStyle', 'czm_tiles3d_style_', shaderState); + sizeStyleFunction = style.getSizeShaderFunction('getSizeFromStyle', 'czm_tiles3d_style_', shaderState); styleTranslucent = shaderState.translucent; } @@ -840,6 +842,7 @@ define([ var hasColorStyle = defined(colorStyleFunction); var hasShowStyle = defined(showStyleFunction); + var hasSizeStyle = defined(sizeStyleFunction); // Get the properties in use by the style var styleableProperties = []; @@ -855,6 +858,11 @@ define([ getStyleableSemantics(showStyleFunction, styleableSemantics); showStyleFunction = modifyStyleFunction(showStyleFunction); } + if (hasSizeStyle) { + getStyleableProperties(sizeStyleFunction, styleableProperties); + getStyleableSemantics(sizeStyleFunction, styleableSemantics); + sizeStyleFunction = modifyStyleFunction(sizeStyleFunction); + } var usesColorSemantic = styleableSemantics.indexOf('COLOR') >= 0; var usesNormalSemantic = styleableSemantics.indexOf('NORMAL') >= 0; @@ -966,6 +974,10 @@ define([ vs += showStyleFunction; } + if (hasSizeStyle) { + vs += sizeStyleFunction; + } + vs += 'void main() \n' + '{ \n'; @@ -1012,6 +1024,12 @@ define([ vs += ' float show = float(getShowFromStyle(position, color, normal)); \n'; } + if (hasSizeStyle) { + vs += ' gl_PointSize = getSizeFromStyle(position, color, normal); \n'; + } else { + vs += ' gl_PointSize = u_pointSize; \n'; + } + vs += ' color = color * u_highlightColor; \n'; if (hasNormals) { @@ -1022,8 +1040,7 @@ define([ } vs += ' v_color = color; \n' + - ' gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n' + - ' gl_PointSize = u_pointSize; \n'; + ' gl_Position = czm_modelViewProjection * vec4(position, 1.0); \n'; if (hasNormals && backFaceCulling) { vs += ' float visible = step(-normal.z, 0.0); \n' + diff --git a/Specs/Data/Cesium3DTiles/Style/style.json b/Specs/Data/Cesium3DTiles/Style/style.json index a3fd709355a1..d6657a525026 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", + "size" : "${id} / 100" } \ No newline at end of file diff --git a/Specs/Scene/Cesium3DTileStyleSpec.js b/Specs/Scene/Cesium3DTileStyleSpec.js index 270c679cb941..3909cd410418 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')", + size : '${id} / 100' }); expect(style.show).toEqual(new Expression('${id} < 100')); expect(style.color).toEqual(new Expression("color('red')")); + expect(style.size).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 size value to default expression', function() { + var style = new Cesium3DTileStyle({}); + expect(style.size).toEqual(new Expression('1')); + + style = new Cesium3DTileStyle(); + expect(style.size).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 }); @@ -167,6 +191,44 @@ defineSuite([ expect(style.color).toEqual(undefined); }); + it ('sets size value to expression', function() { + var style = new Cesium3DTileStyle({ + size : '2' + }); + expect(style.size).toEqual(new Expression('2')); + + style = new Cesium3DTileStyle({ + size : '${height} / 10' + }); + expect(style.size).toEqual(new Expression('${height} / 10')); + + style = new Cesium3DTileStyle({ + size : 2 + }); + expect(style.size).toEqual(new Expression('2')); + }); + + it ('sets size value to conditional', function() { + var jsonExp = { + conditions : [ + ['${height} > 2', '1.0'], + ['true', '2.0'] + ] + }; + + var style = new Cesium3DTileStyle({ + size : jsonExp + }); + expect(style.size).toEqual(new ConditionsExpression(jsonExp)); + }); + + it ('sets size to undefined if not a number, string, or conditional', function() { + var style = new Cesium3DTileStyle({ + size : true + }); + expect(style.size).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 size if not ready', function() { + var style = new Cesium3DTileStyle({}); + style._ready = false; + + expect(function() { + return style.size; + }).toThrowDeveloperError(); + }); + it ('sets meta properties', function() { var style = new Cesium3DTileStyle({ meta : { @@ -246,11 +317,13 @@ defineSuite([ it ('applies default style', function() { var style = new Cesium3DTileStyle({ "show" : "true", - "color" : "color('#ffffff')" + "color" : "color('#ffffff')", + "size" : "1.0" }); expect(style.show.evaluate(undefined)).toEqual(true); expect(style.color.evaluate(undefined)).toEqual(Color.WHITE); + expect(style.size.evaluate(undefined)).toEqual(1.0); }); it ('applies show style with variable', function() { @@ -379,13 +452,68 @@ defineSuite([ expect(style.color.evaluate(feature2)).toEqual(Color.YELLOW); }); + it ('applies size style with variable', function() { + var style = new Cesium3DTileStyle({ + "size" : "${Temperature} / 10.0" + }); + + expect(style.size.evaluate(feature1)).toEqual(7.8); + expect(style.size.evaluate(feature2)).toEqual(9.2); + }); + + it ('applies size style with regexp and variables', function() { + var style = new Cesium3DTileStyle({ + "size" : "(regExp('^Chest').test(${County})) ? 2.0 : 1.0" + }); + + expect(style.size.evaluate(feature1)).toEqual(2.0); + expect(style.size.evaluate(feature2)).toEqual(1.0); + }); + + it ('applies size style with complex conditional', function() { + var style = new Cesium3DTileStyle({ + "size" : { + "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.size.evaluate(feature1)).toEqual(6); + expect(style.size.evaluate(feature2)).toEqual(3); + }); + + it ('applies size style with conditional', function() { + var style = new Cesium3DTileStyle({ + "size" : { + "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.size.evaluate(feature1)).toEqual(6); + expect(style.size.evaluate(feature2)).toEqual(3); + }); + it('return undefined shader functions when the style is empty', function() { - // The default color style is white and the default show style is true, but the generated shader - // functions should just be undefined. We don't want all the points to be white. + // The default color style is white, the default show style is true, and the default size 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 sizeFunction = style.getSizeShaderFunction('getSize', '', {}); expect(colorFunction).toBeUndefined(); expect(showFunction).toBeUndefined(); + expect(sizeFunction).toBeUndefined(); }); }); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index 8aad36cab7c9..a87bf37f83cc 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -458,6 +458,12 @@ defineSuite([ show : '${POSITION}[0] > 0.5' }); expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); + + // Apply size style + tileset.style = new Cesium3DTileStyle({ + size : 5.0 + }); + expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); }); }); From 55cf834c0451c06973044ba66339ecbf18754db0 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Mon, 10 Oct 2016 17:51:35 -0400 Subject: [PATCH 24/27] Change === to == and !== to != --- .../gallery/3D Tiles Point Cloud Styling.html | 4 +-- Apps/Sandcastle/gallery/3D Tiles.html | 4 +-- Source/Scene/ConditionsExpression.js | 4 +-- Source/Scene/Expression.js | 29 ++++++++---------- Specs/Scene/Cesium3DTileStyleSpec.js | 6 ++-- Specs/Scene/ConditionsExpressionSpec.js | 2 +- Specs/Scene/ExpressionSpec.js | 30 +++++++++---------- 7 files changed, 38 insertions(+), 41 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html index 9dcd2c5fb09d..e945fb075ca5 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html @@ -98,11 +98,11 @@ }); addStyle('Show Subsections', { - show : "${id} === 1 || ${id} > 250 && ${id} < 300" + show : "${id} == 1 || ${id} > 250 && ${id} < 300" }); addStyle('Mod', { - show : "${id} % 2 === 0" + show : "${id} % 2 == 0" }); addStyle('Secondary Color', { diff --git a/Apps/Sandcastle/gallery/3D Tiles.html b/Apps/Sandcastle/gallery/3D Tiles.html index 2c0558216d34..f0c1193df905 100644 --- a/Apps/Sandcastle/gallery/3D Tiles.html +++ b/Apps/Sandcastle/gallery/3D Tiles.html @@ -485,7 +485,7 @@ var leftOperand = 0; var rightOperand = 0; - var op = '==='; + var op = '=='; // Left operand (properties and literals) @@ -499,7 +499,7 @@ // Operator - var ops = ['<', '<=', '>', '>=', '===', '!==']; + var ops = ['<', '<=', '>', '>=', '==', '!=']; var operators = [{ text : 'Operator', onselect : function() { diff --git a/Source/Scene/ConditionsExpression.js b/Source/Scene/ConditionsExpression.js index c8c6138dfb90..e1b11bf6f87b 100644 --- a/Source/Scene/ConditionsExpression.js +++ b/Source/Scene/ConditionsExpression.js @@ -33,8 +33,8 @@ define([ * var expression = new Cesium.Expression({ * expression : 'regExp("^1(\\d)").exec(${id})', * conditions : [ - * ['${expression} === "1"', 'color("#FF0000")'], - * ['${expression} === "2"', 'color("#00FF00")'], + * ['${expression} == "1"', 'color("#FF0000")'], + * ['${expression} == "2"', 'color("#00FF00")'], * ['true', 'color("#FFFFFF")'] * ] * }); diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index 110baeec5ee4..a7a347a22d22 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -18,7 +18,7 @@ define([ "use strict"; var unaryOperators = ['!', '-', '+']; - var binaryOperators = ['+', '-', '*', '/', '%', '===', '!==', '>', '>=', '<', '<=', '&&', '||', '!~', '=~']; + var binaryOperators = ['+', '-', '*', '/', '%', '==', '!=', '>', '>=', '<', '<=', '&&', '||', '!~', '=~']; var variableRegex = /\${(.*?)}/g; var backslashRegex = /\\/g; @@ -531,9 +531,9 @@ define([ node.evaluate = node._evaluateDivide; } else if (node._value === '%') { node.evaluate = node._evaluateMod; - } else if (node._value === '===') { + } else if (node._value === '==') { node.evaluate = node._evaluateEquals; - } else if (node._value === '!==') { + } else if (node._value === '!=') { node.evaluate = node._evaluateNotEquals; } else if (node._value === '<') { node.evaluate = node._evaluateLessThan; @@ -840,7 +840,10 @@ define([ if ((right instanceof Color) && (left instanceof Color)) { return Color.equals(left, right); } - return 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._evaluateNotEquals = function(feature) { @@ -1029,14 +1032,12 @@ define([ var value = this._value; // Right may be a string if it's a member variable: e.g. "${property.name}" - var stringMember = typeof(this._right) === 'string'; - //>>includeStart('debug', pragmas.debug); - if (stringMember) { + if (typeof(this._right) === 'string') { + //>>includeStart('debug', pragmas.debug); throw new DeveloperError('Error generating style shader: string members are not supported.'); - } - //>>includeEnd('debug'); - if (stringMember) { - return undefined; + //>>includeEnd('debug'); + // Return undefined when not in debug. Tell jsHint to ignore this line. + return; // jshint ignore:line } if (defined(this._left)) { @@ -1094,13 +1095,9 @@ define([ //>>includeEnd('debug'); return value + left; case ExpressionNodeType.BINARY: - // Supported types: ||, &&, ===, !==, <, >, <=, >=, +, -, *, /, % + // 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: diff --git a/Specs/Scene/Cesium3DTileStyleSpec.js b/Specs/Scene/Cesium3DTileStyleSpec.js index 3909cd410418..e077f0d17eeb 100644 --- a/Specs/Scene/Cesium3DTileStyleSpec.js +++ b/Specs/Scene/Cesium3DTileStyleSpec.js @@ -328,7 +328,7 @@ defineSuite([ it ('applies show style with variable', function() { var style = new Cesium3DTileStyle({ - "show" : "${ZipCode} === '19341'" + "show" : "${ZipCode} == '19341'" }); expect(style.show.evaluate(feature1)).toEqual(true); @@ -404,8 +404,8 @@ defineSuite([ "color" : { "expression" : "regExp('^1(\\d)').exec(${id})", "conditions" : [ - ["${expression} === '1'", "color('#FF0000')"], - ["${expression} === '2'", "color('#00FF00')"], + ["${expression} == '1'", "color('#FF0000')"], + ["${expression} == '2'", "color('#00FF00')"], ["true", "color('#FFFFFF')"] ] } diff --git a/Specs/Scene/ConditionsExpressionSpec.js b/Specs/Scene/ConditionsExpressionSpec.js index bf1a7580b24d..50c1e897e569 100644 --- a/Specs/Scene/ConditionsExpressionSpec.js +++ b/Specs/Scene/ConditionsExpressionSpec.js @@ -46,7 +46,7 @@ defineSuite([ var jsonExpWithUndefinedExpression = { conditions : [ - ['${expression} === undefined', 'color("blue")'], + ['${expression} == undefined', 'color("blue")'], ['true', 'color("lime")'] ] }; diff --git a/Specs/Scene/ExpressionSpec.js b/Specs/Scene/ExpressionSpec.js index b746c637e0ab..806db406cb67 100644 --- a/Specs/Scene/ExpressionSpec.js +++ b/Specs/Scene/ExpressionSpec.js @@ -161,11 +161,11 @@ defineSuite([ }).toThrowDeveloperError(); expect(function() { - return new Expression('2 == 3'); + return new Expression('2 === 3'); }).toThrowDeveloperError(); expect(function() { - return new Expression('2 != 3'); + return new Expression('2 !== 3'); }).toThrowDeveloperError(); expect(function() { @@ -544,24 +544,24 @@ defineSuite([ }); it('evaluates binary equals', function() { - var expression = new Expression('\'hello\' === \'hello\''); + var expression = new Expression('\'hello\' == \'hello\''); expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('1 === 2'); + expression = new Expression('1 == 2'); expect(expression.evaluate(undefined)).toEqual(false); - expression = new Expression('false === true === false'); + expression = new Expression('false == true == false'); expect(expression.evaluate(undefined)).toEqual(true); }); it('evaluates binary not equals', function() { - var expression = new Expression('\'hello\' !== \'hello\''); + var expression = new Expression('\'hello\' != \'hello\''); expect(expression.evaluate(undefined)).toEqual(false); - expression = new Expression('1 !== 2'); + expression = new Expression('1 != 2'); expect(expression.evaluate(undefined)).toEqual(true); - expression = new Expression('false !== true !== false'); + expression = new Expression('false != true != false'); expect(expression.evaluate(undefined)).toEqual(true); }); @@ -709,16 +709,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 +951,7 @@ defineSuite([ color : Color.BLUE }); - expression = new Expression('${feature} === ${feature.feature}'); + expression = new Expression('${feature} == ${feature.feature}'); expect(expression.evaluate(feature)).toEqual(true); }); @@ -1293,14 +1293,14 @@ defineSuite([ }); it('gets shader expression for binary equals', function() { - var expression = new Expression('1.0 === 2.0'); + 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 expression = new Expression('1.0 != 2.0'); var shaderExpression = expression.getShaderExpression('', {}); var expected = '(1.0 != 2.0)'; expect(shaderExpression).toEqual(expected); From 8823472d8fb4a20beebe7928235b9b2d4efce3f9 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 18 Oct 2016 11:00:08 -0400 Subject: [PATCH 25/27] Rename size to pointSize --- .../gallery/3D Tiles Point Cloud Styling.html | 2 +- Source/Scene/Cesium3DTileStyle.js | 58 ++++++------- Source/Scene/PointCloud3DTileContent.js | 22 ++--- Specs/Data/Cesium3DTiles/Style/style.json | 2 +- Specs/Scene/Cesium3DTileStyleSpec.js | 82 +++++++++---------- Specs/Scene/PointCloud3DTileContentSpec.js | 4 +- 6 files changed, 85 insertions(+), 85 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html index e945fb075ca5..9b249c8207d8 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Styling.html @@ -131,7 +131,7 @@ addStyle('Style point size', { color : "color('red')", - size : "${temperature} * 10" + pointSize : "${temperature} * 10" }); function setStyle(style) { diff --git a/Source/Scene/Cesium3DTileStyle.js b/Source/Scene/Cesium3DTileStyle.js index 187315f84ea5..f1fad426e116 100644 --- a/Source/Scene/Cesium3DTileStyle.js +++ b/Source/Scene/Cesium3DTileStyle.js @@ -61,15 +61,15 @@ define([ this._readyPromise = when.defer(); this._color = undefined; this._show = undefined; - this._size = undefined; + this._pointSize = undefined; this._meta = undefined; this._colorShaderFunction = undefined; this._showShaderFunction = undefined; - this._sizeShaderFunction = undefined; + this._pointSizeShaderFunction = undefined; this._colorShaderFunctionReady = false; this._showShaderFunctionReady = false; - this._sizeShaderFunctionReady = false; + this._pointSizeShaderFunctionReady = false; var style = this; if (typeof data === 'string') { @@ -100,13 +100,13 @@ define([ that._showShaderFunctionReady = true; } - if (!defined(styleJson.size)) { - that._sizeShaderFunctionReady = 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 sizeExpression = defaultValue(styleJson.size, DEFAULT_JSON_NUMBER_EXPRESSION); + var pointSizeExpression = defaultValue(styleJson.pointSize, DEFAULT_JSON_NUMBER_EXPRESSION); var color; if (typeof(colorExpression) === 'string') { @@ -128,16 +128,16 @@ define([ that._show = show; - var size; - if (typeof(sizeExpression) === 'number') { - size = new Expression(String(sizeExpression)); - } else if (typeof(sizeExpression) === 'string') { - size = new Expression(sizeExpression); - } else if (defined(sizeExpression.conditions)) { - size = new ConditionsExpression(sizeExpression); + 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._size = size; + that._pointSize = pointSize; var meta = {}; if (defined(styleJson.meta)) { @@ -300,7 +300,7 @@ define([ }, /** - * Gets or sets the {@link StyleExpression} object used to evaluate the style's size property. + * Gets or sets the {@link StyleExpression} object used to evaluate the style's pointSize property. *

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

@@ -313,14 +313,14 @@ define([ * * @example * var style = new Cesium3DTileStyle({ - * size : '(${Temperature} > 90) ? 2.0 : 1.0' + * pointSize : '(${Temperature} > 90) ? 2.0 : 1.0' * }); - * style.size.evaluate(feature); // returns a Number + * style.pointSize.evaluate(feature); // returns a Number * * @example * var style = new Cesium.Cesium3DTileStyle(); - * // Override size expression with a custom function - * style.size = { + * // Override pointSize expression with a custom function + * style.pointSize = { * evaluate : function(feature) { * return 1.0; * } @@ -328,7 +328,7 @@ define([ * * @see {@link https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/Styling|3D Tiles Styling language} */ - size : { + pointSize : { get : function() { //>>includeStart('debug', pragmas.debug); if (!this._ready) { @@ -336,10 +336,10 @@ define([ } //>>includeEnd('debug'); - return this._size; + return this._pointSize; }, set : function(value) { - this._size = value; + this._pointSize = value; } }, @@ -424,7 +424,7 @@ define([ }; /** - * Gets the size shader function for this style. + * 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. @@ -434,15 +434,15 @@ define([ * * @private */ - Cesium3DTileStyle.prototype.getSizeShaderFunction = function(functionName, attributePrefix, shaderState) { - if (this._sizeShaderFunctionReady) { + Cesium3DTileStyle.prototype.getPointSizeShaderFunction = function(functionName, attributePrefix, shaderState) { + if (this._pointSizeShaderFunctionReady) { // Return the cached result, may be undefined - return this._sizeShaderFunction; + return this._pointSizeShaderFunction; } - this._sizeShaderFunctionReady = true; - this._sizeShaderFunction = this.size.getShaderFunction(functionName, attributePrefix, shaderState, 'float'); - return this._sizeShaderFunction; + this._pointSizeShaderFunctionReady = true; + this._pointSizeShaderFunction = this.pointSize.getShaderFunction(functionName, attributePrefix, shaderState, 'float'); + return this._pointSizeShaderFunction; }; return Cesium3DTileStyle; diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 48568e1bc2a0..6768c650819e 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -820,7 +820,7 @@ define([ var colorStyleFunction; var showStyleFunction; - var sizeStyleFunction; + var pointSizeStyleFunction; var styleTranslucent = isTranslucent; if (hasBatchTable) { @@ -834,7 +834,7 @@ define([ }; colorStyleFunction = style.getColorShaderFunction('getColorFromStyle', 'czm_tiles3d_style_', shaderState); showStyleFunction = style.getShowShaderFunction('getShowFromStyle', 'czm_tiles3d_style_', shaderState); - sizeStyleFunction = style.getSizeShaderFunction('getSizeFromStyle', 'czm_tiles3d_style_', shaderState); + pointSizeStyleFunction = style.getPointSizeShaderFunction('getPointSizeFromStyle', 'czm_tiles3d_style_', shaderState); styleTranslucent = shaderState.translucent; } @@ -842,7 +842,7 @@ define([ var hasColorStyle = defined(colorStyleFunction); var hasShowStyle = defined(showStyleFunction); - var hasSizeStyle = defined(sizeStyleFunction); + var hasPointSizeStyle = defined(pointSizeStyleFunction); // Get the properties in use by the style var styleableProperties = []; @@ -858,10 +858,10 @@ define([ getStyleableSemantics(showStyleFunction, styleableSemantics); showStyleFunction = modifyStyleFunction(showStyleFunction); } - if (hasSizeStyle) { - getStyleableProperties(sizeStyleFunction, styleableProperties); - getStyleableSemantics(sizeStyleFunction, styleableSemantics); - sizeStyleFunction = modifyStyleFunction(sizeStyleFunction); + if (hasPointSizeStyle) { + getStyleableProperties(pointSizeStyleFunction, styleableProperties); + getStyleableSemantics(pointSizeStyleFunction, styleableSemantics); + pointSizeStyleFunction = modifyStyleFunction(pointSizeStyleFunction); } var usesColorSemantic = styleableSemantics.indexOf('COLOR') >= 0; @@ -974,8 +974,8 @@ define([ vs += showStyleFunction; } - if (hasSizeStyle) { - vs += sizeStyleFunction; + if (hasPointSizeStyle) { + vs += pointSizeStyleFunction; } vs += 'void main() \n' + @@ -1024,8 +1024,8 @@ define([ vs += ' float show = float(getShowFromStyle(position, color, normal)); \n'; } - if (hasSizeStyle) { - vs += ' gl_PointSize = getSizeFromStyle(position, color, normal); \n'; + if (hasPointSizeStyle) { + vs += ' gl_PointSize = getPointSizeFromStyle(position, color, normal); \n'; } else { vs += ' gl_PointSize = u_pointSize; \n'; } diff --git a/Specs/Data/Cesium3DTiles/Style/style.json b/Specs/Data/Cesium3DTiles/Style/style.json index d6657a525026..6bd92818892d 100644 --- a/Specs/Data/Cesium3DTiles/Style/style.json +++ b/Specs/Data/Cesium3DTiles/Style/style.json @@ -1,5 +1,5 @@ { "color" : "color('red')", "show" : "${id} < 100", - "size" : "${id} / 100" + "pointSize" : "${id} / 100" } \ No newline at end of file diff --git a/Specs/Scene/Cesium3DTileStyleSpec.js b/Specs/Scene/Cesium3DTileStyleSpec.js index e077f0d17eeb..6e516df9457a 100644 --- a/Specs/Scene/Cesium3DTileStyleSpec.js +++ b/Specs/Scene/Cesium3DTileStyleSpec.js @@ -70,11 +70,11 @@ defineSuite([ expect(style.style).toEqual({ show : '${id} < 100', color : "color('red')", - size : '${id} / 100' + pointSize : '${id} / 100' }); expect(style.show).toEqual(new Expression('${id} < 100')); expect(style.color).toEqual(new Expression("color('red')")); - expect(style.size).toEqual(new Expression('${id} / 100')); + expect(style.pointSize).toEqual(new Expression('${id} / 100')); expect(tileStyle.ready).toEqual(true); }).otherwise(function() { fail('should load style.json'); @@ -97,12 +97,12 @@ defineSuite([ expect(style.color).toEqual(new Expression('color("#ffffff")')); }); - it ('sets size value to default expression', function() { + it ('sets pointSize value to default expression', function() { var style = new Cesium3DTileStyle({}); - expect(style.size).toEqual(new Expression('1')); + expect(style.pointSize).toEqual(new Expression('1')); style = new Cesium3DTileStyle(); - expect(style.size).toEqual(new Expression('1')); + expect(style.pointSize).toEqual(new Expression('1')); }); it ('sets show value to expression', function() { @@ -191,24 +191,24 @@ defineSuite([ expect(style.color).toEqual(undefined); }); - it ('sets size value to expression', function() { + it ('sets pointSize value to expression', function() { var style = new Cesium3DTileStyle({ - size : '2' + pointSize : '2' }); - expect(style.size).toEqual(new Expression('2')); + expect(style.pointSize).toEqual(new Expression('2')); style = new Cesium3DTileStyle({ - size : '${height} / 10' + pointSize : '${height} / 10' }); - expect(style.size).toEqual(new Expression('${height} / 10')); + expect(style.pointSize).toEqual(new Expression('${height} / 10')); style = new Cesium3DTileStyle({ - size : 2 + pointSize : 2 }); - expect(style.size).toEqual(new Expression('2')); + expect(style.pointSize).toEqual(new Expression('2')); }); - it ('sets size value to conditional', function() { + it ('sets pointSize value to conditional', function() { var jsonExp = { conditions : [ ['${height} > 2', '1.0'], @@ -217,16 +217,16 @@ defineSuite([ }; var style = new Cesium3DTileStyle({ - size : jsonExp + pointSize : jsonExp }); - expect(style.size).toEqual(new ConditionsExpression(jsonExp)); + expect(style.pointSize).toEqual(new ConditionsExpression(jsonExp)); }); - it ('sets size to undefined if not a number, string, or conditional', function() { + it ('sets pointSize to undefined if not a number, string, or conditional', function() { var style = new Cesium3DTileStyle({ - size : true + pointSize : true }); - expect(style.size).toEqual(undefined); + expect(style.pointSize).toEqual(undefined); }); it ('throws on accessing style if not ready', function() { @@ -256,12 +256,12 @@ defineSuite([ }).toThrowDeveloperError(); }); - it ('throws on accessing size if not ready', function() { + it ('throws on accessing pointSize if not ready', function() { var style = new Cesium3DTileStyle({}); style._ready = false; expect(function() { - return style.size; + return style.pointSize; }).toThrowDeveloperError(); }); @@ -318,12 +318,12 @@ defineSuite([ var style = new Cesium3DTileStyle({ "show" : "true", "color" : "color('#ffffff')", - "size" : "1.0" + "pointSize" : "1.0" }); expect(style.show.evaluate(undefined)).toEqual(true); expect(style.color.evaluate(undefined)).toEqual(Color.WHITE); - expect(style.size.evaluate(undefined)).toEqual(1.0); + expect(style.pointSize.evaluate(undefined)).toEqual(1.0); }); it ('applies show style with variable', function() { @@ -452,27 +452,27 @@ defineSuite([ expect(style.color.evaluate(feature2)).toEqual(Color.YELLOW); }); - it ('applies size style with variable', function() { + it ('applies pointSize style with variable', function() { var style = new Cesium3DTileStyle({ - "size" : "${Temperature} / 10.0" + "pointSize" : "${Temperature} / 10.0" }); - expect(style.size.evaluate(feature1)).toEqual(7.8); - expect(style.size.evaluate(feature2)).toEqual(9.2); + expect(style.pointSize.evaluate(feature1)).toEqual(7.8); + expect(style.pointSize.evaluate(feature2)).toEqual(9.2); }); - it ('applies size style with regexp and variables', function() { + it ('applies pointSize style with regexp and variables', function() { var style = new Cesium3DTileStyle({ - "size" : "(regExp('^Chest').test(${County})) ? 2.0 : 1.0" + "pointSize" : "(regExp('^Chest').test(${County})) ? 2.0 : 1.0" }); - expect(style.size.evaluate(feature1)).toEqual(2.0); - expect(style.size.evaluate(feature2)).toEqual(1.0); + expect(style.pointSize.evaluate(feature1)).toEqual(2.0); + expect(style.pointSize.evaluate(feature2)).toEqual(1.0); }); - it ('applies size style with complex conditional', function() { + it ('applies pointSize style with complex conditional', function() { var style = new Cesium3DTileStyle({ - "size" : { + "pointSize" : { "expression" : "${Height}", "conditions" : [ ["(${expression} >= 1.0) && (${expression} < 10.0)", "1"], @@ -484,13 +484,13 @@ defineSuite([ ] } }); - expect(style.size.evaluate(feature1)).toEqual(6); - expect(style.size.evaluate(feature2)).toEqual(3); + expect(style.pointSize.evaluate(feature1)).toEqual(6); + expect(style.pointSize.evaluate(feature2)).toEqual(3); }); - it ('applies size style with conditional', function() { + it ('applies pointSize style with conditional', function() { var style = new Cesium3DTileStyle({ - "size" : { + "pointSize" : { "conditions" : [ ["(${Height} >= 100.0)", "6"], ["(${Height} >= 70.0)", "5"], @@ -501,19 +501,19 @@ defineSuite([ ] } }); - expect(style.size.evaluate(feature1)).toEqual(6); - expect(style.size.evaluate(feature2)).toEqual(3); + 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 size is 1.0, + // 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 sizeFunction = style.getSizeShaderFunction('getSize', '', {}); + var pointSizeFunction = style.getPointSizeShaderFunction('getPointSize', '', {}); expect(colorFunction).toBeUndefined(); expect(showFunction).toBeUndefined(); - expect(sizeFunction).toBeUndefined(); + expect(pointSizeFunction).toBeUndefined(); }); }); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index a87bf37f83cc..dec0f6ba5b87 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -459,9 +459,9 @@ defineSuite([ }); expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); - // Apply size style + // Apply pointSize style tileset.style = new Cesium3DTileStyle({ - size : 5.0 + pointSize : 5.0 }); expect(scene.renderForSpecs()).not.toEqual([0, 0, 0, 255]); }); From c04ff786537322ad7236d2b730004d6672596a9e Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 18 Oct 2016 11:26:37 -0400 Subject: [PATCH 26/27] Support both === and !== --- Source/Scene/Expression.js | 36 +++++++++++++++++++--- Specs/Scene/ExpressionSpec.js | 56 ++++++++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 12 deletions(-) diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js index a7a347a22d22..90e893d49874 100644 --- a/Source/Scene/Expression.js +++ b/Source/Scene/Expression.js @@ -18,7 +18,7 @@ define([ "use strict"; var unaryOperators = ['!', '-', '+']; - var binaryOperators = ['+', '-', '*', '/', '%', '==', '!=', '>', '>=', '<', '<=', '&&', '||', '!~', '=~']; + var binaryOperators = ['+', '-', '*', '/', '%', '===', '==', '!==', '!=', '>', '>=', '<', '<=', '&&', '||', '!~', '=~']; var variableRegex = /\${(.*?)}/g; var backslashRegex = /\\/g; @@ -531,8 +531,12 @@ define([ node.evaluate = node._evaluateDivide; } 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 === '<') { @@ -834,6 +838,15 @@ define([ return left % right; }; + 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._evaluateEquals = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); @@ -842,11 +855,11 @@ define([ } // 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. + // so that cases like "5 == '5'" return true. Tell jsHint to ignore this line. return left == right; // jshint ignore:line }; - Node.prototype._evaluateNotEquals = function(feature) { + Node.prototype._evaluateNotEqualsStrict = function(feature) { var left = this._left.evaluate(feature); var right = this._right.evaluate(feature); if ((right instanceof Color) && (left instanceof Color)) { @@ -855,6 +868,17 @@ define([ return left !== right; }; + 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); @@ -1095,9 +1119,13 @@ define([ //>>includeEnd('debug'); return value + left; case ExpressionNodeType.BINARY: - // Supported types: ||, &&, ==, !=, <, >, <=, >=, +, -, *, /, % + // 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: diff --git a/Specs/Scene/ExpressionSpec.js b/Specs/Scene/ExpressionSpec.js index 806db406cb67..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,6 +535,20 @@ defineSuite([ expect(expression.evaluate(undefined)).toEqual(2); }); + it('evaluates binary equals strict', 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(false); + }); + it('evaluates binary equals', function() { var expression = new Expression('\'hello\' == \'hello\''); expect(expression.evaluate(undefined)).toEqual(true); @@ -552,6 +558,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 strict', 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(true); }); it('evaluates binary not equals', function() { @@ -563,6 +586,9 @@ 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 less than', function() { @@ -1292,6 +1318,13 @@ defineSuite([ 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('', {}); @@ -1299,6 +1332,13 @@ defineSuite([ 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('', {}); From 83efc1b637bc8eb605bb21acb4782b5ae0172aab Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 18 Oct 2016 13:43:54 -0400 Subject: [PATCH 27/27] Fix styling edge case --- Source/Scene/PointCloud3DTileContent.js | 19 ++++++++----------- .../tileset.json | 18 ------------------ 2 files changed, 8 insertions(+), 29 deletions(-) diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 6768c650819e..d13a66f26cfe 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -496,9 +496,10 @@ define([ var hasBatchTable = defined(batchTable); var styleableVertexAttributes = []; + var styleableShaderAttributes = {}; + content._styleableShaderAttributes = styleableShaderAttributes; if (hasStyleableProperties) { - var styleableShaderAttributes = {}; var attributeLocation = numberOfAttributes; for (var name in styleableProperties) { @@ -533,8 +534,6 @@ define([ ++attributeLocation; } } - - content._styleableShaderAttributes = styleableShaderAttributes; } var uniformMap = { @@ -875,14 +874,12 @@ define([ // Disable vertex attributes that aren't used in the style, enable attributes that are var styleableShaderAttributes = content._styleableShaderAttributes; - if (defined(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; - } + 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; } } 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": {