@loaders.gl/math
Version:
Experimental math classes for loaders.gl
392 lines (345 loc) • 12.8 kB
text/typescript
// This file is derived from the Cesium code base under Apache 2 license
// See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md
// Attribute compression and decompression functions.
import {Vector2, Vector3, clamp, _MathUtils} from '@math.gl/core';
import {assert} from '../utils/assert';
type Vector4 = {
x: number;
y: number;
z: number;
w: number;
};
const RIGHT_SHIFT = 1.0 / 256.0;
const LEFT_SHIFT = 256.0;
const scratchVector2 = new Vector2();
const scratchVector3 = new Vector3();
const scratchEncodeVector2 = new Vector2();
const octEncodeScratch = new Vector2();
const uint8ForceArray = new Uint8Array(1);
/**
* Force a value to Uint8
*
* @param value
* @returns
*/
function forceUint8(value: number): number {
uint8ForceArray[0] = value;
return uint8ForceArray[0];
}
/**
* Converts a SNORM value in the range [0, rangeMaximum] to a scalar in the range [-1.0, 1.0].
*
* @param value SNORM value in the range [0, rangeMaximum]
* @param [rangeMaximum=255] The maximum value in the SNORM range, 255 by default.
* @returns Scalar in the range [-1.0, 1.0].
*
* @see CesiumMath.toSNorm
*/
function fromSNorm(value: number, rangeMaximum = 255): number {
return (clamp(value, 0.0, rangeMaximum) / rangeMaximum) * 2.0 - 1.0;
}
/**
* Converts a scalar value in the range [-1.0, 1.0] to a SNORM in the range [0, rangeMaximum].
*
* @param value The scalar value in the range [-1.0, 1.0]
* @param [rangeMaximum=255] The maximum value in the mapped range, 255 by default.
* @returns A SNORM value, where 0 maps to -1.0 and rangeMaximum maps to 1.0.
*
* @see CesiumMath.fromSNorm
*/
function toSNorm(value: number, rangeMaximum = 255): number {
return Math.round((clamp(value, -1.0, 1.0) * 0.5 + 0.5) * rangeMaximum);
}
/**
* Returns 1.0 if the given value is positive or zero, and -1.0 if it is negative.
* This is similar to `Math.sign` except that returns 1.0 instead of
* 0.0 when the input value is 0.0.
*
* @param value The value to return the sign of.
* @returns The sign of value.
*/
function signNotZero(value: number): number {
return value < 0.0 ? -1.0 : 1.0;
}
/**
* 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 '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 vector The normalized vector to be compressed into 2 component 'oct' encoding.
* @param result The 2 component oct-encoded unit length vector.
* @param rangeMax The maximum value of the SNORM range. The encoded vector is stored in log2(rangeMax+1) bits.
* @returns The 2 component oct-encoded unit length vector.
*
* @exception vector must be normalized.
*
* @see octDecodeInRange
*/
export function octEncodeInRange(vector: Vector3, rangeMax: number, result: Vector2): Vector2 {
assert(vector);
assert(result);
const vector3 = scratchVector3.from(vector);
assert(Math.abs(vector3.magnitudeSquared() - 1.0) <= _MathUtils.EPSILON6);
result.x = vector.x / (Math.abs(vector.x) + Math.abs(vector.y) + Math.abs(vector.z));
result.y = vector.y / (Math.abs(vector.x) + Math.abs(vector.y) + Math.abs(vector.z));
if (vector.z < 0) {
const x = result.x;
const y = result.y;
result.x = (1.0 - Math.abs(y)) * signNotZero(x);
result.y = (1.0 - Math.abs(x)) * signNotZero(y);
}
result.x = toSNorm(result.x, rangeMax);
result.y = 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 vector The normalized vector to be compressed into 2 byte 'oct' encoding.
* @param result The 2 byte oct-encoded unit length vector.
* @returns he 2 byte oct-encoded unit length vector.
*
* @exception vector must be normalized.
*
* @see octEncodeInRange
* @see octDecode
*/
export function octEncode(vector: Vector3, result: Vector2): Vector2 {
return octEncodeInRange(vector, 255, result);
}
/**
* Encodes a normalized vector into 4-byte vector
* @param vector The normalized vector to be compressed into 4 byte 'oct' encoding.
* @param result The 4 byte oct-encoded unit length vector.
* @returns The 4 byte oct-encoded unit length vector.
*
* @exception vector must be normalized.
*
* @see octEncodeInRange
* @see octDecodeFromVector4
*/
export function octEncodeToVector4(vector: Vector3, result: Vector4): Vector4 {
octEncodeInRange(vector, 65535, octEncodeScratch);
result.x = forceUint8(octEncodeScratch.x * RIGHT_SHIFT);
result.y = forceUint8(octEncodeScratch.x);
result.z = forceUint8(octEncodeScratch.y * RIGHT_SHIFT);
result.w = forceUint8(octEncodeScratch.y);
return result;
}
/**
* Decodes a unit-length vector in 'oct' encoding to a normalized 3-component vector.
*
* @param x The x component of the oct-encoded unit length vector.
* @param y The y component of the oct-encoded unit length vector.
* @param rangeMax The maximum value of the SNORM range. The encoded vector is stored in log2(rangeMax+1) bits.
* @param result The decoded and normalized vector
* @returns The decoded and normalized vector.
*
* @exception x and y must be unsigned normalized integers between 0 and rangeMax.
*
* @see octEncodeInRange
*/
export function octDecodeInRange(x: number, y: number, rangeMax: number, result: Vector3): Vector3 {
assert(result);
if (x < 0 || x > rangeMax || y < 0 || y > rangeMax) {
throw new Error(`x and y must be unsigned normalized integers between 0 and ${rangeMax}`);
}
result.x = fromSNorm(x, rangeMax);
result.y = fromSNorm(y, rangeMax);
result.z = 1.0 - (Math.abs(result.x) + Math.abs(result.y));
if (result.z < 0.0) {
const oldVX = result.x;
result.x = (1.0 - Math.abs(result.y)) * signNotZero(oldVX);
result.y = (1.0 - Math.abs(oldVX)) * signNotZero(result.y);
}
return result.normalize();
}
/**
* Decodes a unit-length vector in 2 byte 'oct' encoding to a normalized 3-component vector.
*
* @param x The x component of the oct-encoded unit length vector.
* @param y The y component of the oct-encoded unit length vector.
* @param result The decoded and normalized vector.
* @returns he decoded and normalized vector.
*
* @exception x and y must be an unsigned normalized integer between 0 and 255.
*
* @see octDecodeInRange
*/
export function octDecode(x: number, y: number, result: Vector3): Vector3 {
return octDecodeInRange(x, y, 255, result);
}
/**
* Decodes a unit-length vector in 4 byte 'oct' encoding to a normalized 3-component vector.
*
* @param encoded The oct-encoded unit length vector.
* @param esult The decoded and normalized vector.
* @returns The decoded and normalized vector.
*
* @exception x, y, z, and w must be unsigned normalized integers between 0 and 255.
*
* @see octDecodeInRange
* @see octEncodeToVector4
*/
export function octDecodeFromVector4(encoded: Vector4, result: Vector3): Vector3 {
assert(encoded);
assert(result);
const x = encoded.x;
const y = encoded.y;
const z = encoded.z;
const w = encoded.w;
if (x < 0 || x > 255 || y < 0 || y > 255 || z < 0 || z > 255 || w < 0 || w > 255) {
throw new Error('x, y, z, and w must be unsigned normalized integers between 0 and 255');
}
const xOct16 = x * LEFT_SHIFT + y;
const yOct16 = z * LEFT_SHIFT + w;
return octDecodeInRange(xOct16, yOct16, 65535, result);
}
/**
* Packs an oct encoded vector into a single floating-point number.
*
* @param encoded The oct encoded vector.
* @returns The oct encoded vector packed into a single float.
*
*/
export function octPackFloat(encoded: Vector2): number {
const vector2 = scratchVector2.from(encoded);
return 256.0 * vector2.x + vector2.y;
}
/**
* Encodes a normalized vector into 2 SNORM values in the range of [0-255] following the 'oct' encoding and
* stores those values in a single float-point number.
*
* @param vector The normalized vector to be compressed into 2 byte 'oct' encoding.
* @returns The 2 byte oct-encoded unit length vector.
*
* @exception vector must be normalized.
*/
export function octEncodeFloat(vector: Vector3): number {
octEncode(vector, scratchEncodeVector2);
return octPackFloat(scratchEncodeVector2);
}
/**
* Decodes a unit-length vector in 'oct' encoding packed in a floating-point number to a normalized 3-component vector.
*
* @param value The oct-encoded unit length vector stored as a single floating-point number.
* @param result The decoded and normalized vector
* @returns The decoded and normalized vector.
*
*/
export function octDecodeFloat(value: number, result: Vector3): Vector3 {
assert(Number.isFinite(value));
const temp = value / 256.0;
const x = Math.floor(temp);
const y = (temp - x) * 256.0;
return octDecode(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 v1 A normalized vector to be compressed.
* @param v2 A normalized vector to be compressed.
* @param v3 A normalized vector to be compressed.
* @param result The 'oct' encoded vectors packed into two floating-point numbers.
* @returns The 'oct' encoded vectors packed into two floating-point numbers.
*
*/
export function octPack(v1: Vector3, v2: Vector3, v3: Vector3, result: Vector2): Vector2 {
assert(v1);
assert(v2);
assert(v3);
assert(result);
const encoded1 = octEncodeFloat(v1);
const encoded2 = octEncodeFloat(v2);
const encoded3 = octEncode(v3, scratchEncodeVector2);
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 packed The three oct-encoded unit length vectors stored as two floating-point number.
* @param v1 One decoded and normalized vector.
* @param v2 One decoded and normalized vector.
* @param v3 One decoded and normalized vector.
*/
export function octUnpack(packed: Vector2, v1: Vector3, v2: Vector3, v3: Vector3): void {
let temp = packed.x / 65536.0;
const x = Math.floor(temp);
const encodedFloat1 = (temp - x) * 65536.0;
temp = packed.y / 65536.0;
const y = Math.floor(temp);
const encodedFloat2 = (temp - y) * 65536.0;
octDecodeFloat(encodedFloat1, v1);
octDecodeFloat(encodedFloat2, v2);
octDecode(x, y, v3);
}
/**
* Pack texture coordinates into a single float. The texture coordinates will only preserve 12 bits of precision.
*
* @param textureCoordinates The texture coordinates to compress. Both coordinates must be in the range 0.0-1.0.
* @returns The packed texture coordinates.
*
*/
export function compressTextureCoordinates(textureCoordinates: Vector2): number {
// Move x and y to the range 0-4095;
const x = (textureCoordinates.x * 4095.0) | 0;
const y = (textureCoordinates.y * 4095.0) | 0;
return 4096.0 * x + y;
}
/**
* Decompresses texture coordinates that were packed into a single float.
*
* @param compressed The compressed texture coordinates.
* @param result The decompressed texture coordinates.
* @returns The modified result parameter.
*
*/
export function decompressTextureCoordinates(compressed: number, result: Vector2): Vector2 {
const temp = compressed / 4096.0;
const xZeroTo4095 = Math.floor(temp);
result.x = xZeroTo4095 / 4095.0;
result.y = (compressed - xZeroTo4095 * 4096) / 4095;
return result;
}
/**
* Decodes delta and ZigZag encoded vertices. This modifies the buffers in place.
*
* @param uBuffer The buffer view of u values.
* @param vBuffer The buffer view of v values.
* @param [heightBuffer] The buffer view of height values.
*
* @link https://github.com/AnalyticalGraphicsInc/quantized-mesh|quantized-mesh-1.0 terrain format
*/
export function zigZagDeltaDecode(
uBuffer: Uint16Array,
vBuffer: Uint16Array,
heightBuffer?: Uint16Array | number[]
) {
assert(uBuffer);
assert(vBuffer);
assert(uBuffer.length === vBuffer.length);
if (heightBuffer) {
assert(uBuffer.length === heightBuffer.length);
}
function zigZagDecode(value: number) {
return (value >> 1) ^ -(value & 1);
}
let u = 0;
let v = 0;
let height = 0;
for (let i = 0; i < uBuffer.length; ++i) {
u += zigZagDecode(uBuffer[i]);
v += zigZagDecode(vBuffer[i]);
uBuffer[i] = u;
vBuffer[i] = v;
if (heightBuffer) {
height += zigZagDecode(heightBuffer[i]);
heightBuffer[i] = height;
}
}
}