Mapper

Introduction

vtkMapper is an abstract class to specify interface between data and
graphics primitives. Subclasses of vtkMapper map data through a
lookuptable and control the creation of rendering primitives that
interface to the graphics library. The mapping can be controlled by
supplying a lookup table and specifying a scalar range to map data
through.

There are several important control mechanisms affecting the behavior of
this object. The ScalarVisibility flag controls whether scalar data (if
any) controls the color of the associated actor(s) that refer to the
mapper. The ScalarMode ivar is used to determine whether scalar point data
or cell data is used to color the object. By default, point data scalars
are used unless there are none, then cell scalars are used. Or you can
explicitly control whether to use point or cell scalar data. Finally, the
mapping of scalars through the lookup table varies depending on the
setting of the ColorMode flag. See the documentation for the appropriate
methods for an explanation.

Another important feature of this class is whether to use immediate mode
rendering (ImmediateModeRenderingOn) or display list rendering
(ImmediateModeRenderingOff). If display lists are used, a data structure
is constructed (generally in the rendering library) which can then be
rapidly traversed and rendered by the rendering library. The disadvantage
of display lists is that they require additional memory which may affect
the performance of the system.

Another important feature of the mapper is the ability to shift the
Z-buffer to resolve coincident topology. For example, if you’d like to
draw a mesh with some edges a different color, and the edges lie on the
mesh, this feature can be useful to get nice looking lines. (See the
ResolveCoincidentTopology-related methods.)

lookupTable

Set/Get a lookup table for the mapper to use.

createDefaultLookupTable()

Create default lookup table. Generally used to create one when none
is available with the scalar data.

scalarVisibility (get/set)

Turn on/off flag to control whether scalar data is used to color objects.

static (get/set)

Turn on/off flag to control whether the mapper’s data is static. Static data
means that the mapper does not propagate updates down the pipeline, greatly
decreasing the time it takes to update many mappers. This should only be
used if the data never changes.

colorMode

Description: Control how the scalar data is mapped to colors. By
default (ColorModeToDefault), unsigned char scalars are treated
as colors, and NOT mapped through the lookup table, while
everything else is. ColorModeToDirectScalar extends
ColorModeToDefault such that all integer types are treated as
colors with values in the range 0-255 and floating types are
treated as colors with values in the range 0.0-1.0. Setting
ColorModeToMapScalars means that all scalar data will be mapped
through the lookup table. (Note that for multi-component
scalars, the particular component to use for mapping can be
specified using the SelectColorArray() method.)


// Helper methods
instance.setColorModeToDefault();
instance.setColorModeToMapScalars();
instance.setColorModeToDirectScalars();

getColorModeAsString()

Return the method of coloring scalar data.

interpolateScalarsBeforeMapping

By default, vertex color is used to map colors to a surface.
Colors are interpolated after being mapped.
This option avoids color interpolation by using a one dimensional
texture map for the colors.

useLookupTableScalarRange

Control whether the mapper sets the lookuptable range based on its
own ScalarRange, or whether it will use the LookupTable ScalarRange
regardless of it’s own setting. By default the Mapper is allowed to set
the LookupTable range, but users who are sharing LookupTables between
mappers/actors will probably wish to force the mapper to use the
LookupTable unchanged.

scalarRange

Specify range in terms of scalar minimum and maximum (smin,smax). These
values are used to map scalars into lookup table. Has no effect when
UseLookupTableScalarRange is true.

scalarMode

Control how the filter works with scalar point data and cell attribute
data. By default (ScalarModeToDefault), the filter will use point data,
and if no point data is available, then cell data is used. Alternatively
you can explicitly set the filter to use point data
(ScalarModeToUsePointData) or cell data (ScalarModeToUseCellData).
You can also choose to get the scalars from an array in point field
data (ScalarModeToUsePointFieldData) or cell field data
(ScalarModeToUseCellFieldData). If scalars are coming from a field
data array, you must call SelectColorArray before you call
GetColors.

When ScalarMode is set to use Field Data (ScalarModeToFieldData),
you must call SelectColorArray to choose the field data array to
be used to color cells. In this mode, the default behavior is to
treat the field data tuples as being associated with cells. If
the poly data contains triangle strips, the array is expected to
contain the cell data for each mini-cell formed by any triangle
strips in the poly data as opposed to treating them as a single
tuple that applies to the entire strip. This mode can also be
used to color the entire poly data by a single color obtained by
mapping the tuple at a given index in the field data array
through the color map. Use SetFieldDataTupleId() to specify
the tuple index.

instance.setScalarModeToDefault();
instance.setScalarModeToUsePointData();
instance.setScalarModeToUseCellData();
instance.setScalarModeToUsePointFieldData();
instance.setScalarModeToUseCellFieldData();
instance.setScalarModeToUseFieldData();

getScalarModeAsString()

Return the method for obtaining scalar data.

selectColorArray(arrayName, component = -1)

When ScalarMode is set to UsePointFieldData or UseCellFieldData,
you can specify which array to use for coloring using these methods.
The lookup table will decide how to convert vectors to colors.

fieldDataTupleId

When ScalarMode is set to UseFieldData, set the index of the
tuple by which to color the entire data set. By default, the
index is -1, which means to treat the field data array selected
with SelectColorArray as having a scalar value for each cell.
Indices of 0 or higher mean to use the tuple at the given index
for coloring the entire data set.

colorByArrayName

Set/Get the array name to color by.

resolveCoincidentTopology (STATIC)

Set/Get a global flag that controls whether coincident topology (e.g., a
line on top of a polygon) is shifted to avoid Z-buffer resolution (and
hence rendering problems). If not off, there are two methods to choose
from. PolygonOffset uses graphics systems calls to shift polygons, but
does not distinguish vertices and lines from one another. ShiftZBuffer
remaps the Z-buffer to distinguish vertices, lines, and polygons, but
does not always produce acceptable results. If you use the ShiftZBuffer
approach, you may also want to set the ResolveCoincidentTopologyZShift
value. (Note: not all mappers/graphics systems implement this
functionality.)

Mapper.setResolveCoincidentTopology(val);
Mapper.setResolveCoincidentTopologyToDefault();
Mapper.setResolveCoincidentTopologyToOff()
Mapper.setResolveCoincidentTopologyToPolygonOffset()

console.log(Mapper.getResolveCoincidentTopology());
console.log(Mapper.getResolveCoincidentTopologyAsString());

resolveCoincidentTopologyPolygonOffsetParameters (STATIC)

Used to set the polygon offset scale factor and units.
Used when ResolveCoincidentTopology is set to PolygonOffset.
These are global variables.

Mapper.setResolveCoincidentTopologyPolygonOffsetParameters(factor, units);
console.log(Mapper.getResolveCoincidentTopologyPolygonOffsetParameters());

relativeCoincidentTopologyPolygonOffsetParameters

Used to set the polygon offset values relative to the global
Used when ResolveCoincidentTopology is set to PolygonOffset.

instance.setRelativeCoincidentTopologyPolygonOffsetParameters(factor, unit)
instance.getRelativeCoincidentTopologyPolygonOffsetParameters() => { factor, unit }

resolveCoincidentTopologyLineOffsetParameters (STATIC)

Used to set the line offset scale factor and units.
Used when ResolveCoincidentTopology is set to PolygonOffset.
These are global variables.

Mapper.setResolveCoincidentTopologyLineOffsetParameters(factor, unit)
Mapper.getResolveCoincidentTopologyLineOffsetParameters() => { factor, unit }

relativeCoincidentTopologyLineOffsetParameters

Used to set the line offset values relative to the global
Used when ResolveCoincidentTopology is set to PolygonOffset.

instance.setRelativeCoincidentTopologyLineOffsetParameters(factor, unit)
instance.getRelativeCoincidentTopologyLineOffsetParameters() => { factor, unit

resolveCoincidentTopologyPointOffsetParameter (STATIC)

Used to set the point offset value
Used when ResolveCoincidentTopology is set to PolygonOffset.
These are global variables.

Mapper.setResolveCoincidentTopologyPointOffsetParameter(factor, unit)
Mapper.getResolveCoincidentTopologyPointOffsetParameter() => { factor, unit }

relativeCoincidentTopologyPointOffsetParameter

Used to set the point offset value relative to the global
Used when ResolveCoincidentTopology is set to PolygonOffset.
(factor is ignored but kept for concistency)

instance.setRelativeCoincidentTopologyPointOffsetParameters(factor, unit)
instance.getRelativeCoincidentTopologyPointOffsetParameters() => { factor, unit }

getCoincidentTopologyPolygonOffsetParameters() => { factor, units }

Get the net paramters for handlig coincident topology obtained by summing
the global values with the relative values.

getCoincidentTopologyLineOffsetParameters() => { factor, units }

Get the net paramters for handlig coincident topology obtained by summing
the global values with the relative values.

getCoincidentTopologyPointOffsetParameter() => { factor, units }

Get the net paramters for handlig coincident topology obtained by summing
the global values with the relative values.

resolveCoincidentTopologyPolygonOffsetFaces

Used when ResolveCoincidentTopology is set to PolygonOffset. The polygon
offset can be applied either to the solid polygonal faces or the
lines/vertices. When set (default), the offset is applied to the faces
otherwise it is applied to lines and vertices.
This is a global variable.

getBounds() : Array(6)

Return bounding box (array of six doubles) of data expressed as
(xmin,xmax, ymin,ymax, zmin,zmax).

renderTime (set/get)

This instance variable is used by vtkLODActor to determine which
mapper to use. It is an estimate of the time necessary to render.
Setting the render time does not modify the mapper.

mapScalars(input, alpha) => { rgba, cellFlag }

Map the scalars (if there are any scalars and ScalarVisibility is on)
through the lookup table, returning an unsigned char RGBA array. This is
typically done as part of the rendering process. The alpha parameter
allows the blending of the scalars with an additional alpha (typically
which comes from a vtkActor, etc.)

{
rgba: Uint8Array(),
location: 0/1/2, // Points/Cells/Fields
}

getIsOpaque() : Boolean

Returns if the mapper does not expect to have translucent geometry. This
may happen when using ColorMode is set to not map scalars i.e. render the
scalar array directly as colors and the scalar array has opacity i.e. alpha
component. Default implementation simply returns true. Note that even if
this method returns true, an actor may treat the geometry as translucent
since a constant translucency is set on the property, for example.

canUseTextureMapForColoring(input)

Returns if we can use texture maps for scalar coloring. Note this doesn’t
say we “will” use scalar coloring. It says, if we do use scalar coloring,
we will use a texture.
When rendering multiblock datasets, if any 2 blocks provide different
lookup tables for the scalars, then also we cannot use textures. This case
can be handled if required.

clearColorArrays()

Call to force a rebuild of color result arrays on next MapScalars.
Necessary when using arrays in the case of multiblock data.

getColorMapColors() : Uint8Array()

Provide read access to the color array.

getColorCoordinates() : Float32Array()

Provide read access to the color texture coordinate array

getColorTextureMap() : ?? ImageData ??

Provide read access to the color texture array

viewSpecificProperties

If you want to provide specific properties for rendering engines you can use
viewSpecificProperties.

You can go and have a look in the rendering backend of your choice for details
on specific properties.
For example, for OpenGL/WebGL see OpenGL/PolyDataMapper/api.md
If there is no details, viewSpecificProperties is not supported.

Source

CoincidentTopologyHelper.js
export function addCoincidentTopologyMethods(publicAPI, model, nameList) {
nameList.forEach((item) => {
publicAPI[`get${item.method}`] = () => model[item.key];
publicAPI[`set${item.method}`] = (factor, unit) => {
model[item.key] = { factor, unit };
};
});
}

export const CATEGORIES = ['Polygon', 'Line', 'Point'];

export default {
addCoincidentTopologyMethods,
CATEGORIES,
};
Constants.js
export const ColorMode = {
DEFAULT: 0,
MAP_SCALARS: 1,
DIRECT_SCALARS: 2,
};

export const ScalarMode = {
DEFAULT: 0,
USE_POINT_DATA: 1,
USE_CELL_DATA: 2,
USE_POINT_FIELD_DATA: 3,
USE_CELL_FIELD_DATA: 4,
USE_FIELD_DATA: 5,
};

export const GetArray = {
BY_ID: 0,
BY_NAME: 1,
};

export default {
ColorMode,
GetArray,
ScalarMode,
};
Static.js
let resolveCoincidentTopologyPolygonOffsetFaces = 1;
let resolveCoincidentTopology = 0;

export const RESOLVE_COINCIDENT_TOPOLOGY_MODE = [
'VTK_RESOLVE_OFF',
'VTK_RESOLVE_POLYGON_OFFSET',
];

export function getResolveCoincidentTopologyPolygonOffsetFaces() {
return resolveCoincidentTopologyPolygonOffsetFaces;
}

export function setResolveCoincidentTopologyPolygonOffsetFaces(value) {
resolveCoincidentTopologyPolygonOffsetFaces = value;
}

export function getResolveCoincidentTopology() {
return resolveCoincidentTopology;
}

export function setResolveCoincidentTopology(mode = 0) {
resolveCoincidentTopology = mode;
}

export function setResolveCoincidentTopologyToDefault() {
setResolveCoincidentTopology(0); // VTK_RESOLVE_OFF
}

export function setResolveCoincidentTopologyToOff() {
setResolveCoincidentTopology(0); // VTK_RESOLVE_OFF
}

export function setResolveCoincidentTopologyToPolygonOffset() {
setResolveCoincidentTopology(1); // VTK_RESOLVE_POLYGON_OFFSET
}

export function getResolveCoincidentTopologyAsString() {
return RESOLVE_COINCIDENT_TOPOLOGY_MODE[resolveCoincidentTopology];
}

export default {
getResolveCoincidentTopologyAsString,
getResolveCoincidentTopologyPolygonOffsetFaces,
getResolveCoincidentTopology,
setResolveCoincidentTopology,
setResolveCoincidentTopologyPolygonOffsetFaces,
setResolveCoincidentTopologyToDefault,
setResolveCoincidentTopologyToOff,
setResolveCoincidentTopologyToPolygonOffset,
};
index.js
import macro from 'vtk.js/Sources/macro';
import vtkAbstractMapper3D from 'vtk.js/Sources/Rendering/Core/AbstractMapper3D';
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
import vtkImageData from 'vtk.js/Sources/Common/DataModel/ImageData';
import vtkLookupTable from 'vtk.js/Sources/Common/Core/LookupTable';
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
import vtkScalarsToColors from 'vtk.js/Sources/Common/Core/ScalarsToColors/Constants'; // Need to go inside Constants otherwise dependency loop

import CoincidentTopologyHelper from 'vtk.js/Sources/Rendering/Core/Mapper/CoincidentTopologyHelper';
import otherStaticMethods from 'vtk.js/Sources/Rendering/Core/Mapper/Static';
import Constants from 'vtk.js/Sources/Rendering/Core/Mapper/Constants';

const { ColorMode, ScalarMode, GetArray } = Constants;
const { VectorMode } = vtkScalarsToColors;
const { VtkDataTypes } = vtkDataArray;

// ----------------------------------------------------------------------------

function notImplemented(method) {
return () => macro.vtkErrorMacro(`vtkMapper::${method} - NOT IMPLEMENTED`);
}

// CoincidentTopology static methods ------------------------------------------
/* eslint-disable arrow-body-style */

const staticOffsetModel = {
Polygon: { factor: 2, offset: 0 },
Line: { factor: 1, offset: -1 },
Point: { factor: 0, offset: -2 },
};
const staticOffsetAPI = {};

CoincidentTopologyHelper.addCoincidentTopologyMethods(
staticOffsetAPI,
staticOffsetModel,
CoincidentTopologyHelper.CATEGORIES.map((key) => ({
key,
method: `ResolveCoincidentTopology${key}OffsetParameters`,
}))
);

// ----------------------------------------------------------------------------
// vtkMapper methods
// ----------------------------------------------------------------------------

function vtkMapper(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkMapper');

publicAPI.getBounds = () => {
const input = publicAPI.getInputData();
if (!input) {
model.bounds = vtkMath.createUninitializedBounds();
} else {
if (!model.static) {
publicAPI.update();
}
model.bounds = input.getBounds();
}
return model.bounds;
};

publicAPI.setForceCompileOnly = (v) => {
model.forceCompileOnly = v;
// make sure we do NOT call modified()
};

publicAPI.createDefaultLookupTable = () => {
model.lookupTable = vtkLookupTable.newInstance();
};

publicAPI.getColorModeAsString = () =>
macro.enumToString(ColorMode, model.colorMode);

publicAPI.setColorModeToDefault = () => publicAPI.setColorMode(0);
publicAPI.setColorModeToMapScalars = () => publicAPI.setColorMode(1);
publicAPI.setColorModeToDirectScalars = () => publicAPI.setColorMode(2);

publicAPI.getScalarModeAsString = () =>
macro.enumToString(ScalarMode, model.scalarMode);

publicAPI.setScalarModeToDefault = () => publicAPI.setScalarMode(0);
publicAPI.setScalarModeToUsePointData = () => publicAPI.setScalarMode(1);
publicAPI.setScalarModeToUseCellData = () => publicAPI.setScalarMode(2);
publicAPI.setScalarModeToUsePointFieldData = () => publicAPI.setScalarMode(3);
publicAPI.setScalarModeToUseCellFieldData = () => publicAPI.setScalarMode(4);
publicAPI.setScalarModeToUseFieldData = () => publicAPI.setScalarMode(5);

// Add Static methods to our instance
Object.keys(otherStaticMethods).forEach((methodName) => {
publicAPI[methodName] = otherStaticMethods[methodName];
});
Object.keys(staticOffsetAPI).forEach((methodName) => {
publicAPI[methodName] = staticOffsetAPI[methodName];
});

// Relative metods
/* eslint-disable arrow-body-style */
model.topologyOffset = {
Polygon: { factor: 0, offset: 0 },
Line: { factor: 0, offset: 0 },
Point: { factor: 0, offset: 0 },
};
CoincidentTopologyHelper.addCoincidentTopologyMethods(
publicAPI,
model.topologyOffset,
CoincidentTopologyHelper.CATEGORIES.map((key) => ({
key,
method: `RelativeCoincidentTopology${key}OffsetParameters`,
}))
);
/* eslint-enable arrow-body-style */

publicAPI.getCoincidentTopologyPolygonOffsetParameters = () => {
const globalValue = staticOffsetAPI.getResolveCoincidentTopologyPolygonOffsetParameters();
const localValue = publicAPI.getRelativeCoincidentTopologyPolygonOffsetParameters();
return {
factor: globalValue.factor + localValue.factor,
offset: globalValue.offset + localValue.offset,
};
};

publicAPI.getCoincidentTopologyLineOffsetParameters = () => {
const globalValue = staticOffsetAPI.getResolveCoincidentTopologyLineOffsetParameters();
const localValue = publicAPI.getRelativeCoincidentTopologyLineOffsetParameters();
return {
factor: globalValue.factor + localValue.factor,
offset: globalValue.offset + localValue.offset,
};
};

publicAPI.getCoincidentTopologyPointOffsetParameter = () => {
const globalValue = staticOffsetAPI.getResolveCoincidentTopologyPointOffsetParameters();
const localValue = publicAPI.getRelativeCoincidentTopologyPointOffsetParameters();
return {
factor: globalValue.factor + localValue.factor,
offset: globalValue.offset + localValue.offset,
};
};

publicAPI.getAbstractScalars = (
input,
scalarMode,
arrayAccessMode,
arrayId,
arrayName
) => {
// make sure we have an input
if (!input || !model.scalarVisibility) {
return { scalars: null, cellFLag: false };
}

let scalars = null;
let cellFlag = false;

// get and scalar data according to scalar mode
if (scalarMode === ScalarMode.DEFAULT) {
scalars = input.getPointData().getScalars();
if (!scalars) {
scalars = input.getCellData().getScalars();
cellFlag = true;
}
} else if (scalarMode === ScalarMode.USE_POINT_DATA) {
scalars = input.getPointData().getScalars();
} else if (scalarMode === ScalarMode.USE_CELL_DATA) {
scalars = input.getCellData().getScalars();
cellFlag = true;
} else if (scalarMode === ScalarMode.USE_POINT_FIELD_DATA) {
const pd = input.getPointData();
if (arrayAccessMode === GetArray.BY_ID) {
scalars = pd.getArrayByIndex(arrayId);
} else {
scalars = pd.getArrayByName(arrayName);
}
} else if (scalarMode === ScalarMode.USE_CELL_FIELD_DATA) {
const cd = input.getCellData();
cellFlag = true;
if (arrayAccessMode === GetArray.BY_ID) {
scalars = cd.getArrayByIndex(arrayId);
} else {
scalars = cd.getArrayByName(arrayName);
}
} else if (scalarMode === ScalarMode.USE_FIELD_DATA) {
const fd = input.getFieldData();
if (arrayAccessMode === GetArray.BY_ID) {
scalars = fd.getArrayByIndex(arrayId);
} else {
scalars = fd.getArrayByName(arrayName);
}
}

return { scalars, cellFlag };
};

publicAPI.mapScalars = (input, alpha) => {
const scalars = publicAPI.getAbstractScalars(
input,
model.scalarMode,
model.arrayAccessMode,
model.arrayId,
model.colorByArrayName
).scalars;

if (!scalars) {
model.colorCoordinates = null;
model.colorTextureMap = null;
model.colorMapColors = null;
return;
}

if (!model.useLookupTableScalarRange) {
publicAPI
.getLookupTable()
.setRange(model.scalarRange[0], model.scalarRange[1]);
}

// Decide betweeen texture color or vertex color.
// Cell data always uses vertex color.
// Only point data can use both texture and vertex coloring.
if (publicAPI.canUseTextureMapForColoring(input)) {
publicAPI.mapScalarsToTexture(scalars, alpha);
return;
}

model.colorCoordinates = null;
model.colorTextureMap = null;

const lut = publicAPI.getLookupTable();
if (lut) {
// Ensure that the lookup table is built
lut.build();
model.colorMapColors = lut.mapScalars(scalars, model.colorMode, -1);
}
};

//-----------------------------------------------------------------------------
publicAPI.scalarToTextureCoordinate = (
scalarValue, // Input scalar
rangeMin, // range[0]
invRangeWidth
) => {
// 1/(range[1]-range[0])
let texCoordS = 0.5; // Scalar value is arbitrary when NaN
let texCoordT = 1.0; // 1.0 in t coordinate means NaN
if (!vtkMath.isNan(scalarValue)) {
// 0.0 in t coordinate means not NaN. So why am I setting it to 0.49?
// Because when you are mapping scalars and you have a NaN adjacent to
// anything else, the interpolation everywhere should be NaN. Thus, I
// want the NaN color everywhere except right on the non-NaN neighbors.
// To simulate this, I set the t coord for the real numbers close to
// the threshold so that the interpolation almost immediately looks up
// the NaN value.
texCoordT = 0.49;

texCoordS = (scalarValue - rangeMin) * invRangeWidth;

// Some implementations apparently don't handle relatively large
// numbers (compared to the range [0.0, 1.0]) very well. In fact,
// values above 1122.0f appear to cause texture wrap-around on
// some systems even when edge clamping is enabled. Why 1122.0f? I
// don't know. For safety, we'll clamp at +/- 1000. This will
// result in incorrect images when the texture value should be
// above or below 1000, but I don't have a better solution.
if (texCoordS > 1000.0) {
texCoordS = 1000.0;
} else if (texCoordS < -1000.0) {
texCoordS = -1000.0;
}
}
return { texCoordS, texCoordT };
};

//-----------------------------------------------------------------------------
publicAPI.createColorTextureCoordinates = (
input,
output,
numScalars,
numComps,
component,
range,
tableRange,
tableNumberOfColors,
useLogScale
) => {
// We have to change the range used for computing texture
// coordinates slightly to accomodate the special above- and
// below-range colors that are the first and last texels,
// respectively.
const scalarTexelWidth = (range[1] - range[0]) / tableNumberOfColors;

const paddedRange = [];
paddedRange[0] = range[0] - scalarTexelWidth;
paddedRange[1] = range[1] + scalarTexelWidth;
const invRangeWidth = 1.0 / (paddedRange[1] - paddedRange[0]);

const outputV = output.getData();
const inputV = input.getData();

let count = 0;
let outputCount = 0;
if (component < 0 || component >= numComps) {
for (let scalarIdx = 0; scalarIdx < numScalars; ++scalarIdx) {
let sum = 0;
for (let compIdx = 0; compIdx < numComps; ++compIdx) {
sum += inputV[count] * inputV[count];
count++;
}
let magnitude = Math.sqrt(sum);
if (useLogScale) {
magnitude = vtkLookupTable.applyLogScale(
magnitude,
tableRange,
range
);
}
const outputs = publicAPI.scalarToTextureCoordinate(
magnitude,
paddedRange[0],
invRangeWidth
);
outputV[outputCount] = outputs.texCoordS;
outputV[outputCount + 1] = outputs.texCoordT;
outputCount += 2;
}
} else {
count += component;
for (let scalarIdx = 0; scalarIdx < numScalars; ++scalarIdx) {
let inputValue = inputV[count];
if (useLogScale) {
inputValue = vtkLookupTable.applyLogScale(
inputValue,
tableRange,
range
);
}
const outputs = publicAPI.scalarToTextureCoordinate(
inputValue,
paddedRange[0],
invRangeWidth
);
outputV[outputCount] = outputs.texCoordS;
outputV[outputCount + 1] = outputs.texCoordT;
outputCount += 2;
count += numComps;
}
}
};

publicAPI.mapScalarsToTexture = (scalars, alpha) => {
const range = model.lookupTable.getRange();
const useLogScale = model.lookupTable.usingLogScale();
if (useLogScale) {
// convert range to log.
vtkLookupTable.getLogRange(range, range);
}

const origAlpha = model.lookupTable.getAlpha();

// Get rid of vertex color array. Only texture or vertex coloring
// can be active at one time. The existence of the array is the
// signal to use that technique.
model.colorMapColors = null;

// If the lookup table has changed, then recreate the color texture map.
// Set a new lookup table changes this->MTime.
if (
model.colorTextureMap == null ||
publicAPI.getMTime() > model.colorTextureMap.getMTime() ||
model.lookupTable.getMTime() > model.colorTextureMap.getMTime() ||
model.lookupTable.getAlpha() !== alpha
) {
model.lookupTable.setAlpha(alpha);
model.colorTextureMap = null;

// Get the texture map from the lookup table.
// Create a dummy ramp of scalars.
// In the future, we could extend vtkScalarsToColors.
model.lookupTable.build();
let numberOfColors = model.lookupTable.getNumberOfAvailableColors();
if (numberOfColors > 4094) {
numberOfColors = 4094;
}
numberOfColors += 2;
const k = (range[1] - range[0]) / (numberOfColors - 1 - 2);

const newArray = new Float64Array(numberOfColors * 2);

for (let i = 0; i < numberOfColors; ++i) {
newArray[i] = range[0] + i * k - k; // minus k to start at below range color
if (useLogScale) {
newArray[i] = 10.0 ** newArray[i];
}
}
// Dimension on NaN.
for (let i = 0; i < numberOfColors; ++i) {
newArray[i + numberOfColors] = NaN;
}

model.colorTextureMap = vtkImageData.newInstance();
model.colorTextureMap.setExtent(0, numberOfColors - 1, 0, 1, 0, 0);

const tmp = vtkDataArray.newInstance({
numberOfComponents: 1,
values: newArray,
});

model.colorTextureMap
.getPointData()
.setScalars(model.lookupTable.mapScalars(tmp, model.colorMode, 0));
model.lookupTable.setAlpha(origAlpha);
}

// Create new coordinates if necessary.
// Need to compare lookup table incase the range has changed.
if (
!model.colorCoordinates ||
publicAPI.getMTime() > model.colorCoordinates.getMTime() ||
publicAPI.getInputData(0).getMTime() >
model.colorCoordinates.getMTime() ||
model.lookupTable.getMTime() > model.colorCoordinates.getMTime()
) {
// Get rid of old colors
model.colorCoordinates = null;

// Now create the color texture coordinates.
const numComps = scalars.getNumberOfComponents();
const num = scalars.getNumberOfTuples();

// const fArray = new FloatArray(num * 2);
model.colorCoordinates = vtkDataArray.newInstance({
numberOfComponents: 2,
values: new Float32Array(num * 2),
});

let scalarComponent = model.lookupTable.getVectorComponent();
// Although I like the feature of applying magnitude to single component
// scalars, it is not how the old MapScalars for vertex coloring works.
if (
model.lookupTable.getVectorMode() === VectorMode.MAGNITUDE &&
scalars.getNumberOfComponents() > 1
) {
scalarComponent = -1;
}

publicAPI.createColorTextureCoordinates(
scalars,
model.colorCoordinates,
num,
numComps,
scalarComponent,
range,
model.lookupTable.getRange(),
model.colorTextureMap
.getPointData()
.getScalars()
.getNumberOfTuples() /
2 -
2,
useLogScale
);
}
};

publicAPI.getIsOpaque = () => {
const lut = publicAPI.getLookupTable();
if (lut) {
// Ensure that the lookup table is built
lut.build();
return lut.isOpaque();
}
return true;
};

publicAPI.canUseTextureMapForColoring = (input) => {
if (!model.interpolateScalarsBeforeMapping) {
return false; // user doesn't want us to use texture maps at all.
}

// index color does not use textures
if (model.lookupTable && model.lookupTable.getIndexedLookup()) {
return false;
}

const gasResult = publicAPI.getAbstractScalars(
input,
model.scalarMode,
model.arrayAccessMode,
model.arrayId,
model.colorByArrayName
);
const scalars = gasResult.scalars;

if (!scalars) {
// no scalars on this dataset, we don't care if texture is used at all.
return false;
}

if (gasResult.cellFlag) {
return false; // cell data colors, don't use textures.
}

if (
(model.colorMode === ColorMode.DEFAULT &&
scalars.getDataType() === VtkDataTypes.UNSIGNED_CHAR) ||
model.colorMode === ColorMode.DIRECT_SCALARS
) {
// Don't use texture is direct coloring using RGB unsigned chars is
// requested.
return false;
}

return true;
};

publicAPI.clearColorArrays = () => {
model.colorMapColors = null;
model.colorCoordinates = null;
model.colorTextureMap = null;
};

publicAPI.getLookupTable = () => {
if (!model.lookupTable) {
publicAPI.createDefaultLookupTable();
}
return model.lookupTable;
};

publicAPI.getMTime = () => {
let mt = model.mtime;
if (model.lookupTable !== null) {
const time = model.lookupTable.getMTime();
mt = time > mt ? time : mt;
}
return mt;
};

publicAPI.getPrimitiveCount = () => {
const input = publicAPI.getInputData();
const pcount = {
points: input.getPoints().getNumberOfValues() / 3,
verts:
input.getVerts().getNumberOfValues() -
input.getVerts().getNumberOfCells(),
lines:
input.getLines().getNumberOfValues() -
2 * input.getLines().getNumberOfCells(),
triangles:
input.getPolys().getNumberOfValues() -
3 * input.getLines().getNumberOfCells(),
};
return pcount;
};

publicAPI.acquireInvertibleLookupTable = notImplemented(
'AcquireInvertibleLookupTable'
);
publicAPI.valueToColor = notImplemented('ValueToColor');
publicAPI.colorToValue = notImplemented('ColorToValue');
publicAPI.useInvertibleColorFor = notImplemented('UseInvertibleColorFor');
publicAPI.clearInvertibleColor = notImplemented('ClearInvertibleColor');
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
colorMapColors: null, // Same as this->Colors

static: false,
lookupTable: null,

scalarVisibility: true,
scalarRange: [0, 1],
useLookupTableScalarRange: false,

colorMode: 0,
scalarMode: 0,
arrayAccessMode: 1, // By_NAME

renderTime: 0,

colorByArrayName: null,

fieldDataTupleId: -1,

interpolateScalarsBeforeMapping: false,
colorCoordinates: null,
colorTextureMap: null,

forceCompileOnly: 0,

useInvertibleColors: false,
invertibleScalars: null,
resolveCoincidentTopology: false,

viewSpecificProperties: null,

customShaderAttributes: [],
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
Object.assign(model, DEFAULT_VALUES, initialValues);

// Inheritance
vtkAbstractMapper3D.extend(publicAPI, model, initialValues);

macro.get(publicAPI, model, [
'colorCoordinates',
'colorMapColors',
'colorTextureMap',
]);
macro.setGet(publicAPI, model, [
'colorByArrayName',
'arrayAccessMode',
'colorMode',
'fieldDataTupleId',
'interpolateScalarsBeforeMapping',
'lookupTable',
'renderTime',
'resolveCoincidentTopology',
'scalarMode',
'scalarVisibility',
'static',
'useLookupTableScalarRange',
'viewSpecificProperties',
'customShaderAttributes', // point data array names that will be transfered to the VBO
]);
macro.setGetArray(publicAPI, model, ['scalarRange'], 2);

if (!model.viewSpecificProperties) {
model.viewSpecificProperties = {};
}

// Object methods
vtkMapper(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkMapper');

// ----------------------------------------------------------------------------

export default Object.assign(
{ newInstance, extend },
staticOffsetAPI,
otherStaticMethods,
Constants
);