equi2cubemap first working draft

pull/25/head
GeorgLegato 2023-03-29 00:49:07 +02:00
parent 53d58fdc3d
commit e8e5c6a37a
1 changed files with 202 additions and 0 deletions

202
javascript/e2c.js Normal file
View File

@ -0,0 +1,202 @@
/* code based jaxry, github, LGPL 3.0 */
function clamp(x, min, max) {
return Math.min(max, Math.max(x, min));
}
function mod(x, n) {
return ((x % n) + n) % n;
}
function copyPixelNearest(read, write) {
const {width, height, data} = read;
const readIndex = (x, y) => 4 * (y * width + x);
return (xFrom, yFrom, to) => {
const nearest = readIndex(
clamp(Math.round(xFrom), 0, width - 1),
clamp(Math.round(yFrom), 0, height - 1)
);
for (let channel = 0; channel < 3; channel++) {
write.data[to + channel] = data[nearest + channel];
}
};
}
function copyPixelBilinear(read, write) {
const {width, height, data} = read;
const readIndex = (x, y) => 4 * (y * width + x);
return (xFrom, yFrom, to) => {
const xl = clamp(Math.floor(xFrom), 0, width - 1);
const xr = clamp(Math.ceil(xFrom), 0, width - 1);
const xf = xFrom - xl;
const yl = clamp(Math.floor(yFrom), 0, height - 1);
const yr = clamp(Math.ceil(yFrom), 0, height - 1);
const yf = yFrom - yl;
const p00 = readIndex(xl, yl);
const p10 = readIndex(xr ,yl);
const p01 = readIndex(xl, yr);
const p11 = readIndex(xr, yr);
for (let channel = 0; channel < 3; channel++) {
const p0 = data[p00 + channel] * (1 - xf) + data[p10 + channel] * xf;
const p1 = data[p01 + channel] * (1 - xf) + data[p11 + channel] * xf;
write.data[to + channel] = Math.ceil(p0 * (1 - yf) + p1 * yf);
}
};
}
// performs a discrete convolution with a provided kernel
function kernelResample(read, write, filterSize, kernel) {
const {width, height, data} = read;
const readIndex = (x, y) => 4 * (y * width + x);
const twoFilterSize = 2*filterSize;
const xMax = width - 1;
const yMax = height - 1;
const xKernel = new Array(4);
const yKernel = new Array(4);
return (xFrom, yFrom, to) => {
const xl = Math.floor(xFrom);
const yl = Math.floor(yFrom);
const xStart = xl - filterSize + 1;
const yStart = yl - filterSize + 1;
for (let i = 0; i < twoFilterSize; i++) {
xKernel[i] = kernel(xFrom - (xStart + i));
yKernel[i] = kernel(yFrom - (yStart + i));
}
for (let channel = 0; channel < 3; channel++) {
let q = 0;
for (let i = 0; i < twoFilterSize; i++) {
const y = yStart + i;
const yClamped = clamp(y, 0, yMax);
let p = 0;
for (let j = 0; j < twoFilterSize; j++) {
const x = xStart + j;
const index = readIndex(clamp(x, 0, xMax), yClamped);
p += data[index + channel] * xKernel[j];
}
q += p * yKernel[i];
}
write.data[to + channel] = Math.round(q);
}
};
}
function copyPixelBicubic(read, write) {
const b = -0.5;
const kernel = x => {
x = Math.abs(x);
const x2 = x*x;
const x3 = x*x*x;
return x <= 1 ?
(b + 2)*x3 - (b + 3)*x2 + 1 :
b*x3 - 5*b*x2 + 8*b*x - 4*b;
};
return kernelResample(read, write, 2, kernel);
}
function copyPixelLanczos(read, write) {
const filterSize = 5;
const kernel = x => {
if (x === 0) {
return 1;
}
else {
const xp = Math.PI * x;
return filterSize * Math.sin(xp) * Math.sin(xp / filterSize) / (xp * xp);
}
};
return kernelResample(read, write, filterSize, kernel);
}
const orientations = {
pz: (out, x, y) => {
out.x = -1;
out.y = -x;
out.z = -y;
},
nz: (out, x, y) => {
out.x = 1;
out.y = x;
out.z = -y;
},
px: (out, x, y) => {
out.x = x;
out.y = -1;
out.z = -y;
},
nx: (out, x, y) => {
out.x = -x;
out.y = 1;
out.z = -y;
},
py: (out, x, y) => {
out.x = -y;
out.y = -x;
out.z = 1;
},
ny: (out, x, y) => {
out.x = y;
out.y = -x;
out.z = -1;
}
};
function renderFace({data: readData, face, rotation, interpolation, maxWidth = Infinity}) {
const faceWidth = Math.min(maxWidth, readData.width / 4);
const faceHeight = faceWidth;
const cube = {};
const orientation = orientations[face];
const writeData = new ImageData(faceWidth, faceHeight);
const copyPixel =
interpolation === 'linear' ? copyPixelBilinear(readData, writeData) :
interpolation === 'cubic' ? copyPixelBicubic(readData, writeData) :
interpolation === 'lanczos' ? copyPixelLanczos(readData, writeData) :
copyPixelNearest(readData, writeData);
for (let x = 0; x < faceWidth; x++) {
for (let y = 0; y < faceHeight; y++) {
const to = 4 * (y * faceWidth + x);
// fill alpha channel
writeData.data[to + 3] = 255;
// get position on cube face
// cube is centered at the origin with a side length of 2
orientation(cube, (2 * (x + 0.5) / faceWidth - 1), (2 * (y + 0.5) / faceHeight - 1));
// project cube face onto unit sphere by converting cartesian to spherical coordinates
const r = Math.sqrt(cube.x*cube.x + cube.y*cube.y + cube.z*cube.z);
const lon = mod(Math.atan2(cube.y, cube.x) + rotation, 2 * Math.PI);
const lat = Math.acos(cube.z / r);
copyPixel(readData.width * lon / Math.PI / 2 - 0.5, readData.height * lat / Math.PI - 0.5, to);
}
}
postMessage({data: writeData, faceName: face});
}
onmessage = function({data}) {
if ( data.type && data.type.startsWith("panorama/")) {
renderFace(data);
}
};