UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

253 lines (247 loc) 7.01 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import { createErrorResponse } from './WorkerPool'; // Redeclare those constants to avoid importing them from three.js, since // that would have a huge impact on bundling this file in an inlined worker, // because three.js is notoriously un-tree-shakeable. // (Importing types from three.js is fine, however). const UnsignedByteType = 1009; const FloatType = 1015; export const OPAQUE_BYTE = 255; export const OPAQUE_FLOAT = 1.0; export const TRANSPARENT = 0; export const DEFAULT_NODATA = 0; export function getTypedArrayType(array) { if (array instanceof Float32Array) { return 'Float32Array'; } if (array instanceof Float64Array) { return 'Float64Array'; } if (array instanceof Uint32Array) { return 'Uint32Array'; } if (array instanceof Uint16Array) { return 'Uint16Array'; } if (array instanceof Int32Array) { return 'Int32Array'; } if (array instanceof Int16Array) { return 'Int16Array'; } if (array instanceof Uint8Array) { return 'Uint8Array'; } if (array instanceof Int8Array) { return 'Int8Array'; } if (array instanceof Uint8ClampedArray) { return 'Uint8ClampedArray'; } throw new Error('unsupported type'); } export function createTypedArrayFromBuffer(buf, type) { // Case 1: a texture data type if (typeof type === 'number') { switch (type) { case UnsignedByteType: return new Uint8ClampedArray(buf); case FloatType: return new Float32Array(buf); } } else { // Case 2: a typed array type switch (type) { case 'Float32Array': return new Float32Array(buf); case 'Float64Array': return new Float64Array(buf); case 'Uint8ClampedArray': return new Uint8ClampedArray(buf); case 'Uint8Array': return new Uint8Array(buf); case 'Uint16Array': return new Uint16Array(buf); case 'Uint32Array': return new Uint32Array(buf); case 'Int8Array': return new Int8Array(buf); case 'Int16Array': return new Int16Array(buf); case 'Int32Array': return new Int32Array(buf); } } throw new Error('invalid state'); } // Important note : a lot of code is duplicated to avoid putting // conditional branches inside loops, as this can severely reduce performance. // Note: we don't use Number.isNan(x) in the loops as it slows down the loop due to function // invocation. Instead, we use x !== x, as a NaN is never equal to itself. export function createPixelBuffer(options) { const pixelData = options.input.map(buf => createTypedArrayFromBuffer(buf, options.inputType)); const opaqueValue = options.opaqueValue; let buf; if (options.bufferSize && options.dataType) { switch (options.dataType) { case FloatType: buf = new Float32Array(options.bufferSize); break; case UnsignedByteType: buf = new Uint8ClampedArray(options.bufferSize); break; default: throw new Error('unrecognized buffer type: ' + options.dataType); } } else { console.error('missing values'); throw new Error('missing values'); } let min = +Infinity; let max = -Infinity; let isTransparent = true; if (pixelData.length === 1) { const v = pixelData[0]; const length = v.length; for (let i = 0; i < length; i++) { const idx = i * 2; let value; let a; const raw = v[i]; if (raw !== raw || raw === options.nodata) { value = DEFAULT_NODATA; a = TRANSPARENT; } else { value = raw; a = opaqueValue; isTransparent = false; } min = Math.min(min, value); max = Math.max(max, value); buf[idx + 0] = value; buf[idx + 1] = a; } } if (pixelData.length === 2) { const v = pixelData[0]; const a = pixelData[1]; const length = v.length; for (let i = 0; i < length; i++) { const idx = i * 2; let value; const raw = v[i]; const alpha = a[i]; if (raw !== raw || raw === options.nodata) { value = DEFAULT_NODATA; } else { value = raw; } if (alpha > 0) { isTransparent = false; } min = Math.min(min, value); max = Math.max(max, value); buf[idx + 0] = value; buf[idx + 1] = a[i]; } } if (pixelData.length === 3) { const rChannel = pixelData[0]; const gChannel = pixelData[1]; const bChannel = pixelData[2]; const length = rChannel.length; let a; for (let i = 0; i < length; i++) { const idx = i * 4; let r = rChannel[i]; let g = gChannel[i]; let b = bChannel[i]; if ((r !== r || r === options.nodata) && (g !== g || g === options.nodata) && (b !== b || b === options.nodata)) { r = DEFAULT_NODATA; g = DEFAULT_NODATA; b = DEFAULT_NODATA; a = TRANSPARENT; } else { a = opaqueValue; isTransparent = false; } buf[idx + 0] = r; buf[idx + 1] = g; buf[idx + 2] = b; buf[idx + 3] = a; } } if (pixelData.length === 4) { const rChannel = pixelData[0]; const gChannel = pixelData[1]; const bChannel = pixelData[2]; const aChannel = pixelData[3]; const length = rChannel.length; for (let i = 0; i < length; i++) { const idx = i * 4; let r = rChannel[i]; let g = gChannel[i]; let b = bChannel[i]; let a = aChannel[i]; if ((r !== r || r === options.nodata) && (g !== g || g === options.nodata) && (b !== b || b === options.nodata)) { r = DEFAULT_NODATA; g = DEFAULT_NODATA; b = DEFAULT_NODATA; a = TRANSPARENT; } else { if (a > 0) { isTransparent = false; } } buf[idx + 0] = r; buf[idx + 1] = g; buf[idx + 2] = b; buf[idx + 3] = a; } } return { buffer: buf.buffer, min, max, isTransparent }; } // Web worker implementation onmessage = async function (ev) { const message = ev.data; try { switch (message.type) { case 'CreatePixelBuffer': { const result = createPixelBuffer(message.payload); const response = { requestId: message.id, payload: result }; this.postMessage(response, { transfer: [response.payload.buffer] }); } break; case 'CreateImageBitmap': { const blob = new Blob([message.payload.buffer]); const bitmap = await createImageBitmap(blob, message.payload.options); const response = { requestId: message.id, payload: bitmap }; this.postMessage(response, { transfer: [bitmap] }); } break; } } catch (err) { this.postMessage(createErrorResponse(message.id, err)); } };