@renderlayer/extras
Version:
324 lines (320 loc) • 10.7 kB
JavaScript
import { toHalfFloat } from '@renderlayer/buffers';
import { DataTextureLoader } from '@renderlayer/loaders';
import { HalfFloatType, FloatType, LinearSRGBColorSpace, LinearFilter } from '@renderlayer/shared';
class Clock {
autoStart;
startTime = 0;
oldTime = 0;
elapsedTime = 0;
running = false;
constructor(autoStart = true) {
this.autoStart = autoStart;
}
start() {
this.startTime = _now();
this.oldTime = this.startTime;
this.elapsedTime = 0;
this.running = true;
}
stop() {
this.getElapsedTime();
this.running = false;
this.autoStart = false;
}
getElapsedTime() {
this.getDelta();
return this.elapsedTime;
}
getDelta() {
let diff = 0;
if (this.autoStart && !this.running) {
this.start();
return 0;
}
if (this.running) {
const newTime = _now();
diff = (newTime - this.oldTime) / 1e3;
this.oldTime = newTime;
this.elapsedTime += diff;
}
return diff;
}
}
function _now() {
return (typeof performance === "undefined" ? Date : performance).now();
}
class RGBELoader extends DataTextureLoader {
type = HalfFloatType;
constructor(manager) {
super(manager);
}
// adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html
parse(buffer) {
const rgbe_read_error = 1;
const rgbe_write_error = 2;
const rgbe_format_error = 3;
const rgbe_memory_error = 4;
const rgbe_error = (rgbe_error_code, msg) => {
switch (rgbe_error_code) {
case rgbe_read_error:
throw new Error(`RGBELoader: Read Error: ${msg || ""}`);
case rgbe_write_error:
throw new Error(`RGBELoader: Write Error: ${msg || ""}`);
case rgbe_format_error:
throw new Error(`RGBELoader: Bad File Format: ${msg || ""}`);
default:
case rgbe_memory_error:
throw new Error(`RGBELoader: Memory Error: ${msg || ""}`);
}
};
const RGBE_VALID_PROGRAMTYPE = 1;
const RGBE_VALID_FORMAT = 2;
const RGBE_VALID_DIMENSIONS = 4;
const NEWLINE = "\n";
const fgets = (buffer2, lineLimit, consume) => {
const chunkSize = 128;
lineLimit = !lineLimit ? 1024 : lineLimit;
let p = buffer2.pos;
let i = -1;
let len = 0;
let s = "";
let chunk = String.fromCharCode.apply(
null,
new Uint16Array(buffer2.subarray(p, p + chunkSize))
);
while ((i = chunk.indexOf(NEWLINE)) < 0 && 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 (i > -1) {
buffer2.pos += len + i + 1;
return s + chunk.slice(0, i);
}
return false;
};
const RGBE_ReadHeader = (buffer2) => {
const magic_token_re = /^#\?(\S+)/;
const gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/;
const exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/;
const format_re = /^\s*FORMAT=(\S+)\s*$/;
const dimensions_re = /^\s*-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/;
const header = {
valid: 0,
string: "",
comments: "",
programtype: "RGBE",
format: "",
gamma: 1,
exposure: 1,
width: 0,
height: 0
/* image dimensions, width/height */
};
let line;
let 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}
`;
while (true) {
line = fgets(buffer2);
if (line === false) break;
header.string += `${line}
`;
if (line.charAt(0) === "#") {
header.comments += `${line}
`;
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;
};
const RGBE_ReadPixels_RLE = (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
buffer2[0] !== 2 || buffer2[1] !== 2 || 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;
let 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 (rgbeStart[0] !== 2 || rgbeStart[1] !== 2 || (rgbeStart[2] << 8 | rgbeStart[3]) !== scanline_width) {
rgbe_error(rgbe_format_error, "bad rgbe scanline format");
}
let ptr = 0;
let count;
while (ptr < ptr_end && pos < buffer2.byteLength) {
count = buffer2[pos++];
const isEncodedRun = count > 128;
if (isEncodedRun) count -= 128;
if (count === 0 || 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 = (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 = (sourceArray, sourceOffset, destArray, destOffset) => {
const e = sourceArray[sourceOffset + 3];
const scale = Math.pow(2, e - 128) / 255;
destArray[destOffset + 0] = toHalfFloat(
Math.min(sourceArray[sourceOffset + 0] * scale, 65504)
);
destArray[destOffset + 1] = toHalfFloat(
Math.min(sourceArray[sourceOffset + 1] * scale, 65504)
);
destArray[destOffset + 2] = toHalfFloat(
Math.min(sourceArray[sourceOffset + 2] * scale, 65504)
);
destArray[destOffset + 3] = toHalfFloat(1);
};
const byteArray = new Uint8Array(buffer);
byteArray.pos = 0;
const rgbe_header_info = RGBE_ReadHeader(byteArray);
const w = rgbe_header_info.width;
const h = rgbe_header_info.height;
const image_rgba_data = RGBE_ReadPixels_RLE(byteArray.subarray(byteArray.pos), w, h);
let data;
let type;
let numElements;
let floatArray;
let halfArray;
switch (this.type) {
case FloatType:
numElements = image_rgba_data.length / 4;
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;
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(`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:
texture.colorSpace = LinearSRGBColorSpace;
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 { Clock, RGBELoader };