Skip to content

Commit c1610de

Browse files
committed
feat(spline1D,spline3D): open splines
Two methods implemented (Kochanek, Cardinal). Both methods need boundary conditions. Different conditions are available (on the derivative, the second derivative or the second derivative on the interior point). Values for those conditions can be chosen. The boundary conditions as well as the value for it are the same on both sides but could be separated in further implementations. The spline widget and its example have been updated.
1 parent a932c53 commit c1610de

File tree

13 files changed

+497
-42
lines changed

13 files changed

+497
-42
lines changed

Sources/Common/DataModel/CardinalSpline1D/index.d.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Vector3 } from '../../../types';
2-
import vtkSpline1D, { ISpline1DInitialValues } from '../Spline1D';
3-
2+
import vtkSpline1D, { ISpline1DInitialValues, BoundaryCondition } from '../Spline1D';
43

54
export interface ICardinalSpline1DInitialValues extends ISpline1DInitialValues {}
65

@@ -20,9 +19,14 @@ export interface vtkCardinalSpline1D extends vtkSpline1D {
2019
* @param {Number} size
2120
* @param {Float32Array} work
2221
* @param {Vector3} x
23-
* @param {Vector3} y
22+
* @param {Vector3} y
23+
* @param {Object} options
24+
* @param {BoundaryCondition} options.leftConstraint
25+
* @param {Number} options.leftValue
26+
* @param {BoundaryCondition} options.rightConstraint
27+
* @param {Number} options.rightValue
2428
*/
25-
computeOpenCoefficients(size: number, work: Float32Array, x: Vector3, y: Vector3): void;
29+
computeOpenCoefficients(size: number, work: Float32Array, x: Vector3, y: Vector3, options: { leftConstraint: BoundaryCondition, leftValue: number, rightConstraint: BoundaryCondition, rightValue: Number }): void;
2630

2731
/**
2832
*

Sources/Common/DataModel/CardinalSpline1D/index.js

Lines changed: 144 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import macro from 'vtk.js/Sources/macros';
22
import vtkSpline1D from 'vtk.js/Sources/Common/DataModel/Spline1D';
33

4-
const { vtkErrorMacro } = macro;
4+
import { BoundaryCondition } from 'vtk.js/Sources/Common/DataModel/Spline1D/Constants';
5+
6+
const VTK_EPSILON = 0.0001;
57

68
// ----------------------------------------------------------------------------
79
// vtkCardinalSpline1D methods
@@ -46,9 +48,9 @@ function vtkCardinalSpline1D(publicAPI, model) {
4648
const dN = work[N];
4749

4850
// solve resulting set of equations.
49-
model.coefficients[0 * 4 + 2] = 0;
51+
model.coefficients[4 * 0 + 2] = 0;
5052
work[0] = 0;
51-
model.coefficients[0 * 4 + 3] = 1;
53+
model.coefficients[4 * 0 + 3] = 1;
5254

5355
for (let k = 1; k <= N; k++) {
5456
model.coefficients[4 * k + 1] -=
@@ -114,8 +116,145 @@ function vtkCardinalSpline1D(publicAPI, model) {
114116

115117
// --------------------------------------------------------------------------
116118

117-
publicAPI.computeOpenCoefficients = (size, work, x, y) => {
118-
vtkErrorMacro('Open splines are not implemented yet!');
119+
publicAPI.computeOpenCoefficients = (size, work, x, y, options = {}) => {
120+
if (!model.coefficients || model.coefficients.length !== 4 * size) {
121+
model.coefficients = new Float32Array(4 * size);
122+
}
123+
const N = size - 1;
124+
// develop constraint at leftmost point.
125+
switch (options.leftConstraint) {
126+
case BoundaryCondition.DERIVATIVE:
127+
// desired slope at leftmost point is leftValue.
128+
model.coefficients[4 * 0 + 1] = 1.0;
129+
model.coefficients[4 * 0 + 2] = 0.0;
130+
work[0] = options.leftValue;
131+
break;
132+
case BoundaryCondition.SECOND_DERIVATIVE:
133+
// desired second derivative at leftmost point is leftValue.
134+
model.coefficients[4 * 0 + 1] = 2.0;
135+
model.coefficients[4 * 0 + 2] = 1.0;
136+
work[0] =
137+
3.0 * ((y[1] - y[0]) / (x[1] - x[0])) -
138+
0.5 * (x[1] - x[0]) * options.leftValue;
139+
break;
140+
case BoundaryCondition.SECOND_DERIVATIVE_INTERIOR_POINT:
141+
// desired second derivative at leftmost point is
142+
// leftValue times second derivative at first interior point.
143+
model.coefficients[4 * 0 + 1] = 2.0;
144+
145+
if (Math.abs(options.leftValue + 2) > VTK_EPSILON) {
146+
model.coefficients[4 * 0 + 2] =
147+
4.0 * ((0.5 + options.leftValue) / (2.0 + options.leftValue));
148+
work[0] =
149+
6.0 *
150+
((1.0 + options.leftValue) / (2.0 + options.leftValue)) *
151+
((y[1] - y[0]) / (x[1] - x[0]));
152+
} else {
153+
model.coefficients[4 * 0 + 2] = 0;
154+
work[0] = 0;
155+
}
156+
break;
157+
case BoundaryCondition.DEFAULT:
158+
default:
159+
// desired slope at leftmost point is derivative from two points
160+
model.coefficients[4 * 0 + 1] = 1.0;
161+
model.coefficients[4 * 0 + 2] = 0.0;
162+
work[0] = y[2] - y[0];
163+
break;
164+
}
165+
166+
for (let k = 1; k < N; k++) {
167+
const xlk = x[k] - x[k - 1];
168+
const xlkp = x[k + 1] - x[k];
169+
170+
model.coefficients[4 * k + 0] = xlkp;
171+
model.coefficients[4 * k + 1] = 2 * (xlkp + xlk);
172+
model.coefficients[4 * k + 2] = xlk;
173+
work[k] =
174+
3.0 *
175+
((xlkp * (y[k] - y[k - 1])) / xlk + (xlk * (y[k + 1] - y[k])) / xlkp);
176+
}
177+
178+
// develop constraint at rightmost point.
179+
switch (options.rightConstraint) {
180+
case BoundaryCondition.DERIVATIVE:
181+
// desired slope at rightmost point is rightValue
182+
model.coefficients[4 * N + 0] = 0.0;
183+
model.coefficients[4 * N + 1] = 1.0;
184+
work[N] = options.rightValue;
185+
break;
186+
case BoundaryCondition.SECOND_DERIVATIVE:
187+
// desired second derivative at rightmost point is rightValue.
188+
model.coefficients[4 * N + 0] = 1.0;
189+
model.coefficients[4 * N + 1] = 2.0;
190+
work[N] =
191+
3.0 * ((y[N] - y[N - 1]) / (x[N] - x[N - 1])) +
192+
0.5 * (x[N] - x[N - 1]) * options.rightValue;
193+
break;
194+
case BoundaryCondition.SECOND_DERIVATIVE_INTERIOR_POINT:
195+
// desired second derivative at rightmost point is
196+
// rightValue times second derivative at last interior point.
197+
model.coefficients[4 * N + 1] = 2.0;
198+
if (Math.abs(options.rightValue + 2) > VTK_EPSILON) {
199+
model.coefficients[4 * N + 0] =
200+
4.0 * ((0.5 + options.rightValue) / (2.0 + options.rightValue));
201+
work[N] =
202+
6.0 *
203+
((1.0 + options.rightValue) / (2.0 + options.rightValue)) *
204+
((y[N] - y[size - 2]) / (x[N] - x[size - 2]));
205+
} else {
206+
model.coefficients[4 * N + 0] = 0;
207+
work[N] = 0;
208+
}
209+
break;
210+
case BoundaryCondition.DEFAULT:
211+
default:
212+
// desired slope at rightmost point is derivative from two points
213+
model.coefficients[4 * N + 0] = 0.0;
214+
model.coefficients[4 * N + 1] = 1.0;
215+
work[N] = y[N] - y[N - 2];
216+
break;
217+
}
218+
219+
// solve resulting set of equations.
220+
model.coefficients[4 * 0 + 2] /= model.coefficients[4 * 0 + 1];
221+
work[0] /= model.coefficients[4 * N + 1];
222+
model.coefficients[4 * N + 3] = 1;
223+
224+
for (let k = 1; k <= N; k++) {
225+
model.coefficients[4 * k + 1] -=
226+
model.coefficients[4 * k + 0] * model.coefficients[4 * (k - 1) + 2];
227+
model.coefficients[4 * k + 2] /= model.coefficients[4 * k + 1];
228+
work[k] =
229+
(work[k] - model.coefficients[4 * k + 0] * work[k - 1]) /
230+
model.coefficients[4 * k + 1];
231+
}
232+
233+
for (let k = N - 1; k >= 0; k--) {
234+
work[k] -= model.coefficients[4 * k + 2] * work[k + 1];
235+
}
236+
237+
// the column vector work now contains the first
238+
// derivative of the spline function at each joint.
239+
// compute the coefficients of the cubic between
240+
// each pair of joints.
241+
for (let k = 0; k < N; k++) {
242+
const b = x[k + 1] - x[k];
243+
model.coefficients[4 * k + 0] = y[k];
244+
model.coefficients[4 * k + 1] = work[k];
245+
model.coefficients[4 * k + 2] =
246+
(3 * (y[k + 1] - y[k])) / (b * b) - (work[k + 1] + 2 * work[k]) / b;
247+
model.coefficients[4 * k + 3] =
248+
(2 * (y[k] - y[k + 1])) / (b * b * b) +
249+
(work[k + 1] + work[k]) / (b * b);
250+
}
251+
252+
// the coefficients of a fictitious nth cubic
253+
// are the same as the coefficients in the first interval
254+
model.coefficients[4 * N + 0] = y[N];
255+
model.coefficients[4 * N + 1] = work[N];
256+
model.coefficients[4 * N + 2] = model.coefficients[4 * 0 + 2];
257+
model.coefficients[4 * N + 3] = model.coefficients[4 * 0 + 3];
119258
};
120259

121260
// --------------------------------------------------------------------------

Sources/Common/DataModel/KochanekSpline1D/index.d.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import vtkSpline1D, { ISpline1DInitialValues } from '../Spline1D';
2-
1+
import vtkSpline1D, { ISpline1DInitialValues, BoundaryCondition } from '../Spline1D';
32

43
export interface IKochanekSpline1DInitialValues extends ISpline1DInitialValues {
54
tension?: number;
@@ -23,9 +22,14 @@ export interface vtkKochanekSpline1D extends vtkSpline1D {
2322
* @param {Number} size
2423
* @param {Float32Array} work
2524
* @param {Number[]} x
26-
* @param {Number[]} y
25+
* @param {Number[]} y
26+
* @param {Object} options
27+
* @param {BoundaryCondition} options.leftConstraint
28+
* @param {Number} options.leftValue
29+
* @param {BoundaryCondition} options.rightConstraint
30+
* @param {Number} options.rightValue
2731
*/
28-
computeOpenCoefficients(size: number, work: Float32Array, x: number[], y: number[]): void;
32+
computeOpenCoefficients(size: number, work: Float32Array, x: number[], y: number[], options: { leftConstraint: BoundaryCondition, leftValue: number, rightConstraint: BoundaryCondition, rightValue: Number }): void;
2933

3034
/**
3135
*

Sources/Common/DataModel/KochanekSpline1D/index.js

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import macro from 'vtk.js/Sources/macros';
22
import vtkSpline1D from 'vtk.js/Sources/Common/DataModel/Spline1D';
33

4-
const { vtkErrorMacro } = macro;
4+
import { BoundaryCondition } from 'vtk.js/Sources/Common/DataModel/Spline1D/Constants';
5+
6+
const VTK_EPSILON = 0.0001;
57

68
// ----------------------------------------------------------------------------
79
// vtkKochanekSpline1D methods
@@ -103,8 +105,132 @@ function vtkKochanekSpline1D(publicAPI, model) {
103105

104106
// --------------------------------------------------------------------------
105107

106-
publicAPI.computeOpenCoefficients = (size, work, x, y) => {
107-
vtkErrorMacro('Open splines are not implemented yet!');
108+
publicAPI.computeOpenCoefficients = (size, work, x, y, options = {}) => {
109+
if (!model.coefficients || model.coefficients.length !== 4 * size) {
110+
model.coefficients = new Float32Array(4 * size);
111+
}
112+
const N = size - 1;
113+
114+
for (let i = 1; i < N; i++) {
115+
const cs = y[i] - y[i - 1];
116+
const cd = y[i + 1] - y[i];
117+
118+
let ds =
119+
cs * ((1 - model.tension) * (1 - model.continuity) * (1 + model.bias)) +
120+
cd * ((1 - model.tension) * (1 + model.continuity) * (1 - model.bias));
121+
122+
let dd =
123+
cs * ((1 - model.tension) * (1 + model.continuity) * (1 + model.bias)) +
124+
cd * ((1 - model.tension) * (1 - model.continuity) * (1 - model.bias));
125+
126+
// adjust deriviatives for non uniform spacing between nodes
127+
const n1 = x[i + 1] - x[i];
128+
const n0 = x[i] - x[i - 1];
129+
130+
ds *= n0 / (n0 + n1);
131+
dd *= n1 / (n0 + n1);
132+
133+
model.coefficients[4 * i + 0] = y[i];
134+
model.coefficients[4 * i + 1] = dd;
135+
model.coefficients[4 * i + 2] = ds;
136+
}
137+
138+
model.coefficients[4 * 0 + 0] = y[0];
139+
model.coefficients[4 * N + 0] = y[N];
140+
141+
// Calculate the deriviatives at the end points
142+
143+
switch (options.leftConstraint) {
144+
// desired slope at leftmost point is leftValue
145+
case BoundaryCondition.DERIVATIVE:
146+
model.coefficients[4 * 0 + 1] = options.leftValue;
147+
break;
148+
case BoundaryCondition.SECOND_DERIVATIVE:
149+
// desired second derivative at leftmost point is leftValue
150+
model.coefficients[4 * 0 + 1] =
151+
(6 * (y[1] - y[0]) -
152+
2 * model.coefficients[4 * 1 + 2] -
153+
options.leftValue) /
154+
4;
155+
break;
156+
case BoundaryCondition.SECOND_DERIVATIVE_INTERIOR_POINT:
157+
// desired second derivative at leftmost point is leftValue
158+
// times second derivative at first interior point
159+
if (Math.abs(options.leftValue + 2) > VTK_EPSILON) {
160+
model.coefficients[4 * 0 + 1] =
161+
(3 * (1 + options.leftValue) * (y[1] - y[0]) -
162+
(1 + 2 * options.leftValue) * model.coefficients[4 * 1 + 2]) /
163+
(2 + options.leftValue);
164+
} else {
165+
model.coefficients[4 * 0 + 1] = 0.0;
166+
}
167+
break;
168+
case BoundaryCondition.DEFAULT:
169+
default:
170+
// desired slope at leftmost point is derivative from two points
171+
model.coefficients[4 * 0 + 1] = y[2] - y[0];
172+
break;
173+
}
174+
switch (options.rightConstraint) {
175+
case BoundaryCondition.DERIVATIVE:
176+
// desired slope at rightmost point is leftValue
177+
model.coefficients[4 * N + 2] = options.leftValue;
178+
break;
179+
180+
case BoundaryCondition.SECOND_DERIVATIVE:
181+
// desired second derivative at rightmost point is leftValue
182+
model.coefficients[4 * N + 2] =
183+
(6 * (y[N] - y[N - 1]) -
184+
2 * model.coefficients[4 * (N - 1) + 1] +
185+
options.leftValue) /
186+
4.0;
187+
break;
188+
189+
case BoundaryCondition.SECOND_DERIVATIVE_INTERIOR_POINT:
190+
// desired second derivative at rightmost point is leftValue
191+
// times second derivative at last interior point
192+
if (Math.abs(options.leftValue + 2) > VTK_EPSILON) {
193+
model.coefficients[4 * N + 2] =
194+
(3 * (1 + options.leftValue) * (y[N] - y[N - 1]) -
195+
(1 + 2 * options.leftValue) *
196+
model.coefficients[4 * (N - 1) + 1]) /
197+
(2 + options.leftValue);
198+
} else {
199+
model.coefficients[4 * N + 2] = 0.0;
200+
}
201+
break;
202+
case BoundaryCondition.DEFAULT:
203+
default:
204+
// desired slope at rightmost point is rightValue
205+
model.coefficients[4 * N + 2] = y[N] - y[N - 2];
206+
break;
207+
}
208+
209+
for (let i = 0; i < N; i++) {
210+
//
211+
// c0 = P ; c1 = DD ;
212+
// i i i i
213+
//
214+
// c1 = P ; c2 = DS ;
215+
// i+1 i+1 i+1 i+1
216+
//
217+
// c2 = -3P + 3P - 2DD - DS ;
218+
// i i i+1 i i+1
219+
//
220+
// c3 = 2P - 2P + DD + DS ;
221+
// i i i+1 i i+1
222+
//
223+
model.coefficients[4 * i + 2] =
224+
-3 * y[i] +
225+
3 * y[i + 1] +
226+
-2 * model.coefficients[4 * i + 1] +
227+
-1 * model.coefficients[4 * (i + 1) + 2];
228+
model.coefficients[4 * i + 3] =
229+
2 * y[i] +
230+
-2 * y[i + 1] +
231+
1 * model.coefficients[4 * i + 1] +
232+
1 * model.coefficients[4 * (i + 1) + 2];
233+
}
108234
};
109235

110236
// --------------------------------------------------------------------------
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Boundary conditions available to compute open splines
2+
// DEFAULT : desired slope at boundary point is derivative from two points (boundary and second interior)
3+
// DERIVATIVE : desired slope at boundary point is the boundary value given.
4+
// SECOND_DERIVATIVE : second derivative at boundary point is the boundary value given.
5+
// SECOND_DERIVATIVE_INTERIOR_POINT : desired second derivative at boundary point is the boundary value given times second derivative
6+
// at first interior point.
7+
8+
export const BoundaryCondition = {
9+
DEFAULT: 0,
10+
DERIVATIVE: 1,
11+
SECOND_DERIVATIVE: 2,
12+
SECOND_DERIVATIVE_INTERIOR_POINT: 3,
13+
};
14+
15+
export default {
16+
BoundaryCondition,
17+
};

0 commit comments

Comments
 (0)