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