diff --git a/CHANGES.md b/CHANGES.md index 17aebc23f41..61f7b89c1cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Change Log * Fixed an issue causing entities to disappear when updating multiple entities simultaneously. [#4096](https://github.com/AnalyticalGraphicsInc/cesium/issues/4096) * Added support in CZML for expressing `BillboardGraphics.alignedAxis` as the velocity vector of an entity, using `velocityReference` syntax. * Normalizing the velocity vector produced by `VelocityVectorProperty` is now optional. +* Added `AttributeCompression.octEncodeInRange`, `AttributeCompression.octDecodeInRange` and an optional `rangeMax` parameter to `Math.toSNorm` and `Math.fromSNorm`, to support oct-encoding with variable precision. [#4121](https://github.com/AnalyticalGraphicsInc/cesium/pull/4121) ### 1.23 - 2016-07-01 diff --git a/Source/Core/AttributeCompression.js b/Source/Core/AttributeCompression.js index cac2425a2ba..f4a41c5da94 100644 --- a/Source/Core/AttributeCompression.js +++ b/Source/Core/AttributeCompression.js @@ -2,12 +2,14 @@ define([ './Cartesian2', './Cartesian3', + './defaultValue', './defined', './DeveloperError', './Math' ], function( Cartesian2, Cartesian3, + defaultValue, defined, DeveloperError, CesiumMath) { @@ -23,21 +25,22 @@ define([ var AttributeCompression = {}; /** - * Encodes a normalized vector into 2 SNORM values in the range of [0-255] following the 'oct' encoding. + * Encodes a normalized vector into 2 SNORM values in the range of [0-rangeMax] following the 'oct' encoding. * - * Oct encoding is a compact representation of unit length vectors. The encoding and decoding functions are low cost, and represent the normalized vector within 1 degree of error. + * Oct encoding is a compact representation of unit length vectors. * The 'oct' encoding is described in "A Survey of Efficient Representations of Independent Unit Vectors", * Cigolle et al 2014: {@link http://jcgt.org/published/0003/02/01/} * - * @param {Cartesian3} vector The normalized vector to be compressed into 2 byte 'oct' encoding. - * @param {Cartesian2} result The 2 byte oct-encoded unit length vector. - * @returns {Cartesian2} The 2 byte oct-encoded unit length vector. + * @param {Cartesian3} vector The normalized vector to be compressed into 2 component 'oct' encoding. + * @param {Cartesian2} result The 2 component oct-encoded unit length vector. + * @param {Number} rangeMax The maximum value of the SNORM range. The encoded vector is stored in log2(rangeMax+1) bits. + * @returns {Cartesian2} The 2 component oct-encoded unit length vector. * * @exception {DeveloperError} vector must be normalized. * - * @see AttributeCompression.octDecode + * @see AttributeCompression.octDecodeInRange */ - AttributeCompression.octEncode = function(vector, result) { + AttributeCompression.octEncodeInRange = function(vector, rangeMax, result) { //>>includeStart('debug', pragmas.debug); if (!defined(vector)) { throw new DeveloperError('vector is required.'); @@ -60,36 +63,53 @@ define([ result.y = (1.0 - Math.abs(x)) * CesiumMath.signNotZero(y); } - result.x = CesiumMath.toSNorm(result.x); - result.y = CesiumMath.toSNorm(result.y); + result.x = CesiumMath.toSNorm(result.x, rangeMax); + result.y = CesiumMath.toSNorm(result.y, rangeMax); return result; }; + /** + * Encodes a normalized vector into 2 SNORM values in the range of [0-255] following the 'oct' encoding. + * + * @param {Cartesian3} vector The normalized vector to be compressed into 2 byte 'oct' encoding. + * @param {Cartesian2} result The 2 byte oct-encoded unit length vector. + * @returns {Cartesian2} The 2 byte oct-encoded unit length vector. + * + * @exception {DeveloperError} vector must be normalized. + * + * @see AttributeCompression.octEncodeInRange + * @see AttributeCompression.octDecode + */ + AttributeCompression.octEncode = function(vector, result) { + return AttributeCompression.octEncodeInRange(vector, 255, result); + }; + /** * Decodes a unit-length vector in 'oct' encoding to a normalized 3-component vector. * * @param {Number} x The x component of the oct-encoded unit length vector. * @param {Number} y The y component of the oct-encoded unit length vector. + * @param {Number} rangeMax The maximum value of the SNORM range. The encoded vector is stored in log2(rangeMax+1) bits. * @param {Cartesian3} result The decoded and normalized vector * @returns {Cartesian3} The decoded and normalized vector. * - * @exception {DeveloperError} x and y must be a signed normalized integer between 0 and 255. + * @exception {DeveloperError} x and y must be an unsigned normalized integer between 0 and rangeMax. * - * @see AttributeCompression.octEncode + * @see AttributeCompression.octEncodeInRange */ - AttributeCompression.octDecode = function(x, y, result) { + AttributeCompression.octDecodeInRange = function(x, y, rangeMax, result) { //>>includeStart('debug', pragmas.debug); if (!defined(result)) { throw new DeveloperError('result is required.'); } - if (x < 0 || x > 255 || y < 0 || y > 255) { - throw new DeveloperError('x and y must be a signed normalized integer between 0 and 255'); + if (x < 0 || x > rangeMax || y < 0 || y > rangeMax) { + throw new DeveloperError('x and y must be a signed normalized integer between 0 and ' + rangeMax); } //>>includeEnd('debug'); - result.x = CesiumMath.fromSNorm(x); - result.y = CesiumMath.fromSNorm(y); + result.x = CesiumMath.fromSNorm(x, rangeMax); + result.y = CesiumMath.fromSNorm(y, rangeMax); result.z = 1.0 - (Math.abs(result.x) + Math.abs(result.y)); if (result.z < 0.0) @@ -102,6 +122,22 @@ define([ return Cartesian3.normalize(result, result); }; + /** + * Decodes a unit-length vector in 2 byte 'oct' encoding to a normalized 3-component vector. + * + * @param {Number} x The x component of the oct-encoded unit length vector. + * @param {Number} y The y component of the oct-encoded unit length vector. + * @param {Cartesian3} result The decoded and normalized vector. + * @returns {Cartesian3} The decoded and normalized vector. + * + * @exception {DeveloperError} x and y must be an unsigned normalized integer between 0 and 255. + * + * @see AttributeCompression.octDecodeInRange + */ + AttributeCompression.octDecode = function(x, y, result) { + return AttributeCompression.octDecodeInRange(x, y, 255, result); + }; + /** * Packs an oct encoded vector into a single floating-point number. * diff --git a/Source/Core/Math.js b/Source/Core/Math.js index 0e06c0abe85..7ca053d20e5 100644 --- a/Source/Core/Math.js +++ b/Source/Core/Math.js @@ -218,25 +218,29 @@ define([ }; /** - * Converts a scalar value in the range [-1.0, 1.0] to a 8-bit 2's complement number. + * Converts a scalar value in the range [-1.0, 1.0] to a SNORM in the range [0, rangeMax] * @param {Number} value The scalar value in the range [-1.0, 1.0] - * @returns {Number} The 8-bit 2's complement number, where 0 maps to -1.0 and 255 maps to 1.0. + * @param {Number} [rangeMax=255] The maximum value in the mapped range, 255 by default. + * @returns {Number} A SNORM value, where 0 maps to -1.0 and rangeMax maps to 1.0. * * @see CesiumMath.fromSNorm */ - CesiumMath.toSNorm = function(value) { - return Math.round((CesiumMath.clamp(value, -1.0, 1.0) * 0.5 + 0.5) * 255.0); + CesiumMath.toSNorm = function(value, rangeMax) { + rangeMax = defaultValue(rangeMax, 255); + return Math.round((CesiumMath.clamp(value, -1.0, 1.0) * 0.5 + 0.5) * rangeMax); }; /** - * Converts a SNORM value in the range [0, 255] to a scalar in the range [-1.0, 1.0]. + * Converts a SNORM value in the range [0, rangeMax] to a scalar in the range [-1.0, 1.0]. * @param {Number} value SNORM value in the range [0, 255] + * @param {Number} [rangeMax=255] The maximum value in the SNORM range, 255 by default. * @returns {Number} Scalar in the range [-1.0, 1.0]. * * @see CesiumMath.toSNorm */ - CesiumMath.fromSNorm = function(value) { - return CesiumMath.clamp(value, 0.0, 255.0) / 255.0 * 2.0 - 1.0; + CesiumMath.fromSNorm = function(value, rangeMax) { + rangeMax = defaultValue(rangeMax, 255); + return CesiumMath.clamp(value, 0.0, rangeMax) / rangeMax * 2.0 - 1.0; }; /** diff --git a/Specs/Core/AttributeCompressionSpec.js b/Specs/Core/AttributeCompressionSpec.js index 11313da6e7b..1a37151f338 100644 --- a/Specs/Core/AttributeCompressionSpec.js +++ b/Specs/Core/AttributeCompressionSpec.js @@ -79,23 +79,21 @@ defineSuite([ it('throws oct decode result undefined', function() { var result; expect(function() { - AttributeCompression.octDecode(Cartesian2.ZERO, result); + AttributeCompression.octDecode(0, 0, result); }).toThrowDeveloperError(); }); it('throws oct decode x out of bounds', function() { var result = new Cartesian3(); - var invalidSNorm = new Cartesian2(256, 0); expect(function() { - AttributeCompression.octDecode(invalidSNorm, result); + AttributeCompression.octDecode(256, 0, result); }).toThrowDeveloperError(); }); it('throws oct decode y out of bounds', function() { var result = new Cartesian3(); - var invalidSNorm = new Cartesian2(0, 256); expect(function() { - AttributeCompression.octDecode(invalidSNorm, result); + AttributeCompression.octDecode(0, 256, result); }).toThrowDeveloperError(); }); @@ -169,6 +167,77 @@ defineSuite([ expect(AttributeCompression.octDecode(encoded.x, encoded.y, result)).toEqualEpsilon(normal, epsilon); }); + it('oct encoding high precision', function() { + var rangeMax = 4294967295; + var epsilon = CesiumMath.EPSILON8; + + var encoded = new Cartesian2(); + var result = new Cartesian3(); + var normal = new Cartesian3(0.0, 0.0, 1.0); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(0.0, 0.0, -1.0); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(0.0, 1.0, 0.0); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(0.0, -1.0, 0.0); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(1.0, 0.0, 0.0); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(-1.0, 0.0, 0.0); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(1.0, 1.0, 1.0); + Cartesian3.normalize(normal, normal); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(1.0, -1.0, 1.0); + Cartesian3.normalize(normal, normal); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(-1.0, -1.0, 1.0); + Cartesian3.normalize(normal, normal); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(-1.0, 1.0, 1.0); + Cartesian3.normalize(normal, normal); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(1.0, 1.0, -1.0); + Cartesian3.normalize(normal, normal); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(1.0, -1.0, -1.0); + Cartesian3.normalize(normal, normal); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(-1.0, 1.0, -1.0); + Cartesian3.normalize(normal, normal); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + + normal = new Cartesian3(-1.0, -1.0, -1.0); + Cartesian3.normalize(normal, normal); + AttributeCompression.octEncodeInRange(normal, rangeMax, encoded); + expect(AttributeCompression.octDecodeInRange(encoded.x, encoded.y, rangeMax, result)).toEqualEpsilon(normal, epsilon); + }); + it('octFloat encoding', function() { var epsilon = CesiumMath.EPSILON1;