UNPKG

@renderlayer/extras

Version:
313 lines (309 loc) 10.6 kB
import { toHalfFloat } from '@renderlayer/buffers'; import { DataTextureLoader } from '@renderlayer/loaders'; import { HalfFloatType, FloatType, LinearSRGBColorSpace, LinearFilter } from '@renderlayer/shared'; function now() { return (typeof performance === "undefined" ? Date : performance).now(); } class Clock { constructor(autoStart = true) { this.autoStart = autoStart; this.startTime = 0; this.oldTime = 0; this.elapsedTime = 0; this.running = false; } 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; } } 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; 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, i = -1, len = 0, s = "", 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) { if (consume !== false) buffer2.pos += len + i + 1; return s + chunk.slice(0, i); } return false; }; const RGBE_ReadHeader = (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 /* image dimensions, width/height */ }; 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} `; 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, 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, 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 };