@babylonjs/viewer
Version:
The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.
347 lines (343 loc) • 14.9 kB
JavaScript
import { C as Constants, V as Vector3 } from './index-FzOfPXLV.esm.js';
/**
* Helper class useful to convert panorama picture to their cubemap representation in 6 faces.
*/
class PanoramaToCubeMapTools {
/**
* Converts a panorama stored in RGB right to left up to down format into a cubemap (6 faces).
*
* @param float32Array The source data.
* @param inputWidth The width of the input panorama.
* @param inputHeight The height of the input panorama.
* @param size The willing size of the generated cubemap (each faces will be size * size pixels)
* @param supersample enable supersampling the cubemap
* @returns The cubemap data
*/
static ConvertPanoramaToCubemap(float32Array, inputWidth, inputHeight, size, supersample = false) {
if (!float32Array) {
// eslint-disable-next-line no-throw-literal
throw "ConvertPanoramaToCubemap: input cannot be null";
}
if (float32Array.length != inputWidth * inputHeight * 3) {
// eslint-disable-next-line no-throw-literal
throw "ConvertPanoramaToCubemap: input size is wrong";
}
const textureFront = this.CreateCubemapTexture(size, this.FACE_FRONT, float32Array, inputWidth, inputHeight, supersample);
const textureBack = this.CreateCubemapTexture(size, this.FACE_BACK, float32Array, inputWidth, inputHeight, supersample);
const textureLeft = this.CreateCubemapTexture(size, this.FACE_LEFT, float32Array, inputWidth, inputHeight, supersample);
const textureRight = this.CreateCubemapTexture(size, this.FACE_RIGHT, float32Array, inputWidth, inputHeight, supersample);
const textureUp = this.CreateCubemapTexture(size, this.FACE_UP, float32Array, inputWidth, inputHeight, supersample);
const textureDown = this.CreateCubemapTexture(size, this.FACE_DOWN, float32Array, inputWidth, inputHeight, supersample);
return {
front: textureFront,
back: textureBack,
left: textureLeft,
right: textureRight,
up: textureUp,
down: textureDown,
size: size,
type: Constants.TEXTURETYPE_FLOAT,
format: Constants.TEXTUREFORMAT_RGB,
gammaSpace: false,
};
}
static CreateCubemapTexture(texSize, faceData, float32Array, inputWidth, inputHeight, supersample = false) {
const buffer = new ArrayBuffer(texSize * texSize * 4 * 3);
const textureArray = new Float32Array(buffer);
// If supersampling, determine number of samples needed when source texture width is divided for 4 cube faces
const samples = supersample ? Math.max(1, Math.round(inputWidth / 4 / texSize)) : 1;
const sampleFactor = 1 / samples;
const sampleFactorSqr = sampleFactor * sampleFactor;
const rotDX1 = faceData[1].subtract(faceData[0]).scale(sampleFactor / texSize);
const rotDX2 = faceData[3].subtract(faceData[2]).scale(sampleFactor / texSize);
const dy = 1 / texSize;
let fy = 0;
for (let y = 0; y < texSize; y++) {
for (let sy = 0; sy < samples; sy++) {
let xv1 = faceData[0];
let xv2 = faceData[2];
for (let x = 0; x < texSize; x++) {
for (let sx = 0; sx < samples; sx++) {
const v = xv2.subtract(xv1).scale(fy).add(xv1);
v.normalize();
const color = this.CalcProjectionSpherical(v, float32Array, inputWidth, inputHeight);
// 3 channels per pixels
textureArray[y * texSize * 3 + x * 3 + 0] += color.r * sampleFactorSqr;
textureArray[y * texSize * 3 + x * 3 + 1] += color.g * sampleFactorSqr;
textureArray[y * texSize * 3 + x * 3 + 2] += color.b * sampleFactorSqr;
xv1 = xv1.add(rotDX1);
xv2 = xv2.add(rotDX2);
}
}
fy += dy * sampleFactor;
}
}
return textureArray;
}
static CalcProjectionSpherical(vDir, float32Array, inputWidth, inputHeight) {
let theta = Math.atan2(vDir.z, vDir.x);
const phi = Math.acos(vDir.y);
while (theta < -Math.PI) {
theta += 2 * Math.PI;
}
while (theta > Math.PI) {
theta -= 2 * Math.PI;
}
let dx = theta / Math.PI;
const dy = phi / Math.PI;
// recenter.
dx = dx * 0.5 + 0.5;
let px = Math.round(dx * inputWidth);
if (px < 0) {
px = 0;
}
else if (px >= inputWidth) {
px = inputWidth - 1;
}
let py = Math.round(dy * inputHeight);
if (py < 0) {
py = 0;
}
else if (py >= inputHeight) {
py = inputHeight - 1;
}
const inputY = inputHeight - py - 1;
const r = float32Array[inputY * inputWidth * 3 + px * 3 + 0];
const g = float32Array[inputY * inputWidth * 3 + px * 3 + 1];
const b = float32Array[inputY * inputWidth * 3 + px * 3 + 2];
return {
r: r,
g: g,
b: b,
};
}
}
PanoramaToCubeMapTools.FACE_LEFT = [new Vector3(-1, -1, -1), new Vector3(1.0, -1, -1), new Vector3(-1, 1.0, -1), new Vector3(1.0, 1.0, -1)];
PanoramaToCubeMapTools.FACE_RIGHT = [new Vector3(1.0, -1, 1.0), new Vector3(-1, -1, 1.0), new Vector3(1.0, 1.0, 1.0), new Vector3(-1, 1.0, 1.0)];
PanoramaToCubeMapTools.FACE_FRONT = [new Vector3(1.0, -1, -1), new Vector3(1.0, -1, 1.0), new Vector3(1.0, 1.0, -1), new Vector3(1.0, 1.0, 1.0)];
PanoramaToCubeMapTools.FACE_BACK = [new Vector3(-1, -1, 1.0), new Vector3(-1, -1, -1), new Vector3(-1, 1.0, 1.0), new Vector3(-1, 1.0, -1)];
PanoramaToCubeMapTools.FACE_DOWN = [new Vector3(1.0, 1.0, -1), new Vector3(1.0, 1.0, 1.0), new Vector3(-1, 1.0, -1), new Vector3(-1, 1.0, 1.0)];
PanoramaToCubeMapTools.FACE_UP = [new Vector3(-1, -1, -1), new Vector3(-1, -1, 1.0), new Vector3(1.0, -1, -1), new Vector3(1.0, -1, 1.0)];
/* This groups tools to convert HDR texture to native colors array. */
function Ldexp(mantissa, exponent) {
if (exponent > 1023) {
return mantissa * Math.pow(2, 1023) * Math.pow(2, exponent - 1023);
}
if (exponent < -1074) {
return mantissa * Math.pow(2, -1074) * Math.pow(2, exponent + 1074);
}
return mantissa * Math.pow(2, exponent);
}
function Rgbe2float(float32array, red, green, blue, exponent, index) {
if (exponent > 0) {
/*nonzero pixel*/
exponent = Ldexp(1.0, exponent - (128 + 8));
float32array[index + 0] = red * exponent;
float32array[index + 1] = green * exponent;
float32array[index + 2] = blue * exponent;
}
else {
float32array[index + 0] = 0;
float32array[index + 1] = 0;
float32array[index + 2] = 0;
}
}
function ReadStringLine(uint8array, startIndex) {
let line = "";
let character = "";
for (let i = startIndex; i < uint8array.length - startIndex; i++) {
character = String.fromCharCode(uint8array[i]);
if (character == "\n") {
break;
}
line += character;
}
return line;
}
/**
* Reads header information from an RGBE texture stored in a native array.
* More information on this format are available here:
* https://en.wikipedia.org/wiki/RGBE_image_format
*
* @param uint8array The binary file stored in native array.
* @returns The header information.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
function RGBE_ReadHeader(uint8array) {
let height = 0;
let width = 0;
let line = ReadStringLine(uint8array, 0);
if (line[0] != "#" || line[1] != "?") {
// eslint-disable-next-line no-throw-literal
throw "Bad HDR Format.";
}
let endOfHeader = false;
let findFormat = false;
let lineIndex = 0;
do {
lineIndex += line.length + 1;
line = ReadStringLine(uint8array, lineIndex);
if (line == "FORMAT=32-bit_rle_rgbe") {
findFormat = true;
}
else if (line.length == 0) {
endOfHeader = true;
}
} while (!endOfHeader);
if (!findFormat) {
// eslint-disable-next-line no-throw-literal
throw "HDR Bad header format, unsupported FORMAT";
}
lineIndex += line.length + 1;
line = ReadStringLine(uint8array, lineIndex);
const sizeRegexp = /^-Y (.*) \+X (.*)$/g;
const match = sizeRegexp.exec(line);
// TODO. Support +Y and -X if needed.
if (!match || match.length < 3) {
// eslint-disable-next-line no-throw-literal
throw "HDR Bad header format, no size";
}
width = parseInt(match[2]);
height = parseInt(match[1]);
if (width < 8 || width > 0x7fff) {
// eslint-disable-next-line no-throw-literal
throw "HDR Bad header format, unsupported size";
}
lineIndex += line.length + 1;
return {
height: height,
width: width,
dataPosition: lineIndex,
};
}
/**
* Returns the cubemap information (each faces texture data) extracted from an RGBE texture.
* This RGBE texture needs to store the information as a panorama.
*
* More information on this format are available here:
* https://en.wikipedia.org/wiki/RGBE_image_format
*
* @param buffer The binary file stored in an array buffer.
* @param size The expected size of the extracted cubemap.
* @param supersample enable supersampling the cubemap (default: false)
* @returns The Cube Map information.
*/
function GetCubeMapTextureData(buffer, size, supersample = false) {
const uint8array = new Uint8Array(buffer);
const hdrInfo = RGBE_ReadHeader(uint8array);
const data = RGBE_ReadPixels(uint8array, hdrInfo);
const cubeMapData = PanoramaToCubeMapTools.ConvertPanoramaToCubemap(data, hdrInfo.width, hdrInfo.height, size, supersample);
return cubeMapData;
}
/**
* Returns the pixels data extracted from an RGBE texture.
* This pixels will be stored left to right up to down in the R G B order in one array.
*
* More information on this format are available here:
* https://en.wikipedia.org/wiki/RGBE_image_format
*
* @param uint8array The binary file stored in an array buffer.
* @param hdrInfo The header information of the file.
* @returns The pixels data in RGB right to left up to down order.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
function RGBE_ReadPixels(uint8array, hdrInfo) {
return ReadRGBEPixelsRLE(uint8array, hdrInfo);
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function ReadRGBEPixelsRLE(uint8array, hdrInfo) {
let numScanlines = hdrInfo.height;
const scanlineWidth = hdrInfo.width;
let a, b, c, d, count;
let dataIndex = hdrInfo.dataPosition;
let index = 0, endIndex = 0, i = 0;
const scanLineArrayBuffer = new ArrayBuffer(scanlineWidth * 4); // four channel R G B E
const scanLineArray = new Uint8Array(scanLineArrayBuffer);
// 3 channels of 4 bytes per pixel in float.
const resultBuffer = new ArrayBuffer(hdrInfo.width * hdrInfo.height * 4 * 3);
const resultArray = new Float32Array(resultBuffer);
// read in each successive scanline
while (numScanlines > 0) {
a = uint8array[dataIndex++];
b = uint8array[dataIndex++];
c = uint8array[dataIndex++];
d = uint8array[dataIndex++];
if (a != 2 || b != 2 || c & 0x80 || hdrInfo.width < 8 || hdrInfo.width > 32767) {
return ReadRGBEPixelsNotRLE(uint8array, hdrInfo);
}
if (((c << 8) | d) != scanlineWidth) {
// eslint-disable-next-line no-throw-literal
throw "HDR Bad header format, wrong scan line width";
}
index = 0;
// read each of the four channels for the scanline into the buffer
for (i = 0; i < 4; i++) {
endIndex = (i + 1) * scanlineWidth;
while (index < endIndex) {
a = uint8array[dataIndex++];
b = uint8array[dataIndex++];
if (a > 128) {
// a run of the same value
count = a - 128;
if (count == 0 || count > endIndex - index) {
// eslint-disable-next-line no-throw-literal
throw "HDR Bad Format, bad scanline data (run)";
}
while (count-- > 0) {
scanLineArray[index++] = b;
}
}
else {
// a non-run
count = a;
if (count == 0 || count > endIndex - index) {
// eslint-disable-next-line no-throw-literal
throw "HDR Bad Format, bad scanline data (non-run)";
}
scanLineArray[index++] = b;
if (--count > 0) {
for (let j = 0; j < count; j++) {
scanLineArray[index++] = uint8array[dataIndex++];
}
}
}
}
}
// now convert data from buffer into floats
for (i = 0; i < scanlineWidth; i++) {
a = scanLineArray[i];
b = scanLineArray[i + scanlineWidth];
c = scanLineArray[i + 2 * scanlineWidth];
d = scanLineArray[i + 3 * scanlineWidth];
Rgbe2float(resultArray, a, b, c, d, (hdrInfo.height - numScanlines) * scanlineWidth * 3 + i * 3);
}
numScanlines--;
}
return resultArray;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
function ReadRGBEPixelsNotRLE(uint8array, hdrInfo) {
// this file is not run length encoded
// read values sequentially
let numScanlines = hdrInfo.height;
const scanlineWidth = hdrInfo.width;
let a, b, c, d, i;
let dataIndex = hdrInfo.dataPosition;
// 3 channels of 4 bytes per pixel in float.
const resultBuffer = new ArrayBuffer(hdrInfo.width * hdrInfo.height * 4 * 3);
const resultArray = new Float32Array(resultBuffer);
// read in each successive scanline
while (numScanlines > 0) {
for (i = 0; i < hdrInfo.width; i++) {
a = uint8array[dataIndex++];
b = uint8array[dataIndex++];
c = uint8array[dataIndex++];
d = uint8array[dataIndex++];
Rgbe2float(resultArray, a, b, c, d, (hdrInfo.height - numScanlines) * scanlineWidth * 3 + i * 3);
}
numScanlines--;
}
return resultArray;
}
export { GetCubeMapTextureData as G, RGBE_ReadHeader as R, RGBE_ReadPixels as a };
//# sourceMappingURL=hdr-2oFTOfLl.esm.js.map