UNPKG

gpu-curtains

Version:

gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.

221 lines (220 loc) 5.53 kB
//#region src/extras/loaders/HDRLoader.ts /** * Basic HDR loader class. This loader is not intended to be used as is, but is used internally by the {@link extras/environmentMap/EnvironmentMap.EnvironmentMap | EnvironmentMap} class. * * Allow to load an HDR file from an URI and returns a {@link HDRImageData} object containing the {@link Float32Array} data alongside width, height and other useful information. * * @example * ```javascript * const hdrLoader = new HDRLoader() * const hdr = await hdrLoader.loadFromUrl('path/to/environment.hdr') * * // assuming `renderer` is a valid Renderer * const envTexture = new Texture(renderer, { * label: 'Environment texture', * name: 'envTexture', * visibility: ['fragment'], * format: 'rgba16float', * fixedSize: { * width: hdr.width, * height: hdr.height, * }, * }) * * envTexture.uploadData({ * data: hdr.data, * }) * ``` */ var HDRLoader = class { /** * Load and decode RGBE-encoded data to a flat list of floating point pixel data (RGBA). * @param url - The url of the .hdr file to load. * @returns - The {@link HDRImageData}. */ async loadFromUrl(url) { const buffer = await (await fetch(url)).arrayBuffer(); return this.#decodeRGBE(new DataView(buffer)); } /** * @ignore */ #decodeRGBE(data) { const stream = { data, offset: 0 }; const header = this.#parseHeader(stream); return { width: header.width, height: header.height, exposure: header.exposure, gamma: header.gamma, data: this.#parseData(stream, header) }; } /** * @ignore */ #parseHeader(stream) { let line = this.#readLine(stream); const header = { colorCorr: [ 1, 1, 1 ], exposure: 1, gamma: 1, width: 0, height: 0, flipX: false, flipY: false }; if (line !== "#?RADIANCE" && line !== "#?RGBE") throw new Error("Incorrect file format!"); while (line !== "") { line = this.#readLine(stream); const parts = line.split("="); switch (parts[0]) { case "GAMMA": header.gamma = parseFloat(parts[1]); break; case "FORMAT": if (parts[1] !== "32-bit_rle_rgbe" && parts[1] !== "32-bit_rle_xyze") throw new Error("Incorrect encoding format!"); break; case "EXPOSURE": header.exposure = parseFloat(parts[1]); break; case "COLORCORR": header.colorCorr = parts[1].replace(/^\s+|\s+$/g, "").split(" ").map((m) => parseFloat(m)); break; } } line = this.#readLine(stream); const parts = line.split(" "); this.#parseSize(parts[0], parseInt(parts[1]), header); this.#parseSize(parts[2], parseInt(parts[3]), header); return header; } /** * @ignore */ #parseSize(label, value, header) { switch (label) { case "+X": header.width = value; break; case "-X": header.width = value; header.flipX = true; console.warn("Flipping horizontal orientation not currently supported"); break; case "-Y": header.height = value; header.flipY = true; break; case "+Y": header.height = value; break; } } /** * @ignore */ #readLine(stream) { let ch, str = ""; while ((ch = stream.data.getUint8(stream.offset++)) !== 10) str += String.fromCharCode(ch); return str; } /** * @ignore */ #parseData(stream, header) { const hash = stream.data.getUint16(stream.offset); let data; if (hash === 514) { data = this.#parseNewRLE(stream, header); if (header.flipX) this.#flipX(data, header); if (header.flipY) this.#flipY(data, header); } else throw new Error("Obsolete HDR file version!"); return data; } /** * @ignore */ #parseNewRLE(stream, header) { const { width, height, colorCorr } = header; const tgt = new Float32Array(width * height * 4); let i = 0; let { offset, data } = stream; for (let y = 0; y < height; ++y) { if (data.getUint16(offset) !== 514) throw new Error("Incorrect scanline start hash"); if (data.getUint16(offset + 2) !== width) throw new Error("Scanline doesn't match picture dimension!"); offset += 4; const numComps = width * 4; const comps = []; let x = 0; while (x < numComps) { let value = data.getUint8(offset++); if (value > 128) { const len = value - 128; value = data.getUint8(offset++); for (let rle = 0; rle < len; ++rle) comps[x++] = value; } else for (let n = 0; n < value; ++n) comps[x++] = data.getUint8(offset++); } for (x = 0; x < width; ++x) { const r = comps[x]; const g = comps[x + width]; const b = comps[x + width * 2]; let e = comps[x + width * 3]; e = e ? Math.pow(2, e - 136) : 0; tgt[i++] = r * e * colorCorr[0]; tgt[i++] = g * e * colorCorr[1]; tgt[i++] = b * e * colorCorr[2]; tgt[i++] = e; } } return tgt; } /** * @ignore */ #swap(data, i1, i2) { i1 *= 4; i2 *= 4; for (let i = 0; i < 4; ++i) { const tmp = data[i1 + i]; data[i1 + i] = data[i2 + i]; data[i2 + i] = tmp; } } /** * @ignore */ #flipX(data, header) { const { width, height } = header; const hw = width >> 1; for (let y = 0; y < height; ++y) { const b = y * width; for (let x = 0; x < hw; ++x) { const i1 = b + x; const i2 = b + width - 1 - x; this.#swap(data, i1, i2); } } } /** * @ignore */ #flipY(data, header) { const { width, height } = header; const hh = height >> 1; for (let y = 0; y < hh; ++y) { const b1 = y * width; const b2 = (height - 1 - y) * width; for (let x = 0; x < width; ++x) this.#swap(data, b1 + x, b2 + x); } } }; //#endregion export { HDRLoader };