@acransac/vtk.js
Version:
Visualization Toolkit for the Web
465 lines (407 loc) • 13.8 kB
JavaScript
import { mat4, quat, vec3 } from 'gl-matrix';
import macro from 'vtk.js/Sources/macro';
import vtkBufferObject from 'vtk.js/Sources/Rendering/OpenGL/BufferObject';
import { ObjectType } from 'vtk.js/Sources/Rendering/OpenGL/BufferObject/Constants';
import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants';
const { vtkErrorMacro } = macro;
// ----------------------------------------------------------------------------
// Static functions
// ----------------------------------------------------------------------------
function computeInverseShiftAndScaleMatrix(coordShift, coordScale) {
const inverseScale = vec3.create();
vec3.inverse(inverseScale, coordScale);
const matrix = mat4.create();
mat4.fromRotationTranslationScale(
matrix,
quat.create(),
coordShift,
inverseScale
);
return matrix;
}
function shouldApplyCoordShiftAndScale(coordShift, coordScale) {
if (coordShift === null || coordScale === null) {
return false;
}
return !(
vec3.exactEquals(coordShift, [0, 0, 0]) &&
vec3.exactEquals(coordScale, [1, 1, 1])
);
}
// ----------------------------------------------------------------------------
// vtkOpenGLCellArrayBufferObject methods
// ----------------------------------------------------------------------------
function vtkOpenGLCellArrayBufferObject(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkOpenGLCellArrayBufferObject');
publicAPI.setType(ObjectType.ARRAY_BUFFER);
publicAPI.createVBO = (cellArray, inRep, outRep, options) => {
if (!cellArray.getData() || !cellArray.getData().length) {
model.elementCount = 0;
return 0;
}
// Figure out how big each block will be, currently 6 or 7 floats.
model.blockSize = 3;
model.vertexOffset = 0;
model.normalOffset = 0;
model.tCoordOffset = 0;
model.tCoordComponents = 0;
model.colorComponents = 0;
model.colorOffset = 0;
model.customData = [];
const pointData = options.points.getData();
let normalData = null;
let tcoordData = null;
let colorData = null;
const colorComponents = options.colors
? options.colors.getNumberOfComponents()
: 0;
const textureComponents = options.tcoords
? options.tcoords.getNumberOfComponents()
: 0;
// the values of 4 below are because floats are 4 bytes
if (options.normals) {
model.normalOffset = 4 * model.blockSize;
model.blockSize += 3;
normalData = options.normals.getData();
}
if (options.customAttributes) {
options.customAttributes.forEach((a) => {
if (a) {
model.customData.push({
data: a.getData(),
offset: 4 * model.blockSize,
components: a.getNumberOfComponents(),
name: a.getName(),
});
model.blockSize += a.getNumberOfComponents();
}
});
}
if (options.tcoords) {
model.tCoordOffset = 4 * model.blockSize;
model.tCoordComponents = textureComponents;
model.blockSize += textureComponents;
tcoordData = options.tcoords.getData();
}
if (options.colors) {
model.colorComponents = options.colors.getNumberOfComponents();
model.colorOffset = 0;
colorData = options.colors.getData();
if (!model.colorBO) {
model.colorBO = vtkBufferObject.newInstance();
}
model.colorBO.setOpenGLRenderWindow(model.openGLRenderWindow);
} else {
model.colorBO = null;
}
model.stride = 4 * model.blockSize;
let pointIdx = 0;
let normalIdx = 0;
let tcoordIdx = 0;
let colorIdx = 0;
let custIdx = 0;
let cellCount = 0;
let addAPoint;
const cellBuilders = {
// easy, every input point becomes an output point
anythingToPoints(numPoints, cellPts, offset) {
for (let i = 0; i < numPoints; ++i) {
addAPoint(cellPts[offset + i]);
}
},
linesToWireframe(numPoints, cellPts, offset) {
// for lines we add a bunch of segments
for (let i = 0; i < numPoints - 1; ++i) {
addAPoint(cellPts[offset + i]);
addAPoint(cellPts[offset + i + 1]);
}
},
polysToWireframe(numPoints, cellPts, offset) {
// for polys we add a bunch of segments and close it
if (numPoints > 2) {
for (let i = 0; i < numPoints; ++i) {
addAPoint(cellPts[offset + i]);
addAPoint(cellPts[offset + ((i + 1) % numPoints)]);
}
}
},
stripsToWireframe(numPoints, cellPts, offset) {
if (numPoints > 2) {
// for strips we add a bunch of segments and close it
for (let i = 0; i < numPoints - 1; ++i) {
addAPoint(cellPts[offset + i]);
addAPoint(cellPts[offset + i + 1]);
}
for (let i = 0; i < numPoints - 2; i++) {
addAPoint(cellPts[offset + i]);
addAPoint(cellPts[offset + i + 2]);
}
}
},
polysToSurface(npts, cellPts, offset) {
for (let i = 0; i < npts - 2; i++) {
addAPoint(cellPts[offset + 0]);
addAPoint(cellPts[offset + i + 1]);
addAPoint(cellPts[offset + i + 2]);
}
},
stripsToSurface(npts, cellPts, offset) {
for (let i = 0; i < npts - 2; i++) {
addAPoint(cellPts[offset + i]);
addAPoint(cellPts[offset + i + 1 + (i % 2)]);
addAPoint(cellPts[offset + i + 1 + ((i + 1) % 2)]);
}
},
};
const cellCounters = {
// easy, every input point becomes an output point
anythingToPoints(numPoints, cellPts) {
return numPoints;
},
linesToWireframe(numPoints, cellPts) {
if (numPoints > 1) {
return (numPoints - 1) * 2;
}
return 0;
},
polysToWireframe(numPoints, cellPts) {
if (numPoints > 2) {
return numPoints * 2;
}
return 0;
},
stripsToWireframe(numPoints, cellPts) {
if (numPoints > 2) {
return numPoints * 4 - 6;
}
return 0;
},
polysToSurface(npts, cellPts) {
if (npts > 2) {
return (npts - 2) * 3;
}
return 0;
},
stripsToSurface(npts, cellPts, offset) {
if (npts > 2) {
return (npts - 2) * 3;
}
return 0;
},
};
let func = null;
let countFunc = null;
if (outRep === Representation.POINTS || inRep === 'verts') {
func = cellBuilders.anythingToPoints;
countFunc = cellCounters.anythingToPoints;
} else if (outRep === Representation.WIREFRAME || inRep === 'lines') {
func = cellBuilders[`${inRep}ToWireframe`];
countFunc = cellCounters[`${inRep}ToWireframe`];
} else {
func = cellBuilders[`${inRep}ToSurface`];
countFunc = cellCounters[`${inRep}ToSurface`];
}
const array = cellArray.getData();
const size = array.length;
let caboCount = 0;
for (let index = 0; index < size; ) {
caboCount += countFunc(array[index], array);
index += array[index] + 1;
}
let packedUCVBO = null;
const packedVBO = new Float32Array(caboCount * model.blockSize);
if (colorData) {
packedUCVBO = new Uint8Array(caboCount * 4);
}
let vboidx = 0;
let ucidx = 0;
// Find out if shift scale should be used
// Compute squares of diagonal size and distance from the origin
let diagSq = 0.0;
let distSq = 0.0;
for (let i = 0; i < 3; ++i) {
const range = options.points.getRange(i);
const delta = range[1] - range[0];
diagSq += delta * delta;
const distShift = 0.5 * (range[1] + range[0]);
distSq += distShift * distShift;
}
const useShiftAndScale =
diagSq > 0 &&
(Math.abs(distSq) / diagSq > 1.0e6 || // If data is far from the origin relative to its size
Math.abs(Math.log10(diagSq)) > 3.0 || // If the size is huge when not far from the origin
(diagSq === 0 && distSq > 1.0e6)); // If data is a point, but far from the origin
if (useShiftAndScale) {
// Compute shift and scale vectors
const coordShift = vec3.create();
const coordScale = vec3.create();
for (let i = 0; i < 3; ++i) {
const range = options.points.getRange(i);
const delta = range[1] - range[0];
coordShift[i] = 0.5 * (range[1] + range[0]);
coordScale[i] = delta > 0 ? 1.0 / delta : 1.0;
}
publicAPI.setCoordShiftAndScale(coordShift, coordScale);
} else if (model.coordShiftAndScaleEnabled === true) {
// Make sure to reset
publicAPI.setCoordShiftAndScale(null, null);
}
addAPoint = function addAPointFunc(i) {
// Vertices
pointIdx = i * 3;
if (!model.coordShiftAndScaleEnabled) {
packedVBO[vboidx++] = pointData[pointIdx++];
packedVBO[vboidx++] = pointData[pointIdx++];
packedVBO[vboidx++] = pointData[pointIdx++];
} else {
// Apply shift and scale
packedVBO[vboidx++] =
(pointData[pointIdx++] - model.coordShift[0]) * model.coordScale[0];
packedVBO[vboidx++] =
(pointData[pointIdx++] - model.coordShift[1]) * model.coordScale[1];
packedVBO[vboidx++] =
(pointData[pointIdx++] - model.coordShift[2]) * model.coordScale[2];
}
if (normalData !== null) {
if (options.haveCellNormals) {
normalIdx = (cellCount + options.cellOffset) * 3;
} else {
normalIdx = i * 3;
}
packedVBO[vboidx++] = normalData[normalIdx++];
packedVBO[vboidx++] = normalData[normalIdx++];
packedVBO[vboidx++] = normalData[normalIdx++];
}
model.customData.forEach((attr) => {
custIdx = i * attr.components;
for (let j = 0; j < attr.components; ++j) {
packedVBO[vboidx++] = attr.data[custIdx++];
}
});
if (tcoordData !== null) {
tcoordIdx = i * textureComponents;
for (let j = 0; j < textureComponents; ++j) {
packedVBO[vboidx++] = tcoordData[tcoordIdx++];
}
}
if (colorData !== null) {
if (options.haveCellScalars) {
colorIdx = (cellCount + options.cellOffset) * colorComponents;
} else {
colorIdx = i * colorComponents;
}
packedUCVBO[ucidx++] = colorData[colorIdx++];
packedUCVBO[ucidx++] = colorData[colorIdx++];
packedUCVBO[ucidx++] = colorData[colorIdx++];
packedUCVBO[ucidx++] =
colorComponents === 4 ? colorData[colorIdx++] : 255;
}
};
for (let index = 0; index < size; ) {
func(array[index], array, index + 1);
index += array[index] + 1;
cellCount++;
}
model.elementCount = caboCount;
publicAPI.upload(packedVBO, ObjectType.ARRAY_BUFFER);
if (model.colorBO) {
model.colorBOStride = 4;
model.colorBO.upload(packedUCVBO, ObjectType.ARRAY_BUFFER);
}
return cellCount;
};
publicAPI.setCoordShiftAndScale = (coordShift, coordScale) => {
if (
coordShift !== null &&
(coordShift.constructor !== Float32Array || coordShift.length !== 3)
) {
vtkErrorMacro('Wrong type for coordShift, expected vec3 or null');
return;
}
if (
coordScale !== null &&
(coordScale.constructor !== Float32Array || coordScale.length !== 3)
) {
vtkErrorMacro('Wrong type for coordScale, expected vec3 or null');
return;
}
if (
model.coordShift === null ||
coordShift === null ||
!vec3.equals(coordShift, model.coordShift)
) {
model.coordShift = coordShift;
}
if (
model.coordScale === null ||
coordScale === null ||
!vec3.equals(coordScale, model.coordScale)
) {
model.coordScale = coordScale;
}
model.coordShiftAndScaleEnabled = shouldApplyCoordShiftAndScale(
model.coordShift,
model.coordScale
);
if (model.coordShiftAndScaleEnabled) {
model.inverseShiftAndScaleMatrix = computeInverseShiftAndScaleMatrix(
model.coordShift,
model.coordScale
);
} else {
model.inverseShiftAndScaleMatrix = null;
}
};
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
const DEFAULT_VALUES = {
elementCount: 0,
stride: 0,
colorBOStride: 0,
vertexOffset: 0,
normalOffset: 0,
tCoordOffset: 0,
tCoordComponents: 0,
colorOffset: 0,
colorComponents: 0,
tcoordBO: null,
customData: [],
coordShift: null,
coordScale: null,
coordShiftAndScaleEnabled: false,
inverseShiftAndScaleMatrix: null,
};
// ----------------------------------------------------------------------------
export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);
// Inheritance
vtkBufferObject.extend(publicAPI, model, initialValues);
macro.setGet(publicAPI, model, [
'colorBO',
'elementCount',
'stride',
'colorBOStride',
'vertexOffset',
'normalOffset',
'tCoordOffset',
'tCoordComponents',
'colorOffset',
'colorComponents',
'customData',
]);
macro.get(publicAPI, model, [
'coordShift',
'coordScale',
'coordShiftAndScaleEnabled',
'inverseShiftAndScaleMatrix',
]);
// Object specific methods
vtkOpenGLCellArrayBufferObject(publicAPI, model);
}
// ----------------------------------------------------------------------------
export const newInstance = macro.newInstance(extend);
// ----------------------------------------------------------------------------
export default { newInstance, extend };