PiecewiseFunction

Introduction

vtkPiecewiseFunction Defines a piecewise function mapping. This mapping
allows the addition of control points, and allows the user to control the
function between the control points. A piecewise hermite curve is used
between control points, based on the sharpness and midpoint parameters. A
sharpness of 0 yields a piecewise linear function and a sharpness of 1 yields
a piecewise constant function. The midpoint is the normalized distance
between control points at which the curve reaches the median Y value.

The midpoint and sharpness values specified when adding a node are used to
control the transition to the next node (the last node’s values are ignored)
Outside the range of nodes, the values are 0 if Clamping is off, or the
nearest node point if Clamping is on. Using the legacy methods for adding
points (which do not have Sharpness and Midpoint parameters) will default to
Midpoint = 0.5 (halfway between the control points) and Sharpness = 0.0
(linear).

Usage

const ofun = vtkPiecewiseFunction.newInstance();
ofun.addPoint(200.0, 0.0);
ofun.addPoint(1200.0, 0.2);
ofun.addPoint(4000.0, 0.4);

See Also

vtkColorTransferFunction

Methods

addPoint

Add points to the function.

Argument Type Required Description
x Number Yes The x coordinate.
y Number Yes The y coordinate.

addPointLong

Add points to the function.

Argument Type Required Description
x Number Yes The x coordinate.
y Number Yes The y coordinate.
midpoint Number Yes
sharpness Number Yes

addSegment

Add a line segment to the function.

Argument Type Required Description
x1 Number Yes The first point x coordinate.
y1 Number Yes The first point y coordinate.
x2 Number Yes The second point x coordinate.
y2 Number Yes The second point y coordinate.

adjustRange

Remove all points out of the new range, and make sure there is a point at
each end of that range.

Argument Type Required Description
range Range Yes

estimateMinNumberOfSamples

Estimates the minimum size of a table such that it would correctly sample
this function.

extend

Method used to decorate a given object (publicAPI+model) with vtkPiecewiseFunction characteristics.

Argument Type Required Description
publicAPI Yes object on which methods will be bounds (public)
model Yes object on which data structure will be bounds (protected)
initialValues IPiecewiseFunctionInitialValues No (default: {})

findMinimumXDistance

Traverses the nodes to find the minimum distance.

getAllowDuplicateScalars

Toggle whether to allow duplicate scalar values in the piecewise function
(off by default).

getClamping

When zero range clamping is Off, GetValue() returns 0.0 when a value is
requested outside of the points specified.

When zero range clamping is On, GetValue() returns the value at the value
at the lowest point for a request below all points specified and returns
the value at the highest point for a request above all points specified.
On is the default.

getDataPointer

Returns a pointer to the data stored in the table.

getFirstNonZeroValue

Returns the first point location which precedes a non-zero segment of the
function.

getNodeValue

For the node specified by index, set/get the location (X), value (Y),
midpoint, and sharpness values at the node.

Argument Type Required Description
index Number Yes
val Yes

getRange

Returns the min and max node locations of the function.

getRangeByReference

Returns the min and max node locations of the function.

getSize

Get the number of points used to specify the function.

getTable

Fills in an array of function values evaluated at regular intervals.

Argument Type Required Description
xStart Number Yes
xEnd Number Yes
size Number Yes
table Yes
stride Number No

getType

Return the type of function: Function Types:

  • 0 : Constant (No change in slope between end points)
  • 1 : NonDecreasing (Always increasing or zero slope)
  • 2 : NonIncreasing (Always decreasing or zero slope)
  • 3 : Varied (Contains both decreasing and increasing slopes)

getValue

Returns the value of the function at the specified location using the
specified interpolation.

newInstance

Method used to create a new instance of vtkPiecewiseFunction.

Argument Type Required Description
initialValues IPiecewiseFunctionInitialValues No for pre-setting some of its content

removeAllPoints

Removes all points from the function.

removePoint

Remove the first point found at the given x location Return the index of
the remove point if any, -1 otherwise.

Argument Type Required Description
x Number Yes

setAllowDuplicateScalars

Argument Type Required Description
allowDuplicateScalars Boolean Yes

setClamping

When zero range clamping is Off, GetValue() returns 0.0 when a value is
requested outside of the points specified.

When zero range clamping is On, GetValue() returns the value at the value
at the lowest point for a request below all points specified and returns
the value at the highest point for a request above all points specified.
On is the default.

Argument Type Required Description
clamping Boolean Yes

setNodeValue

Argument Type Required Description
index Number Yes
val Yes

setNodes

Argument Type Required Description
nodes Yes

setRange

Argument Type Required Description
min Number Yes
max Number Yes

setRange

Argument Type Required Description
range Range Yes

setRangeFrom

Argument Type Required Description
range Range Yes

sortAndUpdateRange

Internal method to sort the vector and update the Range whenever a node
is added, edited or removed.

updateRange

Returns true if the range has been updated and Modified() has been
called.

Source

index.d.ts
import { vtkObject } from "../../../interfaces" ;
import { Range } from "../../../types";

export interface IPiecewiseFunctionInitialValues {
range?: Range,
clamping?: boolean,
allowDuplicateScalars?: boolean,
}

export interface vtkPiecewiseFunction extends vtkObject {

/**
* Add points to the function.
* @param {Number} x The x coordinate.
* @param {Number} y The y coordinate.
*/
addPoint(x: number, y: number): void;

/**
* Add points to the function.
* @param {Number} x The x coordinate.
* @param {Number} y The y coordinate.
* @param {Number} midpoint
* @param {Number} sharpness
*/
addPointLong(x: number, y: number, midpoint: number, sharpness: number): number;

/**
* Add a line segment to the function.
* @param {Number} x1 The first point x coordinate.
* @param {Number} y1 The first point y coordinate.
* @param {Number} x2 The second point x coordinate.
* @param {Number} y2 The second point y coordinate.
*/
addSegment(x1: number, y1: number, x2: number, y2: number): void;

/**
* Remove all points out of the new range, and make sure there is a point at
* each end of that range.
* @param {Range} range
*/
adjustRange(range: Range): number;

/**
* Estimates the minimum size of a table such that it would correctly sample
* this function.
*/
estimateMinNumberOfSamples(): number;

/**
* Traverses the nodes to find the minimum distance.
*/
findMinimumXDistance(): number;

/**
* Toggle whether to allow duplicate scalar values in the piecewise function
* (off by default).
*/
getAllowDuplicateScalars(): boolean;

/**
* When zero range clamping is Off, GetValue() returns 0.0 when a value is
* requested outside of the points specified.
*
* When zero range clamping is On, GetValue() returns the value at the value
* at the lowest point for a request below all points specified and returns
* the value at the highest point for a request above all points specified.
* On is the default.
*/
getClamping(): boolean;

/**
* Returns a pointer to the data stored in the table.
*/
getDataPointer(): any[];

/**
* Returns the first point location which precedes a non-zero segment of the
* function.
*/
getFirstNonZeroValue(): number;

/**
* For the node specified by index, set/get the location (X), value (Y),
* midpoint, and sharpness values at the node.
* @param {Number} index
* @param val
*/
getNodeValue(index: number, val: any[]): void;

/**
* Returns the min and max node locations of the function.
*/
getRange(): Range;

/**
* Returns the min and max node locations of the function.
*/
getRangeByReference(): Range;

/**
* Get the number of points used to specify the function.
*/
getSize(): number;

/**
* Fills in an array of function values evaluated at regular intervals.
* @param {Number} xStart
* @param {Number} xEnd
* @param {Number} size
* @param table
* @param {Number} [stride]
*/
getTable(xStart: number, xEnd: number, size: number, table: any, stride?: number): void;

/**
* Return the type of function: Function Types:
* * 0 : Constant (No change in slope between end points)
* * 1 : NonDecreasing (Always increasing or zero slope)
* * 2 : NonIncreasing (Always decreasing or zero slope)
* * 3 : Varied (Contains both decreasing and increasing slopes)
*/
getType(): 'Constant' | 'NonDecreasing' | 'NonIncreasing' | 'Varied';

/**
* Returns the value of the function at the specified location using the
* specified interpolation.
*/
getValue(): any;

/**
* Removes all points from the function.
*/
removeAllPoints(): void;

/**
* Remove the first point found at the given x location Return the index of
* the remove point if any, -1 otherwise.
* @param {Number} x
*/
removePoint(x: number): number;

/**
*
* @param {Boolean} allowDuplicateScalars
*/
setAllowDuplicateScalars(allowDuplicateScalars: boolean): boolean;

/**
* When zero range clamping is Off, GetValue() returns 0.0 when a value is
* requested outside of the points specified.
*
* When zero range clamping is On, GetValue() returns the value at the value
* at the lowest point for a request below all points specified and returns
* the value at the highest point for a request above all points specified.
* On is the default.
* @param {Boolean} clamping
*/
setClamping(clamping: boolean): boolean;

/**
*
* @param {Number} index
* @param val
*/
setNodeValue(index: number, val: any[]): number;

/**
*
* @param nodes
*/
setNodes(nodes: any[]): void;

/**
*
* @param {Range} range
*/
setRange(range: Range): boolean;

/**
*
* @param {Number} min
* @param {Number} max
*/
setRange(min: number, max: number): boolean;

/**
*
* @param {Range} range
*/
setRangeFrom(range: Range): boolean;

/**
* Internal method to sort the vector and update the Range whenever a node
* is added, edited or removed.

*/
sortAndUpdateRange(): void;

/**
* Returns true if the range has been updated and Modified() has been
* called.
*/
updateRange(): boolean;
}

/**
* Method used to decorate a given object (publicAPI+model) with vtkPiecewiseFunction characteristics.
*
* @param publicAPI object on which methods will be bounds (public)
* @param model object on which data structure will be bounds (protected)
* @param {IPiecewiseFunctionInitialValues} [initialValues] (default: {})
*/
export function extend(publicAPI: object, model: object, initialValues?: IPiecewiseFunctionInitialValues): void;

/**
* Method used to create a new instance of vtkPiecewiseFunction.
* @param {IPiecewiseFunctionInitialValues} [initialValues] for pre-setting some of its content
*/
export function newInstance(initialValues?: IPiecewiseFunctionInitialValues): vtkPiecewiseFunction;

/**
* vtkPiecewiseFunction Defines a piecewise function mapping. This mapping
* allows the addition of control points, and allows the user to control the
* function between the control points. A piecewise hermite curve is used
* between control points, based on the sharpness and midpoint parameters. A
* sharpness of 0 yields a piecewise linear function and a sharpness of 1 yields
* a piecewise constant function. The midpoint is the normalized distance
* between control points at which the curve reaches the median Y value.
*
* The midpoint and sharpness values specified when adding a node are used to
* control the transition to the next node (the last node's values are ignored)
* Outside the range of nodes, the values are 0 if Clamping is off, or the
* nearest node point if Clamping is on. Using the legacy methods for adding
* points (which do not have Sharpness and Midpoint parameters) will default to
* Midpoint = 0.5 (halfway between the control points) and Sharpness = 0.0
* (linear).
*
* @example
* ```js
* const ofun = vtkPiecewiseFunction.newInstance();
* ofun.addPoint(200.0, 0.0);
* ofun.addPoint(1200.0, 0.2);
* ofun.addPoint(4000.0, 0.4);
* ```
*
* @see [vtkColorTransferFunction](./Rendering_Core_ColorTransferFunction.html)
*/
export declare const vtkPiecewiseFunction: {
newInstance: typeof newInstance,
extend: typeof extend;
};
export default vtkPiecewiseFunction;
index.js
import macro from 'vtk.js/Sources/macros';

const { vtkErrorMacro } = macro;

// ----------------------------------------------------------------------------
// vtkPiecewiseFunction methods
// ----------------------------------------------------------------------------

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

// Return the number of points which specify this function
publicAPI.getSize = () => model.nodes.length;

// Return the type of function stored in object:
// Function Types:
// 0 : Constant (No change in slope between end points)
// 1 : NonDecreasing (Always increasing or zero slope)
// 2 : NonIncreasing (Always decreasing or zero slope)
// 3 : Varied (Contains both decreasing and increasing slopes)
// 4 : Unknown (Error condition)
//
publicAPI.getType = () => {
let value;
let prevValue = 0.0;
let functionType = 0;

if (model.nodes.length > 0) {
prevValue = model.nodes[0].y;
}

for (let i = 1; i < model.nodes.length; i++) {
value = model.nodes[i].y;

// Do not change the function type if equal
if (value !== prevValue) {
if (value > prevValue) {
switch (functionType) {
case 0:
case 1:
// NonDecreasing
functionType = 1;
break;
case 2:
default:
// Varied
functionType = 3;
break;
}
} else {
// value < prev_value
switch (functionType) {
case 0:
case 2:
// NonIncreasing
functionType = 2;
break;
case 1:
default:
// Varied
functionType = 3;
break;
}
}
}

prevValue = value;

// Exit loop if we find a Varied function
if (functionType === 3) {
break;
}
}

switch (functionType) {
case 0:
return 'Constant';
case 1:
return 'NonDecreasing';
case 2:
return 'NonIncreasing';
case 3:
default:
return 'Varied';
}
};

// Since we no longer store the data in an array, we must
// copy out of the vector into an array. No modified check -
// could be added if performance is a problem
publicAPI.getDataPointer = () => {
const size = model.nodes.length;

model.function = null;

if (size > 0) {
model.function = [];
for (let i = 0; i < size; i++) {
model.function[2 * i] = model.nodes[i].x;
model.function[2 * i + 1] = model.nodes[i].y;
}
}
return model.function;
};

// Returns the first point location which starts a non-zero segment of the
// function. Note that the value at this point may be zero.
publicAPI.getFirstNonZeroValue = () => {
// Check if no points specified
if (model.nodes.length === 0) {
return 0;
}

let allZero = 1;
let x = 0.0;
let i = 0;
for (; i < model.nodes.length; i++) {
if (model.nodes[i].y !== 0.0) {
allZero = 0;
break;
}
}

// If every specified point has a zero value then return
// a large value
if (allZero) {
x = Number.MAX_VALUE;
} else if (i > 0) {
// A point was found with a non-zero value
// Return the value of the point that precedes this one
x = model.nodes[i - 1].x;
} else if (model.clamping) {
// If this is the first point in the function, return its
// value is clamping is off, otherwise VTK_DOUBLE_MIN if
// clamping is on.
x = -Number.MAX_VALUE;
} else {
x = model.nodes[0].x;
}

return x;
};

// For a specified index value, get the node parameters
publicAPI.getNodeValue = (index, val) => {
const size = model.nodes.length;

if (index < 0 || index >= size) {
vtkErrorMacro('Index out of range!');
return -1;
}

val[0] = model.nodes[index].x;
val[1] = model.nodes[index].y;
val[2] = model.nodes[index].midpoint;
val[3] = model.nodes[index].sharpness;

return 1;
};

// For a specified index value, get the node parameters
publicAPI.setNodeValue = (index, val) => {
const size = model.nodes.length;

if (index < 0 || index >= size) {
vtkErrorMacro('Index out of range!');
return -1;
}

const oldX = model.nodes[index].x;
model.nodes[index].x = val[0];
model.nodes[index].y = val[1];
model.nodes[index].midpoint = val[2];
model.nodes[index].sharpness = val[3];

if (oldX !== val[0]) {
// The point has been moved, the order of points or the range might have
// been modified.
publicAPI.sortAndUpdateRange();
// No need to call Modified() here because SortAndUpdateRange() has done it
// already.
} else {
publicAPI.modified();
}

return 1;
};

// Adds a point to the function. If a duplicate point is inserted
// then the function value at that location is set to the new value.
// This is the legacy version that assumes midpoint = 0.5 and
// sharpness = 0.0
publicAPI.addPoint = (x, y) => publicAPI.addPointLong(x, y, 0.5, 0.0);

// Adds a point to the function and returns the array index of the point.
publicAPI.addPointLong = (x, y, midpoint, sharpness) => {
// Error check
if (midpoint < 0.0 || midpoint > 1.0) {
vtkErrorMacro('Midpoint outside range [0.0, 1.0]');
return -1;
}

if (sharpness < 0.0 || sharpness > 1.0) {
vtkErrorMacro('Sharpness outside range [0.0, 1.0]');
return -1;
}

// remove any node already at this X location
if (!model.allowDuplicateScalars) {
publicAPI.removePoint(x);
}

// Create the new node
const node = { x, y, midpoint, sharpness };

// Add it, then sort to get everything in order
model.nodes.push(node);
publicAPI.sortAndUpdateRange();

// Now find this node so we can return the index
let i;
for (i = 0; i < model.nodes.length; i++) {
if (model.nodes[i].x === x) {
break;
}
}

// If we didn't find it, something went horribly wrong so
// return -1
if (i < model.nodes.length) {
return i;
}

return -1;
};

publicAPI.setNodes = (nodes) => {
if (model.nodes !== nodes) {
model.nodes = nodes;
publicAPI.sortAndUpdateRange();
}
};

// Sort the vector in increasing order, then fill in
// the Range
publicAPI.sortAndUpdateRange = () => {
model.nodes.sort((a, b) => a.x - b.x);
const modifiedInvoked = publicAPI.updateRange();
// If range is updated, Modified() has been called, don't call it again.
if (!modifiedInvoked) {
publicAPI.modified();
}
};

//----------------------------------------------------------------------------
publicAPI.updateRange = () => {
const oldRange = model.range.slice();

const size = model.nodes.length;
if (size) {
model.range[0] = model.nodes[0].x;
model.range[1] = model.nodes[size - 1].x;
} else {
model.range[0] = 0;
model.range[1] = 0;
}
// If the rage is the same, then no need to call Modified()
if (oldRange[0] === model.range[0] && oldRange[1] === model.range[1]) {
return false;
}

publicAPI.modified();
return true;
};

// Removes a point from the function. If no point is found then function
// remains the same.
publicAPI.removePoint = (x) => {
// First find the node since we need to know its
// index as our return value
let i;
for (i = 0; i < model.nodes.length; i++) {
if (model.nodes[i].x === x) {
break;
}
}

// If the node doesn't exist, we return -1
if (i >= model.nodes.length) {
return -1;
}

const retVal = i;

// If the first or last point has been removed, then we update the range
// No need to sort here as the order of points hasn't changed.
let modifiedInvoked = false;
model.nodes.splice(i, 1);
if (i === 0 || i === model.nodes.length) {
modifiedInvoked = publicAPI.updateRange();
}
if (!modifiedInvoked) {
publicAPI.modified();
}

return retVal;
};

// Removes all points from the function.
publicAPI.removeAllPoints = () => {
model.nodes = [];
publicAPI.sortAndUpdateRange();
};

// Add in end points of line and remove any points between them
// Legacy method with no way to specify midpoint and sharpness
publicAPI.addSegment = (x1, y1, x2, y2) => {
// First, find all points in this range and remove them
publicAPI.sortAndUpdateRange();
for (let i = 0; i < model.nodes.length; ) {
if (model.nodes[i].x >= x1 && model.nodes[i].x <= x2) {
model.nodes.splice(i, 1);
} else {
i++;
}
}

// Now add the points
publicAPI.addPoint(x1, y1, 0.5, 0.0);
publicAPI.addPoint(x2, y2, 0.5, 0.0);
};

// Return the value of the function at a position
publicAPI.getValue = (x) => {
const table = [];
publicAPI.getTable(x, x, 1, table);
return table[0];
};

// Remove all points outside the range, and make sure a point
// exists at each end of the range. Used as a convenience method
// for transfer function editors
publicAPI.adjustRange = (range) => {
if (range.length < 2) {
return 0;
}

const functionRange = publicAPI.getRange();

// Make sure we have points at each end of the range
if (functionRange[0] < range[0]) {
publicAPI.addPoint(range[0], publicAPI.getValue(range[0]));
} else {
publicAPI.addPoint(range[0], publicAPI.getValue(functionRange[0]));
}

if (functionRange[1] > range[1]) {
publicAPI.addPoint(range[1], publicAPI.getValue(range[1]));
} else {
publicAPI.addPoint(range[1], publicAPI.getValue(functionRange[1]));
}

// Remove all points out-of-range
publicAPI.sortAndUpdateRange();
for (let i = 0; i < model.nodes.length; ) {
if (model.nodes[i].x >= range[0] && model.nodes[i].x <= range[1]) {
model.nodes.splice(i, 1);
} else {
++i;
}
}

publicAPI.sortAndUpdateRange();
return 1;
};

//--------------------------------------------------------------------------
publicAPI.estimateMinNumberOfSamples = (x1, x2) => {
const d = publicAPI.findMinimumXDistance();
return Math.ceil((x2 - x1) / d);
};

//----------------------------------------------------------------------------
publicAPI.findMinimumXDistance = () => {
const size = model.nodes.length;
if (size < 2) {
return -1.0;
}

let distance = model.nodes[1].x - model.nodes[0].x;
for (let i = 0; i < size - 1; i++) {
const currentDist = model.nodes[i + 1].x - model.nodes[i].x;
if (currentDist < distance) {
distance = currentDist;
}
}

return distance;
};

// Returns a table of function values evaluated at regular intervals
/* eslint-disable prefer-destructuring */
/* eslint-disable no-continue */
publicAPI.getTable = (xStart, xEnd, size, table, stride = 1) => {
let i;
let idx = 0;
const numNodes = model.nodes.length;

// Need to keep track of the last value so that
// we can fill in table locations past this with
// this value if Clamping is On.
let lastValue = 0.0;
if (numNodes !== 0) {
lastValue = model.nodes[numNodes - 1].y;
}

let x = 0.0;
let x1 = 0.0;
let x2 = 0.0;
let y1 = 0.0;
let y2 = 0.0;
let midpoint = 0.0;
let sharpness = 0.0;

// For each table entry
for (i = 0; i < size; i++) {
// Find our location in the table
const tidx = stride * i;

// Find our X location. If we are taking only 1 sample, make
// it halfway between start and end (usually start and end will
// be the same in this case)
if (size > 1) {
x = xStart + (i / (size - 1.0)) * (xEnd - xStart);
} else {
x = 0.5 * (xStart + xEnd);
}

// Do we need to move to the next node?
while (idx < numNodes && x > model.nodes[idx].x) {
idx++;
// If we are at a valid point index, fill in
// the value at this node, and the one before (the
// two that surround our current sample location)
// idx cannot be 0 since we just incremented it.
if (idx < numNodes) {
x1 = model.nodes[idx - 1].x;
x2 = model.nodes[idx].x;

y1 = model.nodes[idx - 1].y;
y2 = model.nodes[idx].y;

// We only need the previous midpoint and sharpness
// since these control this region
midpoint = model.nodes[idx - 1].midpoint;
sharpness = model.nodes[idx - 1].sharpness;

// Move midpoint away from extreme ends of range to avoid
// degenerate math
if (midpoint < 0.00001) {
midpoint = 0.00001;
}

if (midpoint > 0.99999) {
midpoint = 0.99999;
}
}
}

// Are we at the end? If so, just use the last value
if (idx >= numNodes) {
table[tidx] = model.clamping ? lastValue : 0.0;
} else if (idx === 0) {
// Are we before the first node? If so, duplicate this nodes values
table[tidx] = model.clamping ? model.nodes[0].y : 0.0;
} else {
// Otherwise, we are between two nodes - interpolate
// Our first attempt at a normalized location [0,1] -
// we will be modifying this based on midpoint and
// sharpness to get the curve shape we want and to have
// it pass through (y1+y2)/2 at the midpoint.
let s = (x - x1) / (x2 - x1);

// Readjust based on the midpoint - linear adjustment
if (s < midpoint) {
s = (0.5 * s) / midpoint;
} else {
s = 0.5 + (0.5 * (s - midpoint)) / (1.0 - midpoint);
}

// override for sharpness > 0.99
// In this case we just want piecewise constant
if (sharpness > 0.99) {
// Use the first value since we are below the midpoint
if (s < 0.5) {
table[tidx] = y1;
continue;
} else {
// Use the second value at or above the midpoint
table[tidx] = y2;
continue;
}
}

// Override for sharpness < 0.01
// In this case we want piecewise linear
if (sharpness < 0.01) {
// Simple linear interpolation
table[tidx] = (1 - s) * y1 + s * y2;
continue;
}

// We have a sharpness between [0.01, 0.99] - we will
// used a modified hermite curve interpolation where we
// derive the slope based on the sharpness, and we compress
// the curve non-linearly based on the sharpness

// First, we will adjust our position based on sharpness in
// order to make the curve sharper (closer to piecewise constant)
if (s < 0.5) {
s = 0.5 * (s * 2) ** (1.0 + 10 * sharpness);
} else if (s > 0.5) {
s = 1.0 - 0.5 * ((1.0 - s) * 2) ** (1 + 10 * sharpness);
}

// Compute some coefficients we will need for the hermite curve
const ss = s * s;
const sss = ss * s;

const h1 = 2 * sss - 3 * ss + 1;
const h2 = -2 * sss + 3 * ss;
const h3 = sss - 2 * ss + s;
const h4 = sss - ss;

// Use one slope for both end points
const slope = y2 - y1;
const t = (1.0 - sharpness) * slope;

// Compute the value
table[tidx] = h1 * y1 + h2 * y2 + h3 * t + h4 * t;

// Final error check to make sure we don't go outside
// the Y range
const min = y1 < y2 ? y1 : y2;
const max = y1 > y2 ? y1 : y2;

table[tidx] = table[tidx] < min ? min : table[tidx];
table[tidx] = table[tidx] > max ? max : table[tidx];
}
}
};
}
/* eslint-enable prefer-destructuring */
/* eslint-enable no-continue */

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

const DEFAULT_VALUES = {
// model.function = NULL;
range: [0, 0],
clamping: true,
allowDuplicateScalars: false,
};

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

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

// Inheritance
macro.obj(publicAPI, model);

// Internal objects initialization
model.nodes = [];

// Create get-set macros
macro.setGet(publicAPI, model, ['allowDuplicateScalars', 'clamping']);

macro.setArray(publicAPI, model, ['range'], 2);

// Create get macros for array
macro.getArray(publicAPI, model, ['range']);

// For more macro methods, see "Sources/macros.js"

// Object specific methods
vtkPiecewiseFunction(publicAPI, model);
}

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

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

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

export default { newInstance, extend };