diff --git a/Apps/Sandcastle/gallery/Geometry and Appearances.html b/Apps/Sandcastle/gallery/Geometry and Appearances.html index 60cd9d8ad2ef..5c0db4f05bd2 100644 --- a/Apps/Sandcastle/gallery/Geometry and Appearances.html +++ b/Apps/Sandcastle/gallery/Geometry and Appearances.html @@ -413,7 +413,8 @@ geometry : new Cesium.WallGeometry({ positions : positions, maximumHeights : maximumHeights, - minimumHeights : minimumHeights + minimumHeights : minimumHeights, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({alpha : 0.7})) diff --git a/Apps/Sandcastle/gallery/Material.html b/Apps/Sandcastle/gallery/Material.html index b6e9e8cbc260..93bc70501dbc 100644 --- a/Apps/Sandcastle/gallery/Material.html +++ b/Apps/Sandcastle/gallery/Material.html @@ -37,11 +37,13 @@ positions : Cesium.Cartesian3.fromDegreesArrayHeights([ -95.5, 50.0, 300000.0, -90.5, 50.0, 300000.0 - ]) + ]), + vertexFormat : Cesium.MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat }) }), appearance : new Cesium.MaterialAppearance({ - material : Cesium.Material.fromType('Checkerboard') + material : Cesium.Material.fromType('Checkerboard'), + materialSupport : Cesium.MaterialAppearance.MaterialSupport.TEXTURED }) })); @@ -52,11 +54,13 @@ positions : Cesium.Cartesian3.fromDegreesArrayHeights([ -100.5, 50.0, 300000.0, -95.5, 50.0, 300000.0 - ]) + ]), + vertexFormat : Cesium.MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat }) }), appearance : new Cesium.MaterialAppearance({ - material : Cesium.Material.fromType('Stripe') + material : Cesium.Material.fromType('Stripe'), + materialSupport : Cesium.MaterialAppearance.MaterialSupport.TEXTURED }) })); //Sandcastle_End diff --git a/Apps/Sandcastle/gallery/Wall.html b/Apps/Sandcastle/gallery/Wall.html index 7143bff99441..d4138fca32bf 100644 --- a/Apps/Sandcastle/gallery/Wall.html +++ b/Apps/Sandcastle/gallery/Wall.html @@ -42,7 +42,8 @@ -90.0, 44.0 ]), maximumHeight : 200000.0, - minimumHeight : 100000.0 + minimumHeight : 100000.0, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.RED) @@ -62,7 +63,8 @@ -107.0, 40.0, -107.0, 43.0 ]), - maximumHeight : 100000.0 + maximumHeight : 100000.0, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), attributes : { color : Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.GREEN) @@ -93,7 +95,8 @@ geometry : new Cesium.WallGeometry({ positions : positions, maximumHeights: maximumHeights, - minimumHeights: minimumHeights + minimumHeights: minimumHeights, + vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT }), attributes : { diff --git a/CHANGES.md b/CHANGES.md index a6528282a2c9..8179a3a63e1d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Change Log * Fixed a bug that caused non-base imagery layers with a limited `rectangle` to be stretched to the edges of imagery tiles. [#416](https://github.com/AnalyticalGraphicsInc/cesium/issues/416) * Fixed rendering polylines with duplicate positions. [#898](https://github.com/AnalyticalGraphicsInc/cesium/issues/898) * Added support to the `CesiumTerrainProvider` for handling terrain tiles that define more than 64k vertices. +* Added `Primitive.compressVertices`. When true, the geometry vertices are compressed, which will save memory. * Upgraded topojson from 1.6.8 to 1.6.18. ### 1.2 - 2014-10-01 diff --git a/Source/Core/GeometryPipeline.js b/Source/Core/GeometryPipeline.js index 379827c47634..028b7e509835 100644 --- a/Source/Core/GeometryPipeline.js +++ b/Source/Core/GeometryPipeline.js @@ -20,6 +20,7 @@ define([ './Math', './Matrix3', './Matrix4', + './Oct', './Plane', './PrimitiveType', './Tipsify' @@ -44,6 +45,7 @@ define([ CesiumMath, Matrix3, Matrix4, + Oct, Plane, PrimitiveType, Tipsify) { @@ -275,7 +277,11 @@ define([ 'normal', 'st', 'binormal', - 'tangent' + 'tangent', + + // From compressing normals + 'stCompressedNormals', + 'compressedNormals' ]; var attributes = geometry.attributes; @@ -1301,6 +1307,117 @@ define([ return geometry; }; + var scratchPacked = new Cartesian2(); + var toEncode1 = new Cartesian3(); + var toEncode2 = new Cartesian3(); + var toEncode3 = new Cartesian3(); + + /** + * Compresses and packs geometry normal attribute values to save memory. + * + * @param {Geometry} geometry The geometry to modify. + * @returns {Geometry} The modified geometry argument, with its normals compressed and packed. + * + * @example + * geometry = Cesium.GeometryPipeline.compressNormals(geometry); + */ + GeometryPipeline.compressNormals = function(geometry) { + //>>includeStart('debug', pragmas.debug); + if (!defined(geometry)) { + throw new DeveloperError('geometry is required.'); + } + //>>includeEnd('debug'); + + var normalAttribute = geometry.attributes.normal; + if (!defined(normalAttribute)) { + return geometry; + } + + var stAttribute = geometry.attributes.st; + var tangentAttribute = geometry.attributes.tangent; + var binormalAttribute = geometry.attributes.binormal; + + var normals = normalAttribute.values; + var st; + var tangents; + var binormals; + + + if (defined(stAttribute)) { + st = stAttribute.values; + } + if (defined(tangentAttribute)) { + tangents = tangentAttribute.values; + } + if (binormalAttribute) { + binormals = binormalAttribute.values; + } + + var length = normals.length; + var compressedLength = length / 3.0; + var numComponents = 1.0; + numComponents += defined(st) ? 2.0 : 0.0; + numComponents += defined(tangents) || defined(binormals) ? 1.0 : 0.0; + compressedLength *= numComponents; + var compressedNormals = new Float32Array(compressedLength); + + var normalIndex = 0; + var stIndex = 0; + var tangentsIndex = 0; + + for (var i = 0; i < length; i += 3) { + if (defined(st)) { + compressedNormals[normalIndex++] = st[stIndex++]; + compressedNormals[normalIndex++] = st[stIndex++]; + } + + if (defined(tangents) && defined(binormals)) { + Cartesian3.fromArray(normals, i, toEncode1); + Cartesian3.fromArray(tangents, i, toEncode2); + Cartesian3.fromArray(binormals, i, toEncode3); + + Oct.pack(toEncode1, toEncode2, toEncode3, scratchPacked); + compressedNormals[normalIndex++] = scratchPacked.x; + compressedNormals[normalIndex++] = scratchPacked.y; + } else { + Cartesian3.fromArray(normals, i, toEncode1); + compressedNormals[normalIndex++] = Oct.encodeFloat(toEncode1); + + if (defined(tangents)) { + Cartesian3.fromArray(tangents, i, toEncode1); + compressedNormals[normalIndex++] = Oct.encodeFloat(toEncode1); + } + + if (defined(binormals)) { + Cartesian3.fromArray(binormals, i, toEncode1); + compressedNormals[normalIndex++] = Oct.encodeFloat(toEncode1); + } + } + } + + var attributeName = defined(st) ? 'stCompressedNormals' : 'compressedNormals'; + geometry.attributes[attributeName] = new GeometryAttribute({ + componentDatatype : ComponentDatatype.FLOAT, + componentsPerAttribute : numComponents, + values : compressedNormals + }); + delete geometry.attributes.normal; + + if (defined(st)) { + delete geometry.attributes.st; + } + + if (defined(tangents)) { + delete geometry.attributes.tangent; + } + + if (defined(binormals)) { + delete geometry.attributes.binormal; + } + + return geometry; + }; + function indexTriangles(geometry) { if (defined(geometry.indices)) { return geometry; diff --git a/Source/Core/Oct.js b/Source/Core/Oct.js index 732322321f8b..25b5b4fcc6d1 100644 --- a/Source/Core/Oct.js +++ b/Source/Core/Oct.js @@ -121,20 +121,110 @@ define([ }; /** - * Decodes a unit-length vector in 'oct' encoding to a normalized 3-component vector. + * Decodes a unit-length vector in 'oct' encoding packed in a floating-point number to a normalized 3-component vector. * * @param {Number} value The oct-encoded unit length vector stored as a single floating-point number. * @param {Cartesian3} result The decoded and normalized vector * @returns {Cartesian3} The decoded and normalized vector. * + * @exception {DeveloperError} value must be defined. * @exception {DeveloperError} result must be defined. */ Oct.decodeFloat = function(value, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(value)) { + throw new DeveloperError('value is required.'); + } + //>>includeEnd('debug'); + var temp = value / 256.0; var x = Math.floor(temp); var y = (temp - x) * 256.0; + return Oct.decode(x, y, result); }; + /** + * Encodes three normalized vectors into 6 SNORM values in the range of [0-255] following the 'oct' encoding and + * packs those into two floating-point numbers. + * + * @param {Cartesian3} v1 A normalized vector to be compressed. + * @param {Cartesian3} v2 A normalized vector to be compressed. + * @param {Cartesian3} v3 A normalized vector to be compressed. + * @param {Cartesian2} result The 'oct' encoded vectors packed into two floating-point numbers. + * @returns {Cartesian2} The 'oct' encoded vectors packed into two floating-point numbers. + * + * @exception {DeveloperError} v1 must be defined. + * @exception {DeveloperError} v2 must be defined. + * @exception {DeveloperError} v3 must be defined. + * @exception {DeveloperError} result must be defined. + */ + Oct.pack = function(v1, v2, v3, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(v1)) { + throw new DeveloperError('v1 is required.'); + } + if (!defined(v2)) { + throw new DeveloperError('v2 is required.'); + } + if (!defined(v3)) { + throw new DeveloperError('v3 is required.'); + } + if (!defined(result)) { + throw new DeveloperError('result is required.'); + } + //>>includeEnd('debug'); + + var encoded1 = Oct.encodeFloat(v1); + var encoded2 = Oct.encodeFloat(v2); + + var encoded3 = Oct.encode(v3, scratchEncodeCart2); + result.x = 65536.0 * encoded3.x + encoded1; + result.y = 65536.0 * encoded3.y + encoded2; + return result; + }; + + /** + * Decodes three unit-length vectors in 'oct' encoding packed into a floating-point number to a normalized 3-component vector. + * + * @param {Cartesian2} packed The three oct-encoded unit length vectors stored as two floating-point number. + * @param {Cartesian3} v1 One decoded and normalized vector. + * @param {Cartesian3} v2 One decoded and normalized vector. + * @param {Cartesian3} v3 One decoded and normalized vector. + * + * @exception {DeveloperError} packed must be defined. + * @exception {DeveloperError} v1 must be defined. + * @exception {DeveloperError} v2 must be defined. + * @exception {DeveloperError} v3 must be defined. + */ + Oct.unpack = function(packed, v1, v2, v3) { + //>>includeStart('debug', pragmas.debug); + if (!defined(packed)) { + throw new DeveloperError('packed is required.'); + } + if (!defined(v1)) { + throw new DeveloperError('v1 is required.'); + } + if (!defined(v2)) { + throw new DeveloperError('v2 is required.'); + } + if (!defined(v3)) { + throw new DeveloperError('v3 is required.'); + } + //>>includeEnd('debug'); + + var temp = packed.x / 65536.0; + var x = Math.floor(temp); + var encodedFloat1 = (temp - x) * 65536.0; + + temp = packed.y / 65536.0; + var y = Math.floor(temp); + var encodedFloat2 = (temp - y) * 65536.0; + + Oct.decodeFloat(encodedFloat1, v1); + Oct.decodeFloat(encodedFloat2, v2); + Oct.decode(x, y, v3); + }; + return Oct; -}); +}); \ No newline at end of file diff --git a/Source/Scene/Primitive.js b/Source/Scene/Primitive.js index 75669f6d0ccb..59a10dd6a458 100644 --- a/Source/Scene/Primitive.js +++ b/Source/Scene/Primitive.js @@ -87,6 +87,7 @@ define([ * @param {Boolean} [options.show=true] Determines if this primitive will be shown. * @param {Boolean} [options.vertexCacheOptimize=false] When true, geometry vertices are optimized for the pre and post-vertex-shader caches. * @param {Boolean} [options.interleave=false] When true, geometry vertex attributes are interleaved, which can slightly improve rendering performance but increases load time. + * @param {Boolean} [options.compressVertices=true] When true, the geometry vertices are compressed, which will save memory. * @param {Boolean} [options.releaseGeometryInstances=true] When true, the primitive does not keep a reference to the input geometryInstances to save memory. * @param {Boolean} [options.allowPicking=true] When true, each geometry instance will only be pickable with {@link Scene#pick}. When false, GPU memory is saved. * @param {Boolean} [options.asynchronous=true] Determines if the primitive will be created asynchronously or block until ready. @@ -228,6 +229,7 @@ define([ this._releaseGeometryInstances = defaultValue(options.releaseGeometryInstances, true); this._allowPicking = defaultValue(options.allowPicking, true); this._asynchronous = defaultValue(options.asynchronous, true); + this._compressVertices = defaultValue(options.compressVertices, true); /** * This property is for debugging only; it is not for production use nor is it optimized. @@ -357,6 +359,22 @@ define([ } }, + /** + * When true, geometry vertices are compressed, which will save memory. + * + * @memberof Primitive.prototype + * + * @type {Boolean} + * @readonly + * + * @default true + */ + compressVertices : { + get : function() { + return this._compressVertices; + } + }, + /** * Determines if the primitive is complete and ready to render. If this property is * true, the primitive will be rendered the next time that {@link Primitive#update} @@ -519,6 +537,72 @@ define([ return renamedVS + '\n' + showMain; } + function modifyForEncodedNormals(primitive, vertexShaderSource) { + if (!primitive.compressVertices) { + return vertexShaderSource; + } + + var containsNormal = vertexShaderSource.search(/attribute\s+vec3\s+normal;/g) !== -1; + if (!containsNormal) { + return vertexShaderSource; + } + + var containsSt = vertexShaderSource.search(/attribute\s+vec2\s+st;/g) !== -1; + var containsTangent = vertexShaderSource.search(/attribute\s+vec3\s+tangent;/g) !== -1; + var containsBinormal = vertexShaderSource.search(/attribute\s+vec3\s+binormal;/g) !== -1; + + var numComponents = 1; + numComponents += containsSt ? 2 : 0; + numComponents += containsTangent || containsBinormal ? 1 : 0; + + var type = (numComponents > 1) ? 'vec' + numComponents : 'float'; + + var attributeName = containsSt ? 'stCompressedNormals' : 'compressedNormals'; + var attributeDecl = 'attribute ' + type + ' ' + attributeName + ';'; + + var globalDecl = 'vec3 normal;\n'; + var decode = ''; + + if (containsSt) { + globalDecl += 'vec2 st;\n'; + decode += ' st = ' + attributeName + '.xy;\n'; + } + + if (containsTangent && containsBinormal) { + globalDecl += + 'vec3 tangent;\n' + + 'vec3 binormal;\n'; + decode += ' czm_octDecode(' + attributeName + '.' + (containsSt ? 'zw' : 'xy') + ', normal, tangent, binormal);\n'; + } else { + decode += ' normal = czm_octDecode(' + attributeName + (numComponents > 1 ? '.' + (containsSt ? 'z' : 'x') : '') + ');\n'; + + if (containsTangent) { + globalDecl += 'vec3 tangent;\n'; + decode += ' tangent = czm_octDecode(' + attributeName + '.' + (containsSt ? 'w' : 'y') + ');\n'; + } + + if (containsBinormal) { + globalDecl += 'vec3 binormal;\n'; + decode += ' binormal = czm_octDecode(' + attributeName + '.' + (containsSt ? 'w' : 'y') + ');\n'; + } + } + + var modifiedVS = vertexShaderSource; + modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+normal;/g, ''); + modifiedVS = modifiedVS.replace(/attribute\s+vec2\s+st;/g, ''); + modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+tangent;/g, ''); + modifiedVS = modifiedVS.replace(/attribute\s+vec3\s+binormal;/g, ''); + modifiedVS = modifiedVS.replace(/void\s+main\s*\(\s*(?:void)?\s*\)/g, 'void czm_non_compressed_main()'); + var compressedMain = + 'void main() \n' + + '{ \n' + + decode + + ' czm_non_compressed_main(); \n' + + '}'; + + return createShaderSource({ sources : [attributeDecl, globalDecl, modifiedVS, compressedMain] }); + } + function validateShaderMatching(shaderProgram, attributeLocations) { // For a VAO and shader program to be compatible, the VAO must have // all active attribute in the shader program. The VAO may have @@ -662,6 +746,7 @@ define([ scene3DOnly : scene3DOnly, allowPicking : allowPicking, vertexCacheOptimize : this.vertexCacheOptimize, + compressVertices : this.compressVertices, modelMatrix : this.modelMatrix }, transferableObjects), transferableObjects); @@ -711,6 +796,7 @@ define([ scene3DOnly : scene3DOnly, allowPicking : allowPicking, vertexCacheOptimize : this.vertexCacheOptimize, + compressVertices : this.compressVertices, modelMatrix : this.modelMatrix }); @@ -847,6 +933,7 @@ define([ if (createSP) { var vs = createColumbusViewShader(this, appearance.vertexShaderSource, scene3DOnly); vs = appendShow(this, vs); + vs = modifyForEncodedNormals(this, vs); var fs = appearance.getFragmentShaderSource(); this._sp = context.replaceShaderProgram(this._sp, vs, fs, attributeLocations); @@ -1151,4 +1238,4 @@ define([ }; return Primitive; -}); +}); \ No newline at end of file diff --git a/Source/Scene/PrimitivePipeline.js b/Source/Scene/PrimitivePipeline.js index d8c822a103c5..90dbf7265f68 100644 --- a/Source/Scene/PrimitivePipeline.js +++ b/Source/Scene/PrimitivePipeline.js @@ -172,6 +172,7 @@ define([ var scene3DOnly = parameters.scene3DOnly; var allowPicking = parameters.allowPicking; var vertexCacheOptimize = parameters.vertexCacheOptimize; + var compressVertices = parameters.compressVertices; var modelMatrix = parameters.modelMatrix; var i; @@ -240,6 +241,11 @@ define([ } } + // oct encode and pack normals + if (compressVertices) { + GeometryPipeline.compressNormals(geometry); + } + if (!uintIndexSupport) { // Break into multiple geometries to fit within unsigned short indices if needed return GeometryPipeline.fitToUnsignedShortIndices(geometry); @@ -836,6 +842,7 @@ define([ scene3DOnly : parameters.scene3DOnly, allowPicking : parameters.allowPicking, vertexCacheOptimize : parameters.vertexCacheOptimize, + compressVertices : parameters.compressVertices, modelMatrix : parameters.modelMatrix }; }; @@ -870,6 +877,7 @@ define([ scene3DOnly : packedParameters.scene3DOnly, allowPicking : packedParameters.allowPicking, vertexCacheOptimize : packedParameters.vertexCacheOptimize, + compressVertices : packedParameters.compressVertices, modelMatrix : Matrix4.clone(packedParameters.modelMatrix) }; }; diff --git a/Source/Shaders/Builtin/Functions/octDecode.glsl b/Source/Shaders/Builtin/Functions/octDecode.glsl index f7863aa7c04f..aecce360a133 100644 --- a/Source/Shaders/Builtin/Functions/octDecode.glsl +++ b/Source/Shaders/Builtin/Functions/octDecode.glsl @@ -20,7 +20,7 @@ } /** - * Decodes a unit-length vector in 'oct' encoding to a normalized 3-component Cartesian vector. + * Decodes a unit-length vector in 'oct' encoding packed into a floating-point number to a normalized 3-component Cartesian vector. * The 'oct' encoding is described in "A Survey of Efficient Representations of Independent Unit Vectors", * Cigolle et al 2014: http://jcgt.org/published/0003/02/01/ * @@ -35,4 +35,30 @@ float y = (temp - x) * 256.0; return czm_octDecode(vec2(x, y)); } + +/** + * Decodes three unit-length vectors in 'oct' encoding packed into two floating-point numbers to normalized 3-component Cartesian vectors. + * The 'oct' encoding is described in "A Survey of Efficient Representations of Independent Unit Vectors", + * Cigolle et al 2014: http://jcgt.org/published/0003/02/01/ + * + * @name czm_octDecode + * @param {vec2} encoded The packed oct-encoded, unit-length vectors. + * @param {vec3} vector1 One decoded and normalized vector. + * @param {vec3} vector2 One decoded and normalized vector. + * @param {vec3} vector3 One decoded and normalized vector. + */ + void czm_octDecode(vec2 encoded, out vec3 vector1, out vec3 vector2, out vec3 vector3) + { + float temp = encoded.x / 65536.0; + float x = floor(temp); + float encodedFloat1 = (temp - x) * 65536.0; + + temp = encoded.y / 65536.0; + float y = floor(temp); + float encodedFloat2 = (temp - y) * 65536.0; + + vector1 = czm_octDecode(encodedFloat1); + vector2 = czm_octDecode(encodedFloat2); + vector3 = czm_octDecode(vec2(x, y)); + } \ No newline at end of file diff --git a/Specs/Core/GeometryPipelineSpec.js b/Specs/Core/GeometryPipelineSpec.js index 11f27817ed9d..df2aa85a8633 100644 --- a/Specs/Core/GeometryPipelineSpec.js +++ b/Specs/Core/GeometryPipelineSpec.js @@ -3,6 +3,7 @@ defineSuite([ 'Core/GeometryPipeline', 'Core/BoundingSphere', 'Core/BoxGeometry', + 'Core/Cartesian2', 'Core/Cartesian3', 'Core/ComponentDatatype', 'Core/Ellipsoid', @@ -14,6 +15,7 @@ defineSuite([ 'Core/GeometryInstance', 'Core/Math', 'Core/Matrix4', + 'Core/Oct', 'Core/PrimitiveType', 'Core/Tipsify', 'Core/VertexFormat' @@ -21,6 +23,7 @@ defineSuite([ GeometryPipeline, BoundingSphere, BoxGeometry, + Cartesian2, Cartesian3, ComponentDatatype, Ellipsoid, @@ -32,6 +35,7 @@ defineSuite([ GeometryInstance, CesiumMath, Matrix4, + Oct, PrimitiveType, Tipsify, VertexFormat) { @@ -1632,6 +1636,121 @@ defineSuite([ } }); + it('compressNormals throws without geometry', function() { + expect(function() { + return GeometryPipeline.compressNormals(); + }).toThrowDeveloperError(); + }); + + it('compressNormals on geometry without normals does nothing', function() { + var geometry = BoxGeometry.createGeometry(new BoxGeometry({ + vertexFormat : new VertexFormat({ + position : true + }), + maximumCorner : new Cartesian3(250000.0, 250000.0, 250000.0), + minimumCorner : new Cartesian3(-250000.0, -250000.0, -250000.0) + })); + expect(geometry.attributes.normal).not.toBeDefined(); + geometry = GeometryPipeline.compressNormals(geometry); + expect(geometry.attributes.normal).not.toBeDefined(); + }); + + it('compressNormals compresses normals', function() { + var geometry = BoxGeometry.createGeometry(new BoxGeometry({ + vertexFormat : new VertexFormat({ + position : true, + normal : true + }), + maximumCorner : new Cartesian3(250000.0, 250000.0, 250000.0), + minimumCorner : new Cartesian3(-250000.0, -250000.0, -250000.0) + })); + expect(geometry.attributes.normal).toBeDefined(); + var originalNormals = Array.prototype.slice.call(geometry.attributes.normal.values); + + geometry = GeometryPipeline.compressNormals(geometry); + + expect(geometry.attributes.compressedNormals).toBeDefined(); + + var normals = geometry.attributes.compressedNormals.values; + expect(normals.length).toEqual(originalNormals.length / 3); + + for (var i = 0; i < normals.length; ++i) { + expect(Oct.decodeFloat(normals[i], new Cartesian3())).toEqualEpsilon(Cartesian3.fromArray(originalNormals, i * 3), CesiumMath.EPSILON2); + } + }); + + it('compressNormals packs compressed normals with texture coordinates', function() { + var geometry = BoxGeometry.createGeometry(new BoxGeometry({ + vertexFormat : new VertexFormat({ + position : true, + normal : true, + st : true + }), + maximumCorner : new Cartesian3(250000.0, 250000.0, 250000.0), + minimumCorner : new Cartesian3(-250000.0, -250000.0, -250000.0) + })); + expect(geometry.attributes.normal).toBeDefined(); + expect(geometry.attributes.st).toBeDefined(); + var originalNormals = Array.prototype.slice.call(geometry.attributes.normal.values); + var originalST = Array.prototype.slice.call(geometry.attributes.st.values); + + geometry = GeometryPipeline.compressNormals(geometry); + + expect(geometry.attributes.normal).not.toBeDefined(); + expect(geometry.attributes.st).not.toBeDefined(); + expect(geometry.attributes.stCompressedNormals).toBeDefined(); + + var stNormal = geometry.attributes.stCompressedNormals.values; + expect(stNormal.length).toEqual(originalNormals.length); + + for (var i = 0; i < stNormal.length; i += 3) { + expect(stNormal[i]).toEqual(originalST[i / 3 * 2]); + expect(stNormal[i + 1]).toEqual(originalST[i / 3 * 2 + 1]); + expect(Oct.decodeFloat(stNormal[i + 2], new Cartesian3())).toEqualEpsilon(Cartesian3.fromArray(originalNormals, i), CesiumMath.EPSILON2); + } + }); + + it('compressNormals packs compressed tangents and binormals', function() { + var geometry = BoxGeometry.createGeometry(new BoxGeometry({ + vertexFormat : new VertexFormat({ + position : true, + normal : true, + tangent : true, + binormal : true + }), + maximumCorner : new Cartesian3(250000.0, 250000.0, 250000.0), + minimumCorner : new Cartesian3(-250000.0, -250000.0, -250000.0) + })); + expect(geometry.attributes.normal).toBeDefined(); + expect(geometry.attributes.tangent).toBeDefined(); + expect(geometry.attributes.binormal).toBeDefined(); + var originalNormals = Array.prototype.slice.call(geometry.attributes.normal.values); + var originalTangents = Array.prototype.slice.call(geometry.attributes.tangent.values); + var originalBinormals = Array.prototype.slice.call(geometry.attributes.binormal.values); + + geometry = GeometryPipeline.compressNormals(geometry); + + expect(geometry.attributes.tangent).not.toBeDefined(); + expect(geometry.attributes.binormal).not.toBeDefined(); + expect(geometry.attributes.compressedNormals).toBeDefined(); + + var compressedNormals = geometry.attributes.compressedNormals.values; + expect(compressedNormals.length).toEqual(originalNormals.length / 3 * 2); + + var normal = new Cartesian3(); + var tangent = new Cartesian3(); + var binormal = new Cartesian3(); + + for (var i = 0; i < compressedNormals.length; i += 2) { + var compressed = Cartesian2.fromArray(compressedNormals, i, new Cartesian2()); + Oct.unpack(compressed, normal, tangent, binormal); + + expect(normal).toEqualEpsilon(Cartesian3.fromArray(originalNormals, i / 2 * 3), CesiumMath.EPSILON2); + expect(tangent).toEqualEpsilon(Cartesian3.fromArray(originalTangents, i / 2 * 3), CesiumMath.EPSILON2); + expect(binormal).toEqualEpsilon(Cartesian3.fromArray(originalBinormals, i / 2 * 3), CesiumMath.EPSILON2); + } + }); + it('wrapLongitude provides indices for an un-indexed triangle list', function() { var geometry = new Geometry({ attributes : { diff --git a/Specs/Core/OctSpec.js b/Specs/Core/OctSpec.js index 3fda01ca2736..d1af65ef183c 100644 --- a/Specs/Core/OctSpec.js +++ b/Specs/Core/OctSpec.js @@ -322,4 +322,86 @@ defineSuite([ Oct.decodeFloat(Oct.encodeFloat(normal), result2); expect(result1).toEqual(result2); }); + + it('encodeFloat throws without vector', function() { + expect(function() { + Oct.encodeFloat(undefined); + }).toThrowDeveloperError(); + }); + + it('decodeFloat throws without value', function() { + expect(function() { + Oct.decodeFloat(undefined, new Cartesian3()); + }).toThrowDeveloperError(); + }); + + it('decodeFloat throws without result', function() { + expect(function() { + Oct.decodeFloat(0.0, undefined); + }).toThrowDeveloperError(); + }); + + it('pack is equivalent to oct encoding', function() { + var x = Cartesian3.UNIT_X; + var y = Cartesian3.UNIT_Y; + var z = Cartesian3.UNIT_Z; + + var packed = Oct.pack(x, y, z, new Cartesian2()); + var decodedX = new Cartesian3(); + var decodedY = new Cartesian3(); + var decodedZ = new Cartesian3(); + Oct.unpack(packed, decodedX, decodedY, decodedZ); + + expect(decodedX).toEqual(Oct.decodeFloat(Oct.encodeFloat(x), new Cartesian3())); + expect(decodedY).toEqual(Oct.decodeFloat(Oct.encodeFloat(y), new Cartesian3())); + expect(decodedZ).toEqual(Oct.decodeFloat(Oct.encodeFloat(z), new Cartesian3())); + }); + + it('pack throws without v1', function() { + expect(function() { + Oct.pack(undefined, new Cartesian3(), new Cartesian3(), new Cartesian2()); + }).toThrowDeveloperError(); + }); + + it('pack throws without v2', function() { + expect(function() { + Oct.pack(new Cartesian3(), undefined, new Cartesian3(), new Cartesian2()); + }).toThrowDeveloperError(); + }); + + it('pack throws without v3', function() { + expect(function() { + Oct.pack(new Cartesian3(), new Cartesian3(), undefined, new Cartesian2()); + }).toThrowDeveloperError(); + }); + + it('pack throws without result', function() { + expect(function() { + Oct.pack(new Cartesian3(), new Cartesian3(), new Cartesian3(), undefined); + }).toThrowDeveloperError(); + }); + + it('unpack throws without packed', function() { + expect(function() { + Oct.unpack(undefined, new Cartesian3(), new Cartesian3(), new Cartesian3()); + }).toThrowDeveloperError(); + }); + + it('unpack throws without v1', function() { + expect(function() { + Oct.unpack(new Cartesian2(), undefined, new Cartesian3(), new Cartesian3()); + }).toThrowDeveloperError(); + }); + + it('unpack throws without v2', function() { + expect(function() { + Oct.unpack(new Cartesian2(), new Cartesian3(), undefined, new Cartesian3()); + }).toThrowDeveloperError(); + }); + + it('unpack throws without v3', function() { + expect(function() { + Oct.unpack(new Cartesian2(), new Cartesian3(), new Cartesian3(), undefined); + }).toThrowDeveloperError(); + }); }); diff --git a/Specs/Renderer/BuiltinFunctionsSpec.js b/Specs/Renderer/BuiltinFunctionsSpec.js index 989bf7a05eae..d588eccf0769 100644 --- a/Specs/Renderer/BuiltinFunctionsSpec.js +++ b/Specs/Renderer/BuiltinFunctionsSpec.js @@ -227,7 +227,7 @@ defineSuite([ it('has czm_octDecode(vec2)', function() { var fs = 'void main() { ' + - ' gl_FragColor = vec4(czm_octDecode(vec2(0.0, 0.0)) == vec3(0.0, 0.0, 1.0)); ' + + ' gl_FragColor = vec4(all(lessThanEqual(abs(czm_octDecode(vec2(128.0, 128.0)) - vec3(0.0, 0.0, 1.0)), vec3(0.01)))); ' + '}'; verifyDraw(fs); }); @@ -235,7 +235,20 @@ defineSuite([ it('has czm_octDecode(float)', function() { var fs = 'void main() { ' + - ' gl_FragColor = vec4(czm_octDecode(0.0) == vec3(0.0, 0.0, 1.0)); ' + + ' gl_FragColor = vec4(all(lessThanEqual(abs(czm_octDecode(32896.0) - vec3(0.0, 0.0, 1.0)), vec3(0.01)))); ' + + '}'; + verifyDraw(fs); + }); + + it('has czm_octDecode(vec2, vec3, vec3, vec3)', function() { + var fs = + 'void main() { ' + + ' vec3 a, b, c;' + + ' czm_octDecode(vec2(8454016.0, 8421631.0), a, b, c);' + + ' bool decoded = all(lessThanEqual(abs(a - vec3(1.0, 0.0, 0.0)), vec3(0.01)));' + + ' decoded = decoded && all(lessThanEqual(abs(b - vec3(0.0, 1.0, 0.0)), vec3(0.01)));' + + ' decoded = decoded && all(lessThanEqual(abs(c - vec3(0.0, 0.0, 1.0)), vec3(0.01)));' + + ' gl_FragColor = vec4(decoded);' + '}'; verifyDraw(fs); }); diff --git a/Specs/Scene/DebugAppearanceSpec.js b/Specs/Scene/DebugAppearanceSpec.js index b269d37eab73..524eacf392ef 100644 --- a/Specs/Scene/DebugAppearanceSpec.js +++ b/Specs/Scene/DebugAppearanceSpec.js @@ -2,6 +2,7 @@ defineSuite([ 'Scene/DebugAppearance', 'Core/ComponentDatatype', + 'Core/defaultValue', 'Core/GeometryInstance', 'Core/GeometryInstanceAttribute', 'Core/Rectangle', @@ -17,6 +18,7 @@ defineSuite([ ], function( DebugAppearance, ComponentDatatype, + defaultValue, GeometryInstance, GeometryInstanceAttribute, Rectangle, @@ -34,20 +36,12 @@ defineSuite([ var context; var frameState; - var rectangleInstance; + var rectangle = Rectangle.fromDegrees(-10.0, -10.0, 10.0, 10.0); beforeAll(function() { context = createContext(); frameState = createFrameState(); - var rectangle = Rectangle.fromDegrees(-10.0, -10.0, 10.0, 10.0); - rectangleInstance = new GeometryInstance({ - geometry : new RectangleGeometry({ - vertexFormat : VertexFormat.ALL, - rectangle : rectangle - }) - }); - frameState.camera.viewRectangle(rectangle); var us = context.uniformState; us.update(context, frameState); @@ -57,6 +51,15 @@ defineSuite([ destroyContext(context); }); + function createInstance(vertexFormat) { + return new GeometryInstance({ + geometry : new RectangleGeometry({ + vertexFormat : defaultValue(vertexFormat, VertexFormat.ALL), + rectangle : rectangle + }) + }); + } + it('constructor throws without attributeName', function() { expect(function() { return new DebugAppearance(); @@ -176,12 +179,17 @@ defineSuite([ }); it('renders normal', function() { + var vertexFormat = new VertexFormat({ + position : true, + normal : true + }); var primitive = new Primitive({ - geometryInstances : rectangleInstance, + geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ attributeName : 'normal' }), - asynchronous : false + asynchronous : false, + compressVertices : false }); ClearCommand.ALL.execute(context); @@ -194,12 +202,18 @@ defineSuite([ }); it('renders binormal', function() { + var vertexFormat = new VertexFormat({ + position : true, + normal : true, + binormal : true + }); var primitive = new Primitive({ - geometryInstances : rectangleInstance, + geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ attributeName : 'binormal' }), - asynchronous : false + asynchronous : false, + compressVertices : false }); ClearCommand.ALL.execute(context); @@ -212,12 +226,18 @@ defineSuite([ }); it('renders tangent', function() { + var vertexFormat = new VertexFormat({ + position : true, + normal : true, + tangent : true + }); var primitive = new Primitive({ - geometryInstances : rectangleInstance, + geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ attributeName : 'tangent' }), - asynchronous : false + asynchronous : false, + compressVertices : false }); ClearCommand.ALL.execute(context); @@ -230,12 +250,17 @@ defineSuite([ }); it('renders st', function() { + var vertexFormat = new VertexFormat({ + position : true, + st : true + }); var primitive = new Primitive({ - geometryInstances : rectangleInstance, + geometryInstances : createInstance(vertexFormat), appearance : new DebugAppearance({ attributeName : 'st' }), - asynchronous : false + asynchronous : false, + compressVertices : false }); ClearCommand.ALL.execute(context); @@ -248,6 +273,7 @@ defineSuite([ }); it('renders float', function() { + var rectangleInstance = createInstance(); rectangleInstance.attributes = { debug : new GeometryInstanceAttribute({ componentDatatype : ComponentDatatype.FLOAT, @@ -274,6 +300,7 @@ defineSuite([ }); it('renders vec2', function() { + var rectangleInstance = createInstance(); rectangleInstance.attributes = { debug : new GeometryInstanceAttribute({ componentDatatype : ComponentDatatype.FLOAT, @@ -300,6 +327,7 @@ defineSuite([ }); it('renders vec3', function() { + var rectangleInstance = createInstance(); rectangleInstance.attributes = { debug : new GeometryInstanceAttribute({ componentDatatype : ComponentDatatype.FLOAT, @@ -326,6 +354,7 @@ defineSuite([ }); it('renders vec4', function() { + var rectangleInstance = createInstance(); rectangleInstance.attributes = { debug : new GeometryInstanceAttribute({ componentDatatype : ComponentDatatype.FLOAT, diff --git a/Specs/Scene/EllipsoidSurfaceAppearanceSpec.js b/Specs/Scene/EllipsoidSurfaceAppearanceSpec.js index 6e58e805ed96..f013c76741e4 100644 --- a/Specs/Scene/EllipsoidSurfaceAppearanceSpec.js +++ b/Specs/Scene/EllipsoidSurfaceAppearanceSpec.js @@ -42,7 +42,8 @@ defineSuite([ primitive = new Primitive({ geometryInstances : new GeometryInstance({ geometry : new RectangleGeometry({ - rectangle : rectangle + rectangle : rectangle, + vertexFormat : EllipsoidSurfaceAppearance.VERTEX_FORMAT }), attributes : { color : new ColorGeometryInstanceAttribute(1.0, 1.0, 0.0, 1.0) diff --git a/Specs/Scene/MaterialAppearanceSpec.js b/Specs/Scene/MaterialAppearanceSpec.js index d3a446da4e3a..460b18e1d9bb 100644 --- a/Specs/Scene/MaterialAppearanceSpec.js +++ b/Specs/Scene/MaterialAppearanceSpec.js @@ -2,6 +2,7 @@ defineSuite([ 'Scene/MaterialAppearance', 'Core/ColorGeometryInstanceAttribute', + 'Core/defaultValue', 'Core/GeometryInstance', 'Core/Rectangle', 'Core/RectangleGeometry', @@ -16,6 +17,7 @@ defineSuite([ ], function( MaterialAppearance, ColorGeometryInstanceAttribute, + defaultValue, GeometryInstance, Rectangle, RectangleGeometry, @@ -33,16 +35,28 @@ defineSuite([ var context; var frameState; var primitive; + var rectangle = Rectangle.fromDegrees(-10.0, -10.0, 10.0, 10.0); beforeAll(function() { context = createContext(); frameState = createFrameState(); - var rectangle = Rectangle.fromDegrees(-10.0, -10.0, 10.0, 10.0); + frameState.camera.viewRectangle(rectangle); + var us = context.uniformState; + us.update(context, frameState); + }); + + afterAll(function() { + primitive = primitive && primitive.destroy(); + destroyContext(context); + }); + + function createPrimitive(vertexFormat) { + vertexFormat = defaultValue(vertexFormat, MaterialAppearance.MaterialSupport.ALL.vertexFormat); primitive = new Primitive({ geometryInstances : new GeometryInstance({ geometry : new RectangleGeometry({ - vertexFormat : MaterialAppearance.MaterialSupport.ALL.vertexFormat, + vertexFormat : vertexFormat, rectangle : rectangle }), attributes : { @@ -51,16 +65,7 @@ defineSuite([ }), asynchronous : false }); - - frameState.camera.viewRectangle(rectangle); - var us = context.uniformState; - us.update(context, frameState); - }); - - afterAll(function() { - primitive = primitive && primitive.destroy(); - destroyContext(context); - }); + } it('constructor', function() { var a = new MaterialAppearance(); @@ -79,6 +84,7 @@ defineSuite([ }); it('renders basic', function() { + createPrimitive(MaterialAppearance.MaterialSupport.BASIC.vertexFormat); primitive.appearance = new MaterialAppearance({ materialSupport : MaterialAppearance.MaterialSupport.BASIC, translucent : false, @@ -94,6 +100,7 @@ defineSuite([ }); it('renders textured', function() { + createPrimitive(MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat); primitive.appearance = new MaterialAppearance({ materialSupport : MaterialAppearance.MaterialSupport.TEXTURED, translucent : false, @@ -109,6 +116,7 @@ defineSuite([ }); it('renders all', function() { + createPrimitive(MaterialAppearance.MaterialSupport.ALL.vertexFormat); primitive.appearance = new MaterialAppearance({ materialSupport : MaterialAppearance.MaterialSupport.ALL, translucent : false, diff --git a/Specs/Scene/PrimitiveSpec.js b/Specs/Scene/PrimitiveSpec.js index 33a014bfeb5d..a82008c3329e 100644 --- a/Specs/Scene/PrimitiveSpec.js +++ b/Specs/Scene/PrimitiveSpec.js @@ -131,6 +131,7 @@ defineSuite([ expect(primitive.allowPicking).toEqual(true); expect(primitive.asynchronous).toEqual(true); expect(primitive.debugShowBoundingVolume).toEqual(false); + expect(primitive.compressVertices).toEqual(true); }); it('releases geometry instances when releaseGeometryInstances is true', function() { @@ -783,7 +784,9 @@ defineSuite([ it('renders when using asynchronous pipeline', function() { var primitive = new Primitive({ geometryInstances : rectangleInstance1, - appearance : new PerInstanceColorAppearance() + appearance : new PerInstanceColorAppearance({ + flat : true + }) }); frameState.camera.viewRectangle(rectangle1);