Skip to content

Commit 19ed33d

Browse files
authored
Merge pull request #2592 from finetjul/improve-edge-locator-perfs
fix(imagemarchingcubes): avoid duplicated points in marching cubes al…
2 parents b2e6153 + b7e60a8 commit 19ed33d

File tree

7 files changed

+206
-73
lines changed

7 files changed

+206
-73
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/**
2+
*
3+
*/
4+
export interface IEdgeLocatorInitialValues {
5+
oriented?: boolean;
6+
}
7+
8+
export interface vtkEdgeLocator {
9+
/**
10+
* Remove all the edges previously added.
11+
*/
12+
initialize(): void;
13+
14+
/**
15+
* Returns the inserted edge or null if no edge was inserted.
16+
* @param {Number} pointId0 Edge first point id
17+
* @param {Number} pointId1 Edge last point id
18+
* @return {key, edgeId, value} or null
19+
*/
20+
isInsertedEdge(pointId0: number, pointId1: number): { key: any; edgeId: number; value?: any } | null;
21+
22+
/**
23+
* Insert edge if it does not already exist.
24+
* Returns the existing or newly inserted edge.
25+
*
26+
* @param {Number} pointId0 Edge first point id
27+
* @param {Number} pointId1 Edge last point id
28+
* @param {any} value Optional value option
29+
* @return {key, edgeId, value}
30+
* @see insertEdge()
31+
* @see isInsertedEdge()
32+
*/
33+
insertUniqueEdge(
34+
pointId0: number,
35+
pointId1: number,
36+
value?: any
37+
): { key: any; edgeId: number; value?: any };
38+
39+
/**
40+
* Insert edge. If the edge already exists, it is overwritten by this
41+
* new edge. You may verify that the edge did not previously exist with
42+
* `isInsertedEdge()`.
43+
* Returns the newly inserted edge.
44+
* @param {Number} pointId0 Edge first point id
45+
* @param {Number} pointId1 Edge last point id
46+
* @param {any} value Optional value option
47+
* @return {key, edgeId, value} or null
48+
* @see isInsertedEdge
49+
* @see insertUniqueEdge
50+
*/
51+
insertEdge(pointId0: number,
52+
pointId1: number,
53+
value?: any
54+
): { key: any; edgeId: number; value?: any };
55+
}
56+
57+
// ----------------------------------------------------------------------------
58+
// Static API
59+
// ----------------------------------------------------------------------------
60+
61+
// ----------------------------------------------------------------------------
62+
63+
/**
64+
* Method use to create a new instance of vtkEdgeLocator
65+
* @param {IEdgeLocatorInitialValues} [initialValues] for pre-setting some of its content
66+
*/
67+
export function newInstance(
68+
initialValues?: IEdgeLocatorInitialValues
69+
): vtkEdgeLocator;
70+
71+
/**
72+
* vtkEdgeLocator
73+
*/
74+
export declare const vtkEdgeLocator: {
75+
newInstance: typeof newInstance;
76+
};
77+
78+
export default vtkEdgeLocator;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
class EdgeLocator {
2+
constructor(oriented = false) {
3+
this.oriented = oriented;
4+
this.edgeMap = new Map();
5+
}
6+
7+
initialize() {
8+
this.edgeMap.clear();
9+
}
10+
11+
computeEdgeKey(pointId0, pointId1) {
12+
return this.oriented || pointId0 < pointId1
13+
? // Cantor pairing function:
14+
0.5 * (pointId0 * pointId1) * (pointId0 * pointId1 + 1) + pointId1
15+
: 0.5 * (pointId1 * pointId0) * (pointId1 * pointId0 + 1) + pointId0;
16+
}
17+
18+
insertUniqueEdge(pointId0, pointId1, newEdgeValue) {
19+
// Generate a unique key
20+
const key = this.computeEdgeKey(pointId0, pointId1);
21+
let node = this.edgeMap.get(key);
22+
if (!node) {
23+
// Didn't find key, so add a new edge entry
24+
node = { key, edgeId: this.edgeMap.size, value: newEdgeValue };
25+
this.edgeMap.set(key, node);
26+
}
27+
return node;
28+
}
29+
30+
insertEdge(pointId0, pointId1, newEdgeValue) {
31+
// Generate a unique key
32+
const key = this.computeEdgeKey(pointId0, pointId1);
33+
// Didn't find key, so add a new edge entry
34+
const node = { key, edgeId: this.edgeMap.size, value: newEdgeValue };
35+
this.edgeMap.set(key, node);
36+
return node;
37+
}
38+
39+
isInsertedEdge(pointId0, pointId1) {
40+
const key = this.computeEdgeKey(pointId0, pointId1);
41+
return this.edgeMap.get(key);
42+
}
43+
44+
static getEdgePointIds(node) {
45+
const n = 0.5 * (-1 + Math.sqrt(8 * node.key + 1));
46+
const pointId0 = node.key - 0.5 * (n + 1) * n;
47+
const pointId1 = n - pointId0;
48+
return [pointId0, pointId1];
49+
}
50+
}
51+
52+
function newInstance(initialValues = {}) {
53+
return new EdgeLocator(initialValues.oriented);
54+
}
55+
56+
export default { newInstance };
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import test from 'tape-catch';
2+
3+
import vtkEdgeLocator from 'vtk.js/Sources/Common/DataModel/EdgeLocator';
4+
5+
test('Test unique edge', (t) => {
6+
const edgeLocator = vtkEdgeLocator.newInstance();
7+
const edge = edgeLocator.insertUniqueEdge(10, 13);
8+
t.ok(edge != null, 'First edge');
9+
t.equal(edge.edgeId, 0, 'Edge id');
10+
11+
const sameEdge = edgeLocator.insertUniqueEdge(10, 13);
12+
t.ok(sameEdge === edge, 'Same edge');
13+
const oppositeEdge = edgeLocator.insertUniqueEdge(13, 10);
14+
t.ok(oppositeEdge === edge, 'Opposite edge');
15+
t.equal(oppositeEdge.edgeId, 0, 'Opposite edge id');
16+
17+
const otherEdge = edgeLocator.insertUniqueEdge(11, 13);
18+
t.ok(otherEdge != null);
19+
t.ok(otherEdge !== edge);
20+
t.equal(otherEdge.edgeId, 1);
21+
22+
const edgeWithValue = edgeLocator.insertUniqueEdge(12, 13, 42);
23+
t.equal(edgeWithValue.value, 42, 'edge with value 42');
24+
25+
edgeWithValue.value = 54;
26+
const sameEdgeWithValue = edgeLocator.insertUniqueEdge(12, 13);
27+
t.equal(sameEdgeWithValue.value, 54, 'same edge with value 54');
28+
29+
t.end();
30+
});
31+
32+
test('Test oriented edge', (t) => {
33+
const edgeLocator = vtkEdgeLocator.newInstance({ oriented: true });
34+
const edge = edgeLocator.insertUniqueEdge(10, 13);
35+
t.ok(edge != null, 'First oriented edge');
36+
t.equal(edge.edgeId, 0, 'Oriented edge Id');
37+
const sameEdge = edgeLocator.insertUniqueEdge(10, 13);
38+
t.ok(sameEdge === edge, 'Same oriented edge');
39+
const oppositeEdge = edgeLocator.insertUniqueEdge(13, 10);
40+
t.ok(oppositeEdge !== edge, 'Opposite edge');
41+
t.equal(oppositeEdge.edgeId, 1, 'Opposite edge id');
42+
43+
t.end();
44+
});

Sources/Filters/General/ClipClosedSurface/ccsEdgeLocator.js

Lines changed: 0 additions & 21 deletions
This file was deleted.

Sources/Filters/General/ClipClosedSurface/index.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import vtkPoints from 'vtk.js/Sources/Common/Core/Points';
77
import vtkDataSetAttributes from 'vtk.js/Sources/Common/DataModel/DataSetAttributes';
88
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
99
import vtkContourTriangulator from 'vtk.js/Sources/Filters/General/ContourTriangulator';
10+
import vtkEdgeLocator from 'vtk.js/Sources/Common/DataModel/EdgeLocator';
1011

1112
import Constants from './Constants';
12-
import CCSEdgeLocator from './ccsEdgeLocator';
1313

1414
const { vtkErrorMacro, capitalize } = macro;
1515
const { ScalarMode } = Constants;
@@ -71,9 +71,9 @@ function vtkClipClosedSurface(publicAPI, model) {
7171
// After the above swap, i0 will be kept, and i1 will be clipped
7272

7373
// Check to see if this point has already been computed
74-
const node = locator.insertUniqueEdge(i0, i1);
75-
if (node.edgeId !== -1) {
76-
return node.edgeId;
74+
const edge = locator.insertUniqueEdge(i0, i1);
75+
if (edge.value != null) {
76+
return edge.value;
7777
}
7878

7979
// Get the edge and interpolate the new point
@@ -94,28 +94,28 @@ function vtkClipClosedSurface(publicAPI, model) {
9494

9595
// Make sure that new point is far enough from kept point
9696
if (vtkMath.distance2BetweenPoints(p, p0) < tol2) {
97-
node.edgeId = i0;
97+
edge.value = i0;
9898
return i0;
9999
}
100100

101101
if (vtkMath.distance2BetweenPoints(p, p1) < tol2) {
102-
node.edgeId = i1;
102+
edge.value = i1;
103103
return i1;
104104
}
105105

106-
node.edgeId = points.insertNextTuple(p);
107-
pointData.interpolateData(pointData, i0, i1, node.edgeId, t);
106+
edge.value = points.insertNextTuple(p);
107+
pointData.interpolateData(pointData, i0, i1, edge.value, t);
108108

109-
return node.edgeId;
109+
return edge.value;
110110
}
111111

112112
/**
113113
* Method for clipping lines and copying the scalar data.
114114
*
115115
* @param {vtkPoints} points
116116
* @param {vtkDataArray} pointScalars
117-
* @param {vtvtkDataSetAttributesk} pointData
118-
* @param {CCSEdgeLocator} edgeLocator
117+
* @param {vtkDataSetAttributesk} pointData
118+
* @param {vtkEdgeLocator} edgeLocator
119119
* @param {vtkCellArray} inputLines
120120
* @param {vtkCellArray} outputLines
121121
* @param {vtkDataSetAttributes} inLineData
@@ -394,7 +394,7 @@ function vtkClipClosedSurface(publicAPI, model) {
394394
* @param {vtkPoints} points
395395
* @param {vtkDataArray} pointScalars
396396
* @param {vtkDataSetAttributes} pointData
397-
* @param {CCSEdgeLocator} edgeLocator
397+
* @param {vtkEdgeLocator} edgeLocator
398398
* @param {Number} triangulate
399399
* @param {vtkCellArray} inputPolys
400400
* @param {vtkCellArray} outputPolys
@@ -670,7 +670,7 @@ function vtkClipClosedSurface(publicAPI, model) {
670670
}
671671

672672
// An edge locator to avoid point duplication while clipping
673-
const edgeLocator = new CCSEdgeLocator();
673+
const edgeLocator = vtkEdgeLocator.newInstance();
674674

675675
// A temporary polydata for the contour lines that are triangulated
676676
const tmpContourData = vtkPolyData.newInstance();

Sources/Filters/General/ImageMarchingCubes/index.js

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import macro from 'vtk.js/Sources/macros';
22
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
3+
import vtkEdgeLocator from 'vtk.js/Sources/Common/DataModel/EdgeLocator';
34
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
45
import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
56

@@ -19,7 +20,7 @@ function vtkImageMarchingCubes(publicAPI, model) {
1920
const voxelScalars = [];
2021
const voxelGradients = [];
2122
const voxelPts = [];
22-
const edgeMap = new Map();
23+
const edgeLocator = vtkEdgeLocator.newInstance();
2324

2425
// Retrieve scalars and voxel coordinates. i-j-k is origin of voxel.
2526
publicAPI.getVoxelScalars = (i, j, k, slice, dims, origin, spacing, s) => {
@@ -213,8 +214,6 @@ function vtkImageMarchingCubes(publicAPI, model) {
213214
const xyz = [];
214215
const n = [];
215216
let pId;
216-
let tmp;
217-
const edge = [];
218217

219218
publicAPI.getVoxelScalars(i, j, k, slice, dims, origin, spacing, scalars);
220219

@@ -247,14 +246,10 @@ function vtkImageMarchingCubes(publicAPI, model) {
247246
const edgeVerts = vtkCaseTable.getEdge(voxelTris[idx + eid]);
248247
pId = undefined;
249248
if (model.mergePoints) {
250-
edge[0] = ids[edgeVerts[0]];
251-
edge[1] = ids[edgeVerts[1]];
252-
if (edge[0] > edge[1]) {
253-
tmp = edge[0];
254-
edge[0] = edge[1];
255-
edge[1] = tmp;
256-
}
257-
pId = edgeMap.get(edge);
249+
pId = edgeLocator.isInsertedEdge(
250+
ids[edgeVerts[0]],
251+
ids[edgeVerts[1]]
252+
)?.value;
258253
}
259254
if (pId === undefined) {
260255
const t =
@@ -285,14 +280,7 @@ function vtkImageMarchingCubes(publicAPI, model) {
285280
}
286281

287282
if (model.mergePoints) {
288-
edge[0] = ids[edgeVerts[0]];
289-
edge[1] = ids[edgeVerts[1]];
290-
if (edge[0] > edge[1]) {
291-
tmp = edge[0];
292-
edge[0] = edge[1];
293-
edge[1] = tmp;
294-
}
295-
edgeMap[edge] = pId;
283+
edgeLocator.insertEdge(ids[edgeVerts[0]], ids[edgeVerts[1]], pId);
296284
}
297285
}
298286
tris.push(pId);

0 commit comments

Comments
 (0)