Skip to content

Commit

Permalink
Merge pull request #4121 from lasalvavida/snorm-range-max
Browse files Browse the repository at this point in the history
Snorm range max
  • Loading branch information
pjcozzi authored Jul 20, 2016
2 parents 171aa7f + fb3a0a7 commit 62c3d28
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
68 changes: 52 additions & 16 deletions Source/Core/AttributeCompression.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
define([
'./Cartesian2',
'./Cartesian3',
'./defaultValue',
'./defined',
'./DeveloperError',
'./Math'
], function(
Cartesian2,
Cartesian3,
defaultValue,
defined,
DeveloperError,
CesiumMath) {
Expand All @@ -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.');
Expand All @@ -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)
Expand All @@ -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.
*
Expand Down
18 changes: 11 additions & 7 deletions Source/Core/Math.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

/**
Expand Down
79 changes: 74 additions & 5 deletions Specs/Core/AttributeCompressionSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand Down Expand Up @@ -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;

Expand Down

0 comments on commit 62c3d28

Please sign in to comment.