244 lines
9.3 KiB
JavaScript
244 lines
9.3 KiB
JavaScript
import { DataTextureLoader, HalfFloatType, FloatType, DataUtils, LinearFilter } from "three";
|
|
class RGBELoader extends DataTextureLoader {
|
|
constructor(manager) {
|
|
super(manager);
|
|
this.type = HalfFloatType;
|
|
}
|
|
// adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html
|
|
parse(buffer) {
|
|
const rgbe_read_error = 1, rgbe_write_error = 2, rgbe_format_error = 3, rgbe_memory_error = 4, rgbe_error = function(rgbe_error_code, msg) {
|
|
switch (rgbe_error_code) {
|
|
case rgbe_read_error:
|
|
throw new Error("THREE.RGBELoader: Read Error: " + (msg || ""));
|
|
case rgbe_write_error:
|
|
throw new Error("THREE.RGBELoader: Write Error: " + (msg || ""));
|
|
case rgbe_format_error:
|
|
throw new Error("THREE.RGBELoader: Bad File Format: " + (msg || ""));
|
|
default:
|
|
case rgbe_memory_error:
|
|
throw new Error("THREE.RGBELoader: Memory Error: " + (msg || ""));
|
|
}
|
|
}, RGBE_VALID_PROGRAMTYPE = 1, RGBE_VALID_FORMAT = 2, RGBE_VALID_DIMENSIONS = 4, NEWLINE = "\n", fgets = function(buffer2, lineLimit, consume) {
|
|
const chunkSize = 128;
|
|
lineLimit = !lineLimit ? 1024 : lineLimit;
|
|
let p = buffer2.pos, i = -1, len = 0, s = "", chunk = String.fromCharCode.apply(null, new Uint16Array(buffer2.subarray(p, p + chunkSize)));
|
|
while (0 > (i = chunk.indexOf(NEWLINE)) && len < lineLimit && p < buffer2.byteLength) {
|
|
s += chunk;
|
|
len += chunk.length;
|
|
p += chunkSize;
|
|
chunk += String.fromCharCode.apply(null, new Uint16Array(buffer2.subarray(p, p + chunkSize)));
|
|
}
|
|
if (-1 < i) {
|
|
if (false !== consume)
|
|
buffer2.pos += len + i + 1;
|
|
return s + chunk.slice(0, i);
|
|
}
|
|
return false;
|
|
}, RGBE_ReadHeader = function(buffer2) {
|
|
const magic_token_re = /^#\?(\S+)/, gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/, exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/, format_re = /^\s*FORMAT=(\S+)\s*$/, dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/, header = {
|
|
valid: 0,
|
|
string: "",
|
|
comments: "",
|
|
programtype: "RGBE",
|
|
format: "",
|
|
gamma: 1,
|
|
exposure: 1,
|
|
width: 0,
|
|
height: 0
|
|
};
|
|
let line, match;
|
|
if (buffer2.pos >= buffer2.byteLength || !(line = fgets(buffer2))) {
|
|
rgbe_error(rgbe_read_error, "no header found");
|
|
}
|
|
if (!(match = line.match(magic_token_re))) {
|
|
rgbe_error(rgbe_format_error, "bad initial token");
|
|
}
|
|
header.valid |= RGBE_VALID_PROGRAMTYPE;
|
|
header.programtype = match[1];
|
|
header.string += line + "\n";
|
|
while (true) {
|
|
line = fgets(buffer2);
|
|
if (false === line)
|
|
break;
|
|
header.string += line + "\n";
|
|
if ("#" === line.charAt(0)) {
|
|
header.comments += line + "\n";
|
|
continue;
|
|
}
|
|
if (match = line.match(gamma_re)) {
|
|
header.gamma = parseFloat(match[1]);
|
|
}
|
|
if (match = line.match(exposure_re)) {
|
|
header.exposure = parseFloat(match[1]);
|
|
}
|
|
if (match = line.match(format_re)) {
|
|
header.valid |= RGBE_VALID_FORMAT;
|
|
header.format = match[1];
|
|
}
|
|
if (match = line.match(dimensions_re)) {
|
|
header.valid |= RGBE_VALID_DIMENSIONS;
|
|
header.height = parseInt(match[1], 10);
|
|
header.width = parseInt(match[2], 10);
|
|
}
|
|
if (header.valid & RGBE_VALID_FORMAT && header.valid & RGBE_VALID_DIMENSIONS)
|
|
break;
|
|
}
|
|
if (!(header.valid & RGBE_VALID_FORMAT)) {
|
|
rgbe_error(rgbe_format_error, "missing format specifier");
|
|
}
|
|
if (!(header.valid & RGBE_VALID_DIMENSIONS)) {
|
|
rgbe_error(rgbe_format_error, "missing image size specifier");
|
|
}
|
|
return header;
|
|
}, RGBE_ReadPixels_RLE = function(buffer2, w2, h2) {
|
|
const scanline_width = w2;
|
|
if (
|
|
// run length encoding is not allowed so read flat
|
|
scanline_width < 8 || scanline_width > 32767 || // this file is not run length encoded
|
|
2 !== buffer2[0] || 2 !== buffer2[1] || buffer2[2] & 128
|
|
) {
|
|
return new Uint8Array(buffer2);
|
|
}
|
|
if (scanline_width !== (buffer2[2] << 8 | buffer2[3])) {
|
|
rgbe_error(rgbe_format_error, "wrong scanline width");
|
|
}
|
|
const data_rgba = new Uint8Array(4 * w2 * h2);
|
|
if (!data_rgba.length) {
|
|
rgbe_error(rgbe_memory_error, "unable to allocate buffer space");
|
|
}
|
|
let offset = 0, pos = 0;
|
|
const ptr_end = 4 * scanline_width;
|
|
const rgbeStart = new Uint8Array(4);
|
|
const scanline_buffer = new Uint8Array(ptr_end);
|
|
let num_scanlines = h2;
|
|
while (num_scanlines > 0 && pos < buffer2.byteLength) {
|
|
if (pos + 4 > buffer2.byteLength) {
|
|
rgbe_error(rgbe_read_error);
|
|
}
|
|
rgbeStart[0] = buffer2[pos++];
|
|
rgbeStart[1] = buffer2[pos++];
|
|
rgbeStart[2] = buffer2[pos++];
|
|
rgbeStart[3] = buffer2[pos++];
|
|
if (2 != rgbeStart[0] || 2 != rgbeStart[1] || (rgbeStart[2] << 8 | rgbeStart[3]) != scanline_width) {
|
|
rgbe_error(rgbe_format_error, "bad rgbe scanline format");
|
|
}
|
|
let ptr = 0, count;
|
|
while (ptr < ptr_end && pos < buffer2.byteLength) {
|
|
count = buffer2[pos++];
|
|
const isEncodedRun = count > 128;
|
|
if (isEncodedRun)
|
|
count -= 128;
|
|
if (0 === count || ptr + count > ptr_end) {
|
|
rgbe_error(rgbe_format_error, "bad scanline data");
|
|
}
|
|
if (isEncodedRun) {
|
|
const byteValue = buffer2[pos++];
|
|
for (let i = 0; i < count; i++) {
|
|
scanline_buffer[ptr++] = byteValue;
|
|
}
|
|
} else {
|
|
scanline_buffer.set(buffer2.subarray(pos, pos + count), ptr);
|
|
ptr += count;
|
|
pos += count;
|
|
}
|
|
}
|
|
const l = scanline_width;
|
|
for (let i = 0; i < l; i++) {
|
|
let off = 0;
|
|
data_rgba[offset] = scanline_buffer[i + off];
|
|
off += scanline_width;
|
|
data_rgba[offset + 1] = scanline_buffer[i + off];
|
|
off += scanline_width;
|
|
data_rgba[offset + 2] = scanline_buffer[i + off];
|
|
off += scanline_width;
|
|
data_rgba[offset + 3] = scanline_buffer[i + off];
|
|
offset += 4;
|
|
}
|
|
num_scanlines--;
|
|
}
|
|
return data_rgba;
|
|
};
|
|
const RGBEByteToRGBFloat = function(sourceArray, sourceOffset, destArray, destOffset) {
|
|
const e = sourceArray[sourceOffset + 3];
|
|
const scale = Math.pow(2, e - 128) / 255;
|
|
destArray[destOffset + 0] = sourceArray[sourceOffset + 0] * scale;
|
|
destArray[destOffset + 1] = sourceArray[sourceOffset + 1] * scale;
|
|
destArray[destOffset + 2] = sourceArray[sourceOffset + 2] * scale;
|
|
destArray[destOffset + 3] = 1;
|
|
};
|
|
const RGBEByteToRGBHalf = function(sourceArray, sourceOffset, destArray, destOffset) {
|
|
const e = sourceArray[sourceOffset + 3];
|
|
const scale = Math.pow(2, e - 128) / 255;
|
|
destArray[destOffset + 0] = DataUtils.toHalfFloat(Math.min(sourceArray[sourceOffset + 0] * scale, 65504));
|
|
destArray[destOffset + 1] = DataUtils.toHalfFloat(Math.min(sourceArray[sourceOffset + 1] * scale, 65504));
|
|
destArray[destOffset + 2] = DataUtils.toHalfFloat(Math.min(sourceArray[sourceOffset + 2] * scale, 65504));
|
|
destArray[destOffset + 3] = DataUtils.toHalfFloat(1);
|
|
};
|
|
const byteArray = new Uint8Array(buffer);
|
|
byteArray.pos = 0;
|
|
const rgbe_header_info = RGBE_ReadHeader(byteArray);
|
|
const w = rgbe_header_info.width, h = rgbe_header_info.height, image_rgba_data = RGBE_ReadPixels_RLE(byteArray.subarray(byteArray.pos), w, h);
|
|
let data, type;
|
|
let numElements;
|
|
switch (this.type) {
|
|
case FloatType:
|
|
numElements = image_rgba_data.length / 4;
|
|
const floatArray = new Float32Array(numElements * 4);
|
|
for (let j = 0; j < numElements; j++) {
|
|
RGBEByteToRGBFloat(image_rgba_data, j * 4, floatArray, j * 4);
|
|
}
|
|
data = floatArray;
|
|
type = FloatType;
|
|
break;
|
|
case HalfFloatType:
|
|
numElements = image_rgba_data.length / 4;
|
|
const halfArray = new Uint16Array(numElements * 4);
|
|
for (let j = 0; j < numElements; j++) {
|
|
RGBEByteToRGBHalf(image_rgba_data, j * 4, halfArray, j * 4);
|
|
}
|
|
data = halfArray;
|
|
type = HalfFloatType;
|
|
break;
|
|
default:
|
|
throw new Error("THREE.RGBELoader: Unsupported type: " + this.type);
|
|
}
|
|
return {
|
|
width: w,
|
|
height: h,
|
|
data,
|
|
header: rgbe_header_info.string,
|
|
gamma: rgbe_header_info.gamma,
|
|
exposure: rgbe_header_info.exposure,
|
|
type
|
|
};
|
|
}
|
|
setDataType(value) {
|
|
this.type = value;
|
|
return this;
|
|
}
|
|
load(url, onLoad, onProgress, onError) {
|
|
function onLoadCallback(texture, texData) {
|
|
switch (texture.type) {
|
|
case FloatType:
|
|
case HalfFloatType:
|
|
if ("colorSpace" in texture)
|
|
texture.colorSpace = "srgb-linear";
|
|
else
|
|
texture.encoding = 3e3;
|
|
texture.minFilter = LinearFilter;
|
|
texture.magFilter = LinearFilter;
|
|
texture.generateMipmaps = false;
|
|
texture.flipY = true;
|
|
break;
|
|
}
|
|
if (onLoad)
|
|
onLoad(texture, texData);
|
|
}
|
|
return super.load(url, onLoadCallback, onProgress, onError);
|
|
}
|
|
}
|
|
export {
|
|
RGBELoader
|
|
};
|
|
//# sourceMappingURL=RGBELoader.js.map
|