playcanvas
Version:
PlayCanvas WebGL game engine
122 lines (119 loc) • 5.21 kB
JavaScript
import { math } from './math.js';
/**
* @import { Color } from './color.js'
*/ var checkRange = 5;
var oneDiv255 = 1 / 255;
var floatView = new Float32Array(1);
var int32View = new Int32Array(floatView.buffer);
/**
* Utility static class providing functionality to pack float values to various storage
* representations.
*
* @category Math
*/ class FloatPacking {
/**
* Packs a float to a 16-bit half-float representation used by the GPU.
*
* @param {number} value - The float value to pack.
* @returns {number} The packed value.
*/ static float2Half(value) {
// based on https://esdiscuss.org/topic/float16array
// This method is faster than the OpenEXR implementation (very often
// used, eg. in Ogre), with the additional benefit of rounding, inspired
// by James Tursa?s half-precision code.
floatView[0] = value;
var x = int32View[0];
var bits = x >> 16 & 0x8000; // Get the sign
var m = x >> 12 & 0x07ff; // Keep one extra bit for rounding
var e = x >> 23 & 0xff; // Using int is faster here
// If zero, or denormal, or exponent underflows too much for a denormal half, return signed zero.
if (e < 103) {
return bits;
}
// If NaN, return NaN. If Inf or exponent overflow, return Inf.
if (e > 142) {
bits |= 0x7c00;
// If exponent was 0xff and one mantissa bit was set, it means NaN,
// not Inf, so make sure we set one mantissa bit too.
bits |= (e === 255 ? 0 : 1) && x & 0x007fffff;
return bits;
}
// If exponent underflows but not too much, return a denormal
if (e < 113) {
m |= 0x0800;
// Extra rounding may overflow and set mantissa to 0 and exponent to 1, which is OK.
bits |= (m >> 114 - e) + (m >> 113 - e & 1);
return bits;
}
bits |= e - 112 << 10 | m >> 1;
// Extra rounding. An overflow will set mantissa to 0 and increment the exponent, which is OK.
bits += m & 1;
return bits;
}
/**
* Packs a float value in [0..1) range to specified number of bytes and stores them in an array
* with start offset. Based on: https://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/
* Note: calls to Math.round are only needed on iOS. Precision is somehow really bad without
* it. Looks like an issue with their implementation of Uint8ClampedArray.
*
* @param {number} value - The float value to pack.
* @param {Uint8ClampedArray} array - The array to store the packed value in.
* @param {number} offset - The start offset in the array to store the packed value at.
* @param {number} numBytes - The number of bytes to pack the value to.
*
* @ignore
*/ static float2Bytes(value, array, offset, numBytes) {
var enc1 = 255.0 * value % 1;
array[offset + 0] = Math.round((value % 1 - oneDiv255 * enc1) * 255);
if (numBytes > 1) {
var enc2 = 65025.0 * value % 1;
array[offset + 1] = Math.round((enc1 - oneDiv255 * enc2) * 255);
if (numBytes > 2) {
var enc3 = 16581375.0 * value % 1;
array[offset + 2] = Math.round((enc2 - oneDiv255 * enc3) * 255);
if (numBytes > 3) {
array[offset + 3] = Math.round(enc3 * 255);
}
}
}
}
/**
* Packs a float into specified number of bytes. Min and max range for the float is specified,
* allowing the float to be normalized to 0..1 range.
*
* @param {number} value - The float value to pack.
* @param {Uint8ClampedArray} array - The array to store the packed value in.
* @param {number} offset - The start offset in the array to store the packed value at.
* @param {number} min - Range minimum.
* @param {number} max - Range maximum.
* @param {number} numBytes - The number of bytes to pack the value to.
*
* @ignore
*/ static float2BytesRange(value, array, offset, min, max, numBytes) {
if (value < min || value > max) {
if (checkRange) {
checkRange--;
console.warn('float2BytesRange - value to pack is out of specified range.');
}
}
value = math.clamp((value - min) / (max - min), 0, 1);
FloatPacking.float2Bytes(value, array, offset, numBytes);
}
/**
* Converts bits of a 32-bit float into RGBA8 format and stores the result in a provided color.
* The float can be reconstructed in shader using the uintBitsToFloat instruction.
*
* @param {number} value - The float value to convert.
* @param {Color} data - The color to store the RGBA8 packed value in.
*
* @ignore
*/ static float2RGBA8(value, data) {
floatView[0] = value;
var intBits = int32View[0];
data.r = (intBits >> 24 & 0xFF) / 255.0;
data.g = (intBits >> 16 & 0xFF) / 255.0;
data.b = (intBits >> 8 & 0xFF) / 255.0;
data.a = (intBits & 0xFF) / 255.0;
}
}
export { FloatPacking };