equi2cubemap first working draft
parent
53d58fdc3d
commit
e8e5c6a37a
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue