@kitware/vtk.js
Version:
Visualization Toolkit for the Web
1,380 lines (1,055 loc) • 54.4 kB
JavaScript
import _defineProperty from '@babel/runtime/helpers/defineProperty';
import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
import Constants from './Texture/Constants.js';
import HalfFloat from '../../Common/Core/HalfFloat.js';
import { newInstance as newInstance$1, obj, set, setGet, get, moveToProtected, newTypedArray, vtkDebugMacro as vtkDebugMacro$1, vtkErrorMacro as vtkErrorMacro$1, vtkWarningMacro as vtkWarningMacro$1 } from '../../macros.js';
import vtkDataArray from '../../Common/Core/DataArray.js';
import { Q as isPowerOfTwo, M as nearestPowerOfTwo } from '../../Common/Core/Math/index.js';
import vtkViewNode from '../SceneGraph/ViewNode.js';
import { registerOverride } from './ViewNodeFactory.js';
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
var Wrap = Constants.Wrap,
Filter = Constants.Filter;
var VtkDataTypes = vtkDataArray.VtkDataTypes;
var vtkDebugMacro = vtkDebugMacro$1,
vtkErrorMacro = vtkErrorMacro$1,
vtkWarningMacro = vtkWarningMacro$1;
var toHalf = HalfFloat.toHalf; // ----------------------------------------------------------------------------
// vtkOpenGLTexture methods
// ----------------------------------------------------------------------------
function vtkOpenGLTexture(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkOpenGLTexture'); // Renders myself
publicAPI.render = function () {
var renWin = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
if (renWin) {
model._openGLRenderWindow = renWin;
} else {
model._openGLRenderer = publicAPI.getFirstAncestorOfType('vtkOpenGLRenderer'); // sync renderable properties
model._openGLRenderWindow = model._openGLRenderer.getParent();
}
model.context = model._openGLRenderWindow.getContext();
if (model.renderable.getInterpolate()) {
if (model.generateMipmap) {
publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
} else {
publicAPI.setMinificationFilter(Filter.LINEAR);
}
publicAPI.setMagnificationFilter(Filter.LINEAR);
} else {
publicAPI.setMinificationFilter(Filter.NEAREST);
publicAPI.setMagnificationFilter(Filter.NEAREST);
}
if (model.renderable.getRepeat()) {
publicAPI.setWrapR(Wrap.REPEAT);
publicAPI.setWrapS(Wrap.REPEAT);
publicAPI.setWrapT(Wrap.REPEAT);
} // clear image if input data is set
if (model.renderable.getInputData()) {
model.renderable.setImage(null);
} // create the texture if it is not done already
if (!model.handle || model.renderable.getMTime() > model.textureBuildTime.getMTime()) {
// if we have an Image
if (model.renderable.getImage() !== null) {
if (model.renderable.getInterpolate()) {
model.generateMipmap = true;
publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
} // Have an Image which may not be complete
if (model.renderable.getImage() && model.renderable.getImageLoaded()) {
publicAPI.create2DFromImage(model.renderable.getImage());
publicAPI.activate();
publicAPI.sendParameters();
model.textureBuildTime.modified();
}
} // if we have a canvas
if (model.renderable.getCanvas() !== null) {
if (model.renderable.getInterpolate()) {
model.generateMipmap = true;
publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
}
var canvas = model.renderable.getCanvas();
publicAPI.create2DFromRaw(canvas.width, canvas.height, 4, VtkDataTypes.UNSIGNED_CHAR, canvas, true);
publicAPI.activate();
publicAPI.sendParameters();
model.textureBuildTime.modified();
} // if we have jsImageData
if (model.renderable.getJsImageData() !== null) {
var jsid = model.renderable.getJsImageData();
if (model.renderable.getInterpolate()) {
model.generateMipmap = true;
publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
}
publicAPI.create2DFromRaw(jsid.width, jsid.height, 4, VtkDataTypes.UNSIGNED_CHAR, jsid.data, true);
publicAPI.activate();
publicAPI.sendParameters();
model.textureBuildTime.modified();
} // if we have InputData
var input = model.renderable.getInputData(0);
if (input && input.getPointData().getScalars()) {
var ext = input.getExtent();
var inScalars = input.getPointData().getScalars(); // do we have a cube map? Six inputs
var data = [];
for (var i = 0; i < model.renderable.getNumberOfInputPorts(); ++i) {
var indata = model.renderable.getInputData(i);
var scalars = indata ? indata.getPointData().getScalars().getData() : null;
if (scalars) {
data.push(scalars);
}
}
if (model.renderable.getInterpolate() && inScalars.getNumberOfComponents() === 4) {
model.generateMipmap = true;
publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
}
if (data.length % 6 === 0) {
publicAPI.createCubeFromRaw(ext[1] - ext[0] + 1, ext[3] - ext[2] + 1, inScalars.getNumberOfComponents(), inScalars.getDataType(), data);
} else {
publicAPI.create2DFromRaw(ext[1] - ext[0] + 1, ext[3] - ext[2] + 1, inScalars.getNumberOfComponents(), inScalars.getDataType(), inScalars.getData());
}
publicAPI.activate();
publicAPI.sendParameters();
model.textureBuildTime.modified();
}
}
if (model.handle) {
publicAPI.activate();
}
}; //----------------------------------------------------------------------------
publicAPI.destroyTexture = function () {
// deactivate it first
publicAPI.deactivate();
if (model.context && model.handle) {
model.context.deleteTexture(model.handle);
}
model.handle = 0;
model.numberOfDimensions = 0;
model.target = 0;
model.components = 0;
model.width = 0;
model.height = 0;
model.depth = 0;
publicAPI.resetFormatAndType();
}; //----------------------------------------------------------------------------
publicAPI.createTexture = function () {
// reuse the existing handle if we have one
if (!model.handle) {
model.handle = model.context.createTexture();
if (model.target) {
model.context.bindTexture(model.target, model.handle); // See: http://www.openmodel.context..org/wiki/Common_Mistakes#Creating_a_complete_texture
// turn off mip map filter or set the base and max level correctly. here
// both are done.
model.context.texParameteri(model.target, model.context.TEXTURE_MIN_FILTER, publicAPI.getOpenGLFilterMode(model.minificationFilter));
model.context.texParameteri(model.target, model.context.TEXTURE_MAG_FILTER, publicAPI.getOpenGLFilterMode(model.magnificationFilter));
model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_S, publicAPI.getOpenGLWrapMode(model.wrapS));
model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_T, publicAPI.getOpenGLWrapMode(model.wrapT));
if (model._openGLRenderWindow.getWebgl2()) {
model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_R, publicAPI.getOpenGLWrapMode(model.wrapR));
}
model.context.bindTexture(model.target, null);
}
}
}; //---------------------------------------------------------------------------
publicAPI.getTextureUnit = function () {
if (model._openGLRenderWindow) {
return model._openGLRenderWindow.getTextureUnitForTexture(publicAPI);
}
return -1;
}; //---------------------------------------------------------------------------
publicAPI.activate = function () {
// activate a free texture unit for this texture
model._openGLRenderWindow.activateTexture(publicAPI);
publicAPI.bind();
}; //---------------------------------------------------------------------------
publicAPI.deactivate = function () {
if (model._openGLRenderWindow) {
model._openGLRenderWindow.deactivateTexture(publicAPI);
}
}; //---------------------------------------------------------------------------
publicAPI.releaseGraphicsResources = function (rwin) {
if (rwin && model.handle) {
rwin.activateTexture(publicAPI);
rwin.deactivateTexture(publicAPI);
model.context.deleteTexture(model.handle);
model.handle = 0;
model.numberOfDimensions = 0;
model.target = 0;
model.internalFormat = 0;
model.format = 0;
model.openGLDataType = 0;
model.components = 0;
model.width = 0;
model.height = 0;
model.depth = 0;
}
if (model.shaderProgram) {
model.shaderProgram.releaseGraphicsResources(rwin);
model.shaderProgram = null;
}
}; //----------------------------------------------------------------------------
publicAPI.bind = function () {
model.context.bindTexture(model.target, model.handle);
if (model.autoParameters && publicAPI.getMTime() > model.sendParametersTime.getMTime()) {
publicAPI.sendParameters();
}
}; //----------------------------------------------------------------------------
publicAPI.isBound = function () {
var result = false;
if (model.context && model.handle) {
var target = 0;
switch (model.target) {
case model.context.TEXTURE_2D:
target = model.context.TEXTURE_BINDING_2D;
break;
default:
vtkWarningMacro('impossible case');
break;
}
var oid = model.context.getIntegerv(target);
result = oid === model.handle;
}
return result;
}; //----------------------------------------------------------------------------
publicAPI.sendParameters = function () {
model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_S, publicAPI.getOpenGLWrapMode(model.wrapS));
model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_T, publicAPI.getOpenGLWrapMode(model.wrapT));
if (model._openGLRenderWindow.getWebgl2()) {
model.context.texParameteri(model.target, model.context.TEXTURE_WRAP_R, publicAPI.getOpenGLWrapMode(model.wrapR));
}
model.context.texParameteri(model.target, model.context.TEXTURE_MIN_FILTER, publicAPI.getOpenGLFilterMode(model.minificationFilter));
model.context.texParameteri(model.target, model.context.TEXTURE_MAG_FILTER, publicAPI.getOpenGLFilterMode(model.magnificationFilter));
if (model._openGLRenderWindow.getWebgl2()) {
model.context.texParameteri(model.target, model.context.TEXTURE_BASE_LEVEL, model.baseLevel);
model.context.texParameteri(model.target, model.context.TEXTURE_MAX_LEVEL, model.maxLevel);
} // model.context.texParameterf(model.target, model.context.TEXTURE_MIN_LOD, model.minLOD);
// model.context.texParameterf(model.target, model.context.TEXTURE_MAX_LOD, model.maxLOD);
model.sendParametersTime.modified();
}; //----------------------------------------------------------------------------
publicAPI.getInternalFormat = function (vtktype, numComps) {
if (!model._forceInternalFormat) {
model.internalFormat = publicAPI.getDefaultInternalFormat(vtktype, numComps);
}
if (!model.internalFormat) {
vtkDebugMacro("Unable to find suitable internal format for T=".concat(vtktype, " NC= ").concat(numComps));
}
return model.internalFormat;
}; //----------------------------------------------------------------------------
publicAPI.getDefaultInternalFormat = function (vtktype, numComps) {
var result = 0; // try default next
result = model._openGLRenderWindow.getDefaultTextureInternalFormat(vtktype, numComps, model.oglNorm16Ext, model.useHalfFloat);
if (result) {
return result;
}
if (!result) {
vtkDebugMacro('Unsupported internal texture type!');
vtkDebugMacro("Unable to find suitable internal format for T=".concat(vtktype, " NC= ").concat(numComps));
}
return result;
}; //----------------------------------------------------------------------------
publicAPI.setInternalFormat = function (iFormat) {
model._forceInternalFormat = true;
if (iFormat !== model.internalFormat) {
model.internalFormat = iFormat;
publicAPI.modified();
}
}; //----------------------------------------------------------------------------
publicAPI.getFormat = function (vtktype, numComps) {
model.format = publicAPI.getDefaultFormat(vtktype, numComps);
return model.format;
}; //----------------------------------------------------------------------------
publicAPI.getDefaultFormat = function (vtktype, numComps) {
if (model._openGLRenderWindow.getWebgl2()) {
switch (numComps) {
case 1:
return model.context.RED;
case 2:
return model.context.RG;
case 3:
return model.context.RGB;
case 4:
return model.context.RGBA;
default:
return model.context.RGB;
}
} else {
// webgl1
switch (numComps) {
case 1:
return model.context.LUMINANCE;
case 2:
return model.context.LUMINANCE_ALPHA;
case 3:
return model.context.RGB;
case 4:
return model.context.RGBA;
default:
return model.context.RGB;
}
}
}; //----------------------------------------------------------------------------
publicAPI.resetFormatAndType = function () {
model.format = 0;
model.internalFormat = 0;
model._forceInternalFormat = false;
model.openGLDataType = 0;
}; //----------------------------------------------------------------------------
publicAPI.getDefaultDataType = function (vtkScalarType) {
// DON'T DEAL with VTK_CHAR as this is platform dependent.
if (model._openGLRenderWindow.getWebgl2()) {
switch (vtkScalarType) {
// case VtkDataTypes.SIGNED_CHAR:
// return model.context.BYTE;
case VtkDataTypes.UNSIGNED_CHAR:
return model.context.UNSIGNED_BYTE;
// prefer norm16 since that is accurate compared to
// half float which is not
case model.oglNorm16Ext && !model.useHalfFloat && VtkDataTypes.SHORT:
return model.context.SHORT;
case model.oglNorm16Ext && !model.useHalfFloat && VtkDataTypes.UNSIGNED_SHORT:
return model.context.UNSIGNED_SHORT;
// use half float type
case model.useHalfFloat && VtkDataTypes.SHORT:
return model.context.HALF_FLOAT;
case model.useHalfFloat && VtkDataTypes.UNSIGNED_SHORT:
return model.context.HALF_FLOAT;
// case VtkDataTypes.INT:
// return model.context.INT;
// case VtkDataTypes.UNSIGNED_INT:
// return model.context.UNSIGNED_INT;
case VtkDataTypes.FLOAT:
case VtkDataTypes.VOID: // used for depth component textures.
default:
return model.context.FLOAT;
}
}
switch (vtkScalarType) {
// case VtkDataTypes.SIGNED_CHAR:
// return model.context.BYTE;
case VtkDataTypes.UNSIGNED_CHAR:
return model.context.UNSIGNED_BYTE;
// case VtkDataTypes.SHORT:
// return model.context.SHORT;
// case VtkDataTypes.UNSIGNED_SHORT:
// return model.context.UNSIGNED_SHORT;
// case VtkDataTypes.INT:
// return model.context.INT;
// case VtkDataTypes.UNSIGNED_INT:
// return model.context.UNSIGNED_INT;
case VtkDataTypes.FLOAT:
case VtkDataTypes.VOID: // used for depth component textures.
default:
if (model.context.getExtension('OES_texture_float') && model.context.getExtension('OES_texture_float_linear')) {
return model.context.FLOAT;
}
{
var halfFloat = model.context.getExtension('OES_texture_half_float');
if (halfFloat && model.context.getExtension('OES_texture_half_float_linear')) {
return halfFloat.HALF_FLOAT_OES;
}
}
return model.context.UNSIGNED_BYTE;
}
}; //----------------------------------------------------------------------------
publicAPI.getOpenGLDataType = function (vtkScalarType) {
var forceUpdate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (!model.openGLDataType || forceUpdate) {
model.openGLDataType = publicAPI.getDefaultDataType(vtkScalarType);
}
return model.openGLDataType;
};
publicAPI.getShiftAndScale = function () {
var shift = 0.0;
var scale = 1.0; // for all float type internal formats
switch (model.openGLDataType) {
case model.context.BYTE:
scale = 127.5;
shift = scale - 128.0;
break;
case model.context.UNSIGNED_BYTE:
scale = 255.0;
shift = 0.0;
break;
case model.context.SHORT:
scale = 32767.5;
shift = scale - 32768.0;
break;
case model.context.UNSIGNED_SHORT:
scale = 65536.0;
shift = 0.0;
break;
case model.context.INT:
scale = 2147483647.5;
shift = scale - 2147483648.0;
break;
case model.context.UNSIGNED_INT:
scale = 4294967295.0;
shift = 0.0;
break;
case model.context.FLOAT:
}
return {
shift: shift,
scale: scale
};
}; //----------------------------------------------------------------------------
publicAPI.getOpenGLFilterMode = function (emode) {
switch (emode) {
case Filter.NEAREST:
return model.context.NEAREST;
case Filter.LINEAR:
return model.context.LINEAR;
case Filter.NEAREST_MIPMAP_NEAREST:
return model.context.NEAREST_MIPMAP_NEAREST;
case Filter.NEAREST_MIPMAP_LINEAR:
return model.context.NEAREST_MIPMAP_LINEAR;
case Filter.LINEAR_MIPMAP_NEAREST:
return model.context.LINEAR_MIPMAP_NEAREST;
case Filter.LINEAR_MIPMAP_LINEAR:
return model.context.LINEAR_MIPMAP_LINEAR;
default:
return model.context.NEAREST;
}
}; //----------------------------------------------------------------------------
publicAPI.getOpenGLWrapMode = function (vtktype) {
switch (vtktype) {
case Wrap.CLAMP_TO_EDGE:
return model.context.CLAMP_TO_EDGE;
case Wrap.REPEAT:
return model.context.REPEAT;
case Wrap.MIRRORED_REPEAT:
return model.context.MIRRORED_REPEAT;
default:
return model.context.CLAMP_TO_EDGE;
}
}; //----------------------------------------------------------------------------
function updateArrayDataType(dataType, data) {
var depth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var pixData = [];
var pixCount = model.width * model.height * model.components;
if (depth) {
pixCount *= model.depth;
} // if the opengl data type is float
// then the data array must be float
if (dataType !== VtkDataTypes.FLOAT && model.openGLDataType === model.context.FLOAT) {
for (var idx = 0; idx < data.length; idx++) {
if (data[idx]) {
var dataArrayToCopy = data[idx].length > pixCount ? data[idx].subarray(0, pixCount) : data[idx];
pixData.push(new Float32Array(dataArrayToCopy));
} else {
pixData.push(null);
}
}
} // if the opengl data type is ubyte
// then the data array must be u8, we currently simply truncate the data
if (dataType !== VtkDataTypes.UNSIGNED_CHAR && model.openGLDataType === model.context.UNSIGNED_BYTE) {
for (var _idx = 0; _idx < data.length; _idx++) {
if (data[_idx]) {
var _dataArrayToCopy = data[_idx].length > pixCount ? data[_idx].subarray(0, pixCount) : data[_idx];
pixData.push(new Uint8Array(_dataArrayToCopy));
} else {
pixData.push(null);
}
}
} // if the opengl data type is half float
// then the data array must be u16
var halfFloat = false;
if (model._openGLRenderWindow.getWebgl2()) {
halfFloat = model.openGLDataType === model.context.HALF_FLOAT;
} else {
var halfFloatExt = model.context.getExtension('OES_texture_half_float');
halfFloat = halfFloatExt && model.openGLDataType === halfFloatExt.HALF_FLOAT_OES;
}
if (halfFloat) {
for (var _idx2 = 0; _idx2 < data.length; _idx2++) {
if (data[_idx2]) {
var newArray = new Uint16Array(pixCount);
var src = data[_idx2];
for (var i = 0; i < pixCount; i++) {
newArray[i] = toHalf(src[i]);
}
pixData.push(newArray);
} else {
pixData.push(null);
}
}
} // The output has to be filled
if (pixData.length === 0) {
for (var _i = 0; _i < data.length; _i++) {
pixData.push(data[_i]);
}
}
return pixData;
} //----------------------------------------------------------------------------
function scaleTextureToHighestPowerOfTwo(data) {
if (model._openGLRenderWindow.getWebgl2()) {
// No need if webGL2
return data;
}
var pixData = [];
var width = model.width;
var height = model.height;
var numComps = model.components;
if (data && (!isPowerOfTwo(width) || !isPowerOfTwo(height))) {
// Scale up the texture to the next highest power of two dimensions.
var halfFloat = model.context.getExtension('OES_texture_half_float');
var newWidth = nearestPowerOfTwo(width);
var newHeight = nearestPowerOfTwo(height);
var pixCount = newWidth * newHeight * model.components;
for (var idx = 0; idx < data.length; idx++) {
if (data[idx] !== null) {
var newArray = null;
var jFactor = height / newHeight;
var iFactor = width / newWidth;
var usingHalf = false;
if (model.openGLDataType === model.context.FLOAT) {
newArray = new Float32Array(pixCount);
} else if (halfFloat && model.openGLDataType === halfFloat.HALF_FLOAT_OES) {
newArray = new Uint16Array(pixCount);
usingHalf = true;
} else {
newArray = new Uint8Array(pixCount);
}
for (var j = 0; j < newHeight; j++) {
var joff = j * newWidth * numComps;
var jidx = j * jFactor;
var jlow = Math.floor(jidx);
var jhi = Math.ceil(jidx);
if (jhi >= height) {
jhi = height - 1;
}
var jmix = jidx - jlow;
var jmix1 = 1.0 - jmix;
jlow = jlow * width * numComps;
jhi = jhi * width * numComps;
for (var i = 0; i < newWidth; i++) {
var ioff = i * numComps;
var iidx = i * iFactor;
var ilow = Math.floor(iidx);
var ihi = Math.ceil(iidx);
if (ihi >= width) {
ihi = width - 1;
}
var imix = iidx - ilow;
ilow *= numComps;
ihi *= numComps;
for (var c = 0; c < numComps; c++) {
if (usingHalf) {
newArray[joff + ioff + c] = HalfFloat.toHalf(HalfFloat.fromHalf(data[idx][jlow + ilow + c]) * jmix1 * (1.0 - imix) + HalfFloat.fromHalf(data[idx][jlow + ihi + c]) * jmix1 * imix + HalfFloat.fromHalf(data[idx][jhi + ilow + c]) * jmix * (1.0 - imix) + HalfFloat.fromHalf(data[idx][jhi + ihi + c]) * jmix * imix);
} else {
newArray[joff + ioff + c] = data[idx][jlow + ilow + c] * jmix1 * (1.0 - imix) + data[idx][jlow + ihi + c] * jmix1 * imix + data[idx][jhi + ilow + c] * jmix * (1.0 - imix) + data[idx][jhi + ihi + c] * jmix * imix;
}
}
}
}
pixData.push(newArray);
model.width = newWidth;
model.height = newHeight;
} else {
pixData.push(null);
}
}
} // The output has to be filled
if (pixData.length === 0) {
for (var _i2 = 0; _i2 < data.length; _i2++) {
pixData.push(data[_i2]);
}
}
return pixData;
} //----------------------------------------------------------------------------
function useTexStorage(dataType) {
if (model._openGLRenderWindow) {
var _model$renderable;
if (model.resizable || (_model$renderable = model.renderable) !== null && _model$renderable !== void 0 && _model$renderable.getResizable()) {
// Cannot use texStorage if the texture is supposed to be resizable.
return false;
}
if (model._openGLRenderWindow.getWebgl2()) {
var webGLInfo = model._openGLRenderWindow.getGLInformations();
if (webGLInfo.RENDERER.value.match(/WebKit/gi) && navigator.platform.match(/Mac/gi) && model.oglNorm16Ext && (dataType === VtkDataTypes.UNSIGNED_SHORT || dataType === VtkDataTypes.SHORT)) {
// Cannot use texStorage with EXT_texture_norm16 textures on Mac M1 GPU.
// No errors reported but the texture is unusable.
return false;
} // Use texStorage for WebGL2
return true;
}
return false;
}
return false;
} //----------------------------------------------------------------------------
publicAPI.create2DFromRaw = function (width, height, numComps, dataType, data) {
var flip = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
// Now determine the texture parameters using the arguments.
publicAPI.getOpenGLDataType(dataType, true);
publicAPI.getInternalFormat(dataType, numComps);
publicAPI.getFormat(dataType, numComps);
if (!model.internalFormat || !model.format || !model.openGLDataType) {
vtkErrorMacro('Failed to determine texture parameters.');
return false;
}
model.target = model.context.TEXTURE_2D;
model.components = numComps;
model.width = width;
model.height = height;
model.depth = 1;
model.numberOfDimensions = 2;
model._openGLRenderWindow.activateTexture(publicAPI);
publicAPI.createTexture();
publicAPI.bind(); // Create an array of texture with one texture
var dataArray = [data];
var pixData = updateArrayDataType(dataType, dataArray);
var scaledData = scaleTextureToHighestPowerOfTwo(pixData); // Source texture data from the PBO.
model.context.pixelStorei(model.context.UNPACK_FLIP_Y_WEBGL, flip);
model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
if (useTexStorage(dataType)) {
model.context.texStorage2D(model.target, 1, model.internalFormat, model.width, model.height);
if (scaledData[0] != null) {
model.context.texSubImage2D(model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, scaledData[0]);
}
} else {
model.context.texImage2D(model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, scaledData[0]);
}
if (model.generateMipmap) {
model.context.generateMipmap(model.target);
} // always reset the flip
if (flip) {
model.context.pixelStorei(model.context.UNPACK_FLIP_Y_WEBGL, false);
}
publicAPI.deactivate();
return true;
}; //----------------------------------------------------------------------------
publicAPI.createCubeFromRaw = function (width, height, numComps, dataType, data) {
// Now determine the texture parameters using the arguments.
publicAPI.getOpenGLDataType(dataType);
publicAPI.getInternalFormat(dataType, numComps);
publicAPI.getFormat(dataType, numComps);
if (!model.internalFormat || !model.format || !model.openGLDataType) {
vtkErrorMacro('Failed to determine texture parameters.');
return false;
}
model.target = model.context.TEXTURE_CUBE_MAP;
model.components = numComps;
model.width = width;
model.height = height;
model.depth = 1;
model.numberOfDimensions = 2;
model._openGLRenderWindow.activateTexture(publicAPI);
model.maxLevel = data.length / 6 - 1;
publicAPI.createTexture();
publicAPI.bind();
var pixData = updateArrayDataType(dataType, data);
var scaledData = scaleTextureToHighestPowerOfTwo(pixData); // invert the data because opengl is messed up with cube maps
// and uses the old renderman standard with Y going down
// even though it is completely at odds with OpenGL standards
var invertedData = [];
var widthLevel = model.width;
var heightLevel = model.height;
for (var i = 0; i < scaledData.length; i++) {
if (i % 6 === 0 && i !== 0) {
widthLevel /= 2;
heightLevel /= 2;
}
invertedData[i] = newTypedArray(dataType, heightLevel * widthLevel * model.components);
for (var y = 0; y < heightLevel; ++y) {
var row1 = y * widthLevel * model.components;
var row2 = (heightLevel - y - 1) * widthLevel * model.components;
invertedData[i].set(scaledData[i].slice(row2, row2 + widthLevel * model.components), row1);
}
} // Source texture data from the PBO.
model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
if (useTexStorage(dataType)) {
model.context.texStorage2D(model.target, 6, model.internalFormat, model.width, model.height);
} // We get the 6 images
for (var _i3 = 0; _i3 < 6; _i3++) {
// For each mipmap level
var j = 0;
var w = model.width;
var h = model.height;
while (w >= 1 && h >= 1) {
// In webgl 1, all levels need to be defined. So if the latest level size is
// 8x8, we have to add 3 more null textures (4x4, 2x2, 1x1)
// In webgl 2, the attribute maxLevel will be use.
var tempData = null;
if (j <= model.maxLevel) {
tempData = invertedData[6 * j + _i3];
}
if (useTexStorage(dataType)) {
if (tempData != null) {
model.context.texSubImage2D(model.context.TEXTURE_CUBE_MAP_POSITIVE_X + _i3, j, 0, 0, w, h, model.format, model.openGLDataType, tempData);
}
} else {
model.context.texImage2D(model.context.TEXTURE_CUBE_MAP_POSITIVE_X + _i3, j, model.internalFormat, w, h, 0, model.format, model.openGLDataType, tempData);
}
j++;
w /= 2;
h /= 2;
}
} // generateMipmap must not be called here because we manually upload all levels
// if it is called, all levels will be overwritten
publicAPI.deactivate();
return true;
}; //----------------------------------------------------------------------------
publicAPI.createDepthFromRaw = function (width, height, dataType, data) {
// Now determine the texture parameters using the arguments.
publicAPI.getOpenGLDataType(dataType);
model.format = model.context.DEPTH_COMPONENT;
if (model._openGLRenderWindow.getWebgl2()) {
if (dataType === VtkDataTypes.FLOAT) {
model.internalFormat = model.context.DEPTH_COMPONENT32F;
} else {
model.internalFormat = model.context.DEPTH_COMPONENT16;
}
} else {
model.internalFormat = model.context.DEPTH_COMPONENT;
}
if (!model.internalFormat || !model.format || !model.openGLDataType) {
vtkErrorMacro('Failed to determine texture parameters.');
return false;
}
model.target = model.context.TEXTURE_2D;
model.components = 1;
model.width = width;
model.height = height;
model.depth = 1;
model.numberOfDimensions = 2;
model._openGLRenderWindow.activateTexture(publicAPI);
publicAPI.createTexture();
publicAPI.bind(); // Source texture data from the PBO.
// model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
if (useTexStorage(dataType)) {
model.context.texStorage2D(model.target, 1, model.internalFormat, model.width, model.height);
if (data != null) {
model.context.texSubImage2D(model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, data);
}
} else {
model.context.texImage2D(model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, data);
}
if (model.generateMipmap) {
model.context.generateMipmap(model.target);
}
publicAPI.deactivate();
return true;
}; //----------------------------------------------------------------------------
publicAPI.create2DFromImage = function (image) {
// Now determine the texture parameters using the arguments.
publicAPI.getOpenGLDataType(VtkDataTypes.UNSIGNED_CHAR);
publicAPI.getInternalFormat(VtkDataTypes.UNSIGNED_CHAR, 4);
publicAPI.getFormat(VtkDataTypes.UNSIGNED_CHAR, 4);
if (!model.internalFormat || !model.format || !model.openGLDataType) {
vtkErrorMacro('Failed to determine texture parameters.');
return false;
}
model.target = model.context.TEXTURE_2D;
model.components = 4;
model.depth = 1;
model.numberOfDimensions = 2;
model._openGLRenderWindow.activateTexture(publicAPI);
publicAPI.createTexture();
publicAPI.bind(); // Source texture data from the PBO.
// model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1); // Scale up the texture to the next highest power of two dimensions (if needed) and flip y.
var needNearestPowerOfTwo = !model._openGLRenderWindow.getWebgl2() && (!isPowerOfTwo(image.width) || !isPowerOfTwo(image.height));
var canvas = document.createElement('canvas');
canvas.width = needNearestPowerOfTwo ? nearestPowerOfTwo(image.width) : image.width;
canvas.height = needNearestPowerOfTwo ? nearestPowerOfTwo(image.height) : image.height;
model.width = canvas.width;
model.height = canvas.height;
var ctx = canvas.getContext('2d');
ctx.translate(0, canvas.height);
ctx.scale(1, -1);
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
var safeImage = canvas;
if (useTexStorage(VtkDataTypes.UNSIGNED_CHAR)) {
model.context.texStorage2D(model.target, 1, model.internalFormat, model.width, model.height);
if (safeImage != null) {
model.context.texSubImage2D(model.target, 0, 0, 0, model.width, model.height, model.format, model.openGLDataType, safeImage);
}
} else {
model.context.texImage2D(model.target, 0, model.internalFormat, model.width, model.height, 0, model.format, model.openGLDataType, safeImage);
}
if (model.generateMipmap) {
model.context.generateMipmap(model.target);
}
publicAPI.deactivate();
return true;
}; // Compute scale and offset per component from min and max per component
function computeScaleOffsets(min, max, numComps) {
var offset = new Array(numComps);
var scale = new Array(numComps);
for (var c = 0; c < numComps; ++c) {
offset[c] = min[c];
scale[c] = max[c] - min[c] || 1.0;
}
return {
scale: scale,
offset: offset
};
} // HalfFloat only represents numbers between [-2048, 2048] exactly accurate,
// for numbers outside of this range there is a precision limitation
function hasExactHalfFloat(offset, scale) {
// Per Component
for (var c = 0; c < offset.length; c++) {
var min = offset[c];
var max = scale[c] + min;
if (min < -2048 || min > 2048 || max < -2048 || max > 2048) {
return false;
}
}
return true;
}
function setUseHalfFloat(dataType, offset, scale, preferSizeOverAccuracy) {
publicAPI.getOpenGLDataType(dataType);
var useHalfFloat = false;
if (model._openGLRenderWindow.getWebgl2()) {
useHalfFloat = model.openGLDataType === model.context.HALF_FLOAT;
} else {
var halfFloatExt = model.context.getExtension('OES_texture_half_float');
useHalfFloat = halfFloatExt && model.openGLDataType === halfFloatExt.HALF_FLOAT_OES;
} // Don't consider halfFloat and convert back to Float when the range of data does not generate an accurate halfFloat
// AND it is not preferable to have a smaller texture than an exact texture.
var isHalfFloat = useHalfFloat && (hasExactHalfFloat(offset, scale) || preferSizeOverAccuracy);
model.useHalfFloat = isHalfFloat;
}
function processDataArray(dataArray, preferSizeOverAccuracy) {
var numComps = dataArray.getNumberOfComponents();
var dataType = dataArray.getDataType();
var data = dataArray.getData(); // Compute min max from array
// Using the vtkDataArray.getRange() enables caching
var minArray = new Array(numComps);
var maxArray = new Array(numComps);
for (var c = 0; c < numComps; ++c) {
var _dataArray$getRange = dataArray.getRange(c),
_dataArray$getRange2 = _slicedToArray(_dataArray$getRange, 2),
min = _dataArray$getRange2[0],
max = _dataArray$getRange2[1];
minArray[c] = min;
maxArray[c] = max;
}
var scaleOffsets = computeScaleOffsets(minArray, maxArray, numComps); // preferSizeOverAccuracy will override norm16 due to bug with norm16 implementation
// https://bugs.chromium.org/p/chromium/issues/detail?id=1408247
setUseHalfFloat(dataType, scaleOffsets.offset, scaleOffsets.scale, preferSizeOverAccuracy); // since our default is to use half float, in case that we can't use it
// we need to use another type
if (!model.useHalfFloat) {
publicAPI.getOpenGLDataType(dataType, true);
}
return {
numComps: numComps,
dataType: dataType,
data: data,
scaleOffsets: scaleOffsets
};
}
publicAPI.create2DFilterableFromRaw = function (width, height, numberOfComponents, dataType, values) {
var preferSizeOverAccuracy = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
return publicAPI.create2DFilterableFromDataArray(width, height, vtkDataArray.newInstance({
numberOfComponents: numberOfComponents,
dataType: dataType,
values: values
}), preferSizeOverAccuracy);
};
publicAPI.create2DFilterableFromDataArray = function (width, height, dataArray) {
var preferSizeOverAccuracy = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var _processDataArray = processDataArray(dataArray, preferSizeOverAccuracy),
numComps = _processDataArray.numComps,
dataType = _processDataArray.dataType,
data = _processDataArray.data;
publicAPI.create2DFromRaw(width, height, numComps, dataType, data);
}; //----------------------------------------------------------------------------
publicAPI.create3DFromRaw = function (width, height, depth, numComps, dataType, data) {
// Permit OpenGLDataType to be half float, if applicable, for 3D
publicAPI.getOpenGLDataType(dataType); // Now determine the texture parameters using the arguments.
publicAPI.getInternalFormat(dataType, numComps);
publicAPI.getFormat(dataType, numComps);
if (!model.internalFormat || !model.format || !model.openGLDataType) {
vtkErrorMacro('Failed to determine texture parameters.');
return false;
}
model.target = model.context.TEXTURE_3D;
model.components = numComps;
model.width = width;
model.height = height;
model.depth = depth;
model.numberOfDimensions = 3;
model._openGLRenderWindow.activateTexture(publicAPI);
publicAPI.createTexture();
publicAPI.bind(); // Create an array of texture with one texture
var dataArray = [data];
var is3DArray = true;
var pixData = updateArrayDataType(dataType, dataArray, is3DArray);
var scaledData = scaleTextureToHighestPowerOfTwo(pixData); // Source texture data from the PBO.
// model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1); // openGLDataType
if (useTexStorage(dataType)) {
model.context.texStorage3D(model.target, 1, model.internalFormat, model.width, model.height, model.depth);
if (scaledData[0] != null) {
model.context.texSubImage3D(model.target, 0, 0, 0, 0, model.width, model.height, model.depth, model.format, model.openGLDataType, scaledData[0]);
}
} else {
model.context.texImage3D(model.target, 0, model.internalFormat, model.width, model.height, model.depth, 0, model.format, model.openGLDataType, scaledData[0]);
}
if (model.generateMipmap) {
model.context.generateMipmap(model.target);
}
publicAPI.deactivate();
return true;
}; //----------------------------------------------------------------------------
// This method simulates a 3D texture using 2D
// Prefer create3DFilterableFromDataArray to enable caching of min and max values
publicAPI.create3DFilterableFromRaw = function (width, height, depth, numberOfComponents, dataType, values) {
var preferSizeOverAccuracy = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false;
return publicAPI.create3DFilterableFromDataArray(width, height, depth, vtkDataArray.newInstance({
numberOfComponents: numberOfComponents,
dataType: dataType,
values: values
}), preferSizeOverAccuracy);
}; //----------------------------------------------------------------------------
// This method create a 3D texture from dimensions and a DataArray
publicAPI.create3DFilterableFromDataArray = function (width, height, depth, dataArray) {
var preferSizeOverAccuracy = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
var _processDataArray2 = processDataArray(dataArray, preferSizeOverAccuracy),
numComps = _processDataArray2.numComps,
dataType = _processDataArray2.dataType,
data = _processDataArray2.data,
scaleOffsets = _processDataArray2.scaleOffsets;
var numPixelsIn = width * height * depth;
var offset = [];
var scale = [];
for (var c = 0; c < numComps; ++c) {
offset[c] = 0.0;
scale[c] = 1.0;
} // store the information, we will need it later
// offset and scale are the offset and scale required to get
// the texture value back to data values ala
// data = texture * scale + offset
// and texture = (data - offset)/scale
model.volumeInfo = {
scale: scale,
offset: offset,
dataComputedScale: scaleOffsets.scale,
dataComputedOffset: scaleOffsets.offset,
width: width,
height: height,
depth: depth
}; // Create a copy of scale and offset to avoid aliasing issues
// Original is read only, copy is read/write
// Use the copy as volumeInfo.scale and volumeInfo.offset
var scaleOffsetsCopy = structuredClone(scaleOffsets); // WebGL2 path, we have 3d textures etc
if (model._openGLRenderWindow.getWebgl2()) {
if (model.oglNorm16Ext && !model.useHalfFloat && dataType === VtkDataTypes.SHORT) {
for (var _c = 0; _c < numComps; ++_c) {
model.volumeInfo.scale[_c] = 32767.0;
}
return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data);
}
if (model.oglNorm16Ext && !model.useHalfFloat && dataType === VtkDataTypes.UNSIGNED_SHORT) {
for (var _c2 = 0; _c2 < numComps; ++_c2) {
model.volumeInfo.scale[_c2] = 65535.0;
}
return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data);
}
if (dataType === VtkDataTypes.FLOAT || model.useHalfFloat && (dataType === VtkDataTypes.SHORT || dataType === VtkDataTypes.UNSIGNED_SHORT)) {
return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data);
}
if (dataType === VtkDataTypes.UNSIGNED_CHAR) {
for (var _c3 = 0; _c3 < numComps; ++_c3) {
model.volumeInfo.scale[_c3] = 255.0;
}
return publicAPI.create3DFromRaw(width, height, depth, numComps, dataType, data);
} // otherwise convert to float
var _newArray = new Float32Array(numPixelsIn * numComps); // use computed scale and offset
model.volumeInfo.offset = scaleOffsetsCopy.offset;
model.volumeInfo.scale = scaleOffsetsCopy.scale;
var count = 0;
var scaleInverse = scaleOffsetsCopy.scale.map(function (s) {
return 1 / s;
});
for (var i = 0; i < numPixelsIn; i++) {
for (var nc = 0; nc < numComps; nc++) {
_newArray[count] = (data[count] - scaleOffsetsCopy.offset[nc]) * scaleInverse[nc];
count++;
}
}
return publicAPI.create3DFromRaw(width, height, depth, numComps, VtkDataTypes.FLOAT, _newArray);
} // not webgl2, deal with webgl1, no 3d textures
// and maybe no float textures
var volCopyData = function volCopyData(outArray, outIdx, inValue, smin, smax) {
outArray[outIdx] = inValue;
};
var dataTypeToUse = VtkDataTypes.UNSIGNED_CHAR; // unsigned char gets used as is
if (dataType === VtkDataTypes.UNSIGNED_CHAR) {
for (var _c4 = 0; _c4 < numComps; ++_c4) {
scaleOffsetsCopy.offset[_c4] = 0.0;
scaleOffsetsCopy.scale[_c4] = 255.0;
}
} else if (model.context.getExtension('OES_texture_float') && model.context.getExtension('OES_texture_float_linear')) {
// use float textures scaled to 0.0 to 1.0
dataTypeToUse = VtkDataTypes.FLOAT;
volCopyData = function volCopyData(outArray, outIdx, inValue, soffset, sscale) {
outArray[outIdx] = (inValue - soffset) / sscale;
};
} else {
// worst case, scale data to uchar
dataTypeToUse = VtkDataTypes.UNSIGNED_CHAR;
volCopyData = function volCopyData(outArray, outIdx, inValue, soffset, sscale) {
outArray[outIdx] = 255.0 * (inValue - soffset) / sscale;
};
} // Now determine the texture parameters using the arguments.
publicAPI.getOpenGLDataType(dataTypeToUse);
publicAPI.getInternalFormat(dataTypeToUse, numComps);
publicAPI.getFormat(dataTypeToUse, numComps);
if (!model.internalFormat || !model.format || !model.openGLDataType) {
vtkErrorMacro('Failed to determine texture parameters.');
return false;
} // have to pack this 3D texture into pot 2D texture
model.target = model.context.TEXTURE_2D;
model.components = numComps;
model.depth = 1;
model.numberOfDimensions = 2; // MAX_TEXTURE_SIZE gives the max dimensions that can be supported by the GPU,
// but it doesn't mean it will fit in memory. If we have to use a float data type
// or 4 components, there are good chances that the texture size will blow up
// and could not fit in the GPU memory. Use a smaller texture size in that case,
// which will force a downsampling of the dataset.
// That problem does not occur when using webGL2 since we can pack the data in
// denser textures based on our data type.
// TODO: try to fit in the biggest supported texture, catch the gl error if it
// does not fix (OUT_OF_MEMORY), then attempt with smaller texture
var maxTexDim = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
if (maxTexDim > 4096 && (dataTypeToUse === VtkDataTypes.FLOAT || numComps >= 3)) {
maxTexDim = 4096;
} // compute estimate for XY subsample
var xstride = 1;
var ystride = 1;
if (numPixelsIn > maxTexDim * maxTexDim) {
xstride = Math.ceil(Math.sqrt(numPixelsIn / (maxTexDim * maxTexDim)));
ystride = xstride;
}
var targetWidth = Math.sqrt(numPixelsIn) / xstride;
targetWidth = nearestPowerOfTwo(targetWidth); // determine X reps
var xreps = Math.floor(targetWidth * xstride / width);
var yreps = Math.ceil(depth / xreps);
var targetHeight = nearestPowerOfTwo(height * yreps / ystride);
model.width = targetWidth;
model.height = targetHeight;
model._openGLRenderWindow.activateTexture(publicAPI);
publicAPI.createTexture();
publicAPI.bind(); // store the information, we will need it later
model.volumeInfo.xreps = xreps;
model.volumeInfo.yreps = yreps;
model.volumeInfo.xstride = xstride;
model.volumeInfo.ystride = ystride;
model.volumeInfo.offset = scaleOffsetsCopy.offset;
model.volumeInfo.scale = scaleOffsetsCopy.scale; // OK stuff the data into the 2d TEXTURE
// first allocate the new texture
var newArray;
var pixCount = targetWidth * targetHeight * numComps;
if (dataTypeToUse === VtkDataTypes.FLOAT) {
newArray = new Float32Array(pixCount);
} else {
newArray = new Uint8Array(pixCount);
} // then stuff the data into it, nothing fancy right now
// for stride
var outIdx = 0;
var tileWidth = Math.floor(width / xstride);
var tileHeight = Math.floor(height / ystride);
for (var yRep = 0; yRep < yreps; yRep++) {
var xrepsThisRow = Math.min(xreps, depth - yRep * xreps);
var outXContI