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);