@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
122 lines (121 loc) • 6.21 kB
JavaScript
import { Vector3 } from "../../Maths/math.vector.js";
/**
* Helper class useful to convert panorama picture to their cubemap representation in 6 faces.
*/
export 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: 1,
format: 4,
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.0, -1.0, -1.0), new Vector3(1.0, -1.0, -1.0), new Vector3(-1.0, 1.0, -1.0), new Vector3(1.0, 1.0, -1.0)];
PanoramaToCubeMapTools.FACE_RIGHT = [new Vector3(1.0, -1.0, 1.0), new Vector3(-1.0, -1.0, 1.0), new Vector3(1.0, 1.0, 1.0), new Vector3(-1.0, 1.0, 1.0)];
PanoramaToCubeMapTools.FACE_FRONT = [new Vector3(1.0, -1.0, -1.0), new Vector3(1.0, -1.0, 1.0), new Vector3(1.0, 1.0, -1.0), new Vector3(1.0, 1.0, 1.0)];
PanoramaToCubeMapTools.FACE_BACK = [new Vector3(-1.0, -1.0, 1.0), new Vector3(-1.0, -1.0, -1.0), new Vector3(-1.0, 1.0, 1.0), new Vector3(-1.0, 1.0, -1.0)];
PanoramaToCubeMapTools.FACE_DOWN = [new Vector3(1.0, 1.0, -1.0), new Vector3(1.0, 1.0, 1.0), new Vector3(-1.0, 1.0, -1.0), new Vector3(-1.0, 1.0, 1.0)];
PanoramaToCubeMapTools.FACE_UP = [new Vector3(-1.0, -1.0, -1.0), new Vector3(-1.0, -1.0, 1.0), new Vector3(1.0, -1.0, -1.0), new Vector3(1.0, -1.0, 1.0)];
//# sourceMappingURL=panoramaToCubemap.js.map