From 54f71be9bd1ac12171f29f505d35463a5dd2209d Mon Sep 17 00:00:00 2001
From: Sean Lilley <lilleyse@gmail.com>
Date: Tue, 18 Feb 2020 19:30:22 -0500
Subject: [PATCH] Add isNaN, isFinite, null, and undefined support to GLSL
 styling backend

---
 CHANGES.md                    |  1 +
 Source/Scene/Expression.js    | 14 +++++++--
 Specs/Scene/ExpressionSpec.js | 56 +++++++++++++++++------------------
 3 files changed, 40 insertions(+), 31 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index aa47624113fa..30656ca51f19 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -9,6 +9,7 @@ Change Log
 * Added `tileset.uri`, `tileset.show`, and `tileset.maximumScreenSpaceError` properties to CZML processing for loading 3D Tiles.
 * Added `Color.lerp` for linearly interpolating between two RGB colors. [#8607](https://github.com/CesiumGS/cesium/pull/8607)
 * `CesiumTerrainProvider` now supports terrain tiles using a `WebMercatorTilingScheme` by specifying `"projection": "EPSG:3857"` in `layer.json`. It also now supports numbering tiles from the North instead of the South by specifying `"scheme": "slippyMap"` in `layer.json`. [#8563](https://github.com/CesiumGS/cesium/pull/8563)
+* Added basic support for `isNaN`, `isFinite`, `null`, and `undefined` in the 3D Tiles styling GLSL backend for point clouds. [#8621](https://github.com/CesiumGS/cesium/pull/8621)
 
 ##### Fixes :wrench:
 
diff --git a/Source/Scene/Expression.js b/Source/Scene/Expression.js
index a4bf2cab2dac..16dc89873443 100644
--- a/Source/Scene/Expression.js
+++ b/Source/Scene/Expression.js
@@ -1559,6 +1559,8 @@ import ExpressionNodeType from './ExpressionNodeType.js';
         return expressions;
     }
 
+    var nullSentinel = 'czm_infinity'; // null just needs to be some sentinel value that will cause "[expression] === null" to be false in nearly all cases. GLSL doesn't have a NaN constant so use czm_infinity.
+
     Node.prototype.getShaderExpression = function(attributePrefix, shaderState, parent) {
         var color;
         var left;
@@ -1603,7 +1605,13 @@ import ExpressionNodeType from './ExpressionNodeType.js';
                     return 'floor(' + left + ' + 0.5)';
                 } else if (defined(unaryFunctions[value])) {
                     return value + '(' + left + ')';
-                } else if ((value === 'isNaN') || (value === 'isFinite') || (value === 'String') || (value === 'isExactClass') || (value === 'isClass') || (value === 'getExactClassName')) {
+                } else if (value === 'isNaN') {
+                    // In GLSL 2.0 use isnan instead
+                    return '(' + left + ' != ' + left + ')';
+                } else if (value === 'isFinite') {
+                    // In GLSL 2.0 use isinf instead. GLSL doesn't have an infinity constant so use czm_infinity which is an arbitrarily big enough number.
+                    return '(abs(' + left + ') < czm_infinity)';
+                } else if ((value === 'String') || (value === 'isExactClass') || (value === 'isClass') || (value === 'getExactClassName')) {
                     throw new RuntimeError('Error generating style shader: "' + value + '" is not supported.');
                 } else if (defined(unaryFunctions[value])) {
                     return value + '(' + left + ')';
@@ -1659,7 +1667,7 @@ import ExpressionNodeType from './ExpressionNodeType.js';
             case ExpressionNodeType.VARIABLE_IN_STRING:
                 throw new RuntimeError('Error generating style shader: Converting a variable to a string is not supported.');
             case ExpressionNodeType.LITERAL_NULL:
-                throw new RuntimeError('Error generating style shader: null is not supported.');
+                return nullSentinel;
             case ExpressionNodeType.LITERAL_BOOLEAN:
                 return value ? 'true' : 'false';
             case ExpressionNodeType.LITERAL_NUMBER:
@@ -1745,7 +1753,7 @@ import ExpressionNodeType from './ExpressionNodeType.js';
             case ExpressionNodeType.LITERAL_REGEX:
                 throw new RuntimeError('Error generating style shader: Regular expressions are not supported.');
             case ExpressionNodeType.LITERAL_UNDEFINED:
-                throw new RuntimeError('Error generating style shader: undefined is not supported.');
+                return nullSentinel;
             case ExpressionNodeType.BUILTIN_VARIABLE:
                 if (value === 'tiles3d_tileset_time') {
                     return 'u_time';
diff --git a/Specs/Scene/ExpressionSpec.js b/Specs/Scene/ExpressionSpec.js
index 32bd03f59abc..b5474f81b1c9 100644
--- a/Specs/Scene/ExpressionSpec.js
+++ b/Specs/Scene/ExpressionSpec.js
@@ -3546,6 +3546,34 @@ describe('Scene/Expression', function() {
         expect(shaderExpression).toEqual(expected);
     });
 
+    it('gets shader expression for isNaN', function() {
+        var expression = new Expression('isNaN(1.0)');
+        var shaderExpression = expression.getShaderExpression('', {});
+        var expected = '(1.0 != 1.0)';
+        expect(shaderExpression).toEqual(expected);
+    });
+
+    it('gets shader expression for isFinite', function() {
+        var expression = new Expression('isFinite(1.0)');
+        var shaderExpression = expression.getShaderExpression('', {});
+        var expected = '(abs(1.0) < czm_infinity)';
+        expect(shaderExpression).toEqual(expected);
+    });
+
+    it('gets shader expression for null', function() {
+        var expression = new Expression('null');
+        var shaderExpression = expression.getShaderExpression('', {});
+        var expected = 'czm_infinity';
+        expect(shaderExpression).toEqual(expected);
+    });
+
+    it('gets shader expression for undefined', function() {
+        var expression = new Expression('undefined');
+        var shaderExpression = expression.getShaderExpression('', {});
+        var expected = 'czm_infinity';
+        expect(shaderExpression).toEqual(expected);
+    });
+
     it('throws when getting shader expression for regex', function() {
         var expression = new Expression('regExp("a").test("abc")');
         expect(function() {
@@ -3610,34 +3638,6 @@ describe('Scene/Expression', function() {
         }).toThrowRuntimeError();
     });
 
-    it('throws when getting shader expression for literal undefined', function() {
-        var expression = new Expression('undefined');
-        expect(function() {
-            return expression.getShaderExpression('', {});
-        }).toThrowRuntimeError();
-    });
-
-    it('throws when getting shader expression for literal null', function() {
-        var expression = new Expression('null');
-        expect(function() {
-            return expression.getShaderExpression('', {});
-        }).toThrowRuntimeError();
-    });
-
-    it('throws when getting shader expression for isNaN', function() {
-        var expression = new Expression('isNaN(1.0)');
-        expect(function() {
-            return expression.getShaderExpression('', {});
-        }).toThrowRuntimeError();
-    });
-
-    it('throws when getting shader expression for isFinite', function() {
-        var expression = new Expression('isFinite(1.0)');
-        expect(function() {
-            return expression.getShaderExpression('', {});
-        }).toThrowRuntimeError();
-    });
-
     it('throws when getting shader expression for isExactClass', function() {
         var expression = new Expression('isExactClass("door")');
         expect(function() {