Skip to content

Commit 6e39ec5

Browse files
dakerfinetjul
authored andcommitted
feat(MergePoint): add vtkMergePoints
1 parent b4c6ec1 commit 6e39ec5

File tree

3 files changed

+336
-0
lines changed

3 files changed

+336
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { Vector3 } from '../../../types';
2+
import vtkPointLocator, {
3+
IInsertPointResult,
4+
IPointLocatorInitialValues,
5+
} from '../PointLocator';
6+
7+
/**
8+
* Initial values for vtkMergePoints.
9+
*/
10+
export interface IMergePointsInitialValues extends IPointLocatorInitialValues {
11+
bucketSize?: number;
12+
}
13+
14+
export interface vtkMergePoints extends vtkPointLocator {
15+
/**
16+
* Check if a point is already inserted in the merge points structure.
17+
*
18+
* @param {Vector3} x The point to check.
19+
* @returns {Number} The ID of the point if it exists, otherwise -1.
20+
*/
21+
isInsertedPoint(x: Vector3): number;
22+
23+
/**
24+
* Insert a point into the merge points structure.
25+
* If the point is already present, it returns the existing ID.
26+
* Otherwise, it inserts the point and returns a new ID.
27+
*
28+
* @param {Vector3} x The point to insert as an array of 3 numbers.
29+
* @returns {IInsertPointResult} An object indicating if the point was inserted and its ID.
30+
*/
31+
insertUniquePoint(x: Vector3): IInsertPointResult;
32+
}
33+
34+
/**
35+
* Method used to decorate a given object (publicAPI+model) with vtkMergePoints characteristics.
36+
*
37+
* @param publicAPI object on which methods will be bounds (public)
38+
* @param model object on which data structure will be bounds (protected)
39+
* @param {IMergePointsInitialValues} [initialValues] (default: {})
40+
*/
41+
export function extend(
42+
publicAPI: object,
43+
model: object,
44+
initialValues?: IMergePointsInitialValues
45+
): void;
46+
47+
/**
48+
* Method used to create a new instance of vtkMergePoints.
49+
* @param {IMergePointsInitialValues} [initialValues] for pre-setting some of its content
50+
*/
51+
export function newInstance(
52+
initialValues?: IMergePointsInitialValues
53+
): vtkMergePoints;
54+
55+
/**
56+
* vtkMergePoints merge exactly coincident points.
57+
*
58+
* vtkMergePoints is a locator object to quickly locate points in 3D.
59+
*/
60+
export declare const vtkMergePoints: {
61+
newInstance: typeof newInstance;
62+
extend: typeof extend;
63+
};
64+
export default vtkMergePoints;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import macro from 'vtk.js/Sources/macros';
2+
import vtkPointLocator from 'vtk.js/Sources/Common/DataModel/PointLocator';
3+
4+
const { vtkErrorMacro } = macro;
5+
6+
/**
7+
* Search for a point in the array using indices from bucketIds
8+
* @param {Number[]} bucketIds - The list of point IDs in the bucket.
9+
* @param {vtkPoints} points - The vtkPoints object containing the points.
10+
* @param {Vector3} x - The point to check.
11+
* @returns {Number} - The ID of the point if it exists, otherwise -1.
12+
*/
13+
function findPointInBucket(bucketIds, points, x) {
14+
const data = points.getData();
15+
for (let i = 0; i < bucketIds.length; ++i) {
16+
const ptId = bucketIds[i];
17+
const idx = ptId * 3;
18+
if (
19+
x[0] === data[idx] &&
20+
x[1] === data[idx + 1] &&
21+
x[2] === data[idx + 2]
22+
) {
23+
return ptId;
24+
}
25+
}
26+
return -1;
27+
}
28+
29+
// ----------------------------------------------------------------------------
30+
// vtkMergePoints methods
31+
// ----------------------------------------------------------------------------
32+
33+
function vtkMergePoints(publicAPI, model) {
34+
// Set our className
35+
model.classHierarchy.push('vtkMergePoints');
36+
37+
/**
38+
* Check if a point is already inserted in the merge points structure.
39+
*
40+
* @param {Vector3} x The point to check.
41+
* @returns {Number} The ID of the point if it exists, otherwise -1.
42+
*/
43+
publicAPI.isInsertedPoint = (x) => {
44+
const idx = publicAPI.getBucketIndex(x);
45+
const bucketIds = model.hashTable.get(idx);
46+
if (bucketIds) {
47+
return findPointInBucket(bucketIds, model.points, x);
48+
}
49+
return -1;
50+
};
51+
52+
/**
53+
* Insert a point into the merge points structure.
54+
* If the point is already present, it returns the existing ID.
55+
* Otherwise, it inserts the point and returns a new ID.
56+
*
57+
* @param {Vector3} x The point to insert as an array of 3 numbers.
58+
* @returns {IInsertPointResult} An object indicating if the point was inserted and its ID.
59+
*/
60+
publicAPI.insertUniquePoint = (x) => {
61+
if (!x || x.length !== 3) {
62+
vtkErrorMacro('Point must be a Vector3.');
63+
return { inserted: false, id: -1 };
64+
}
65+
66+
const idx = publicAPI.getBucketIndex(x);
67+
let bucketIds = model.hashTable.get(idx);
68+
let id = null;
69+
70+
if (bucketIds !== undefined) {
71+
const ptId = findPointInBucket(bucketIds, model.points, x);
72+
if (ptId !== -1) {
73+
id = ptId;
74+
return { inserted: false, id };
75+
}
76+
} else {
77+
bucketIds = [];
78+
model.hashTable.set(idx, bucketIds);
79+
}
80+
81+
// Insert new point
82+
bucketIds.push(model.insertionPointId);
83+
model.points.insertNextPoint(...x);
84+
id = model.insertionPointId++;
85+
return { inserted: true, id };
86+
};
87+
}
88+
89+
// ----------------------------------------------------------------------------
90+
// Object factory
91+
// ----------------------------------------------------------------------------
92+
93+
function defaultValues(initialValues) {
94+
return {
95+
// points: null,
96+
// hashTable: null,
97+
...initialValues,
98+
};
99+
}
100+
101+
// ----------------------------------------------------------------------------
102+
103+
export function extend(publicAPI, model, initialValues = {}) {
104+
vtkPointLocator.extend(publicAPI, model, defaultValues(initialValues));
105+
106+
// Make this a VTK object
107+
macro.obj(publicAPI, model);
108+
109+
// Object specific methods
110+
vtkMergePoints(publicAPI, model);
111+
}
112+
113+
// ----------------------------------------------------------------------------
114+
115+
export const newInstance = macro.newInstance(extend, 'vtkMergePoints');
116+
117+
// ----------------------------------------------------------------------------
118+
119+
export default { newInstance, extend };
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import test from 'tape';
2+
import vtkMergePoints from 'vtk.js/Sources/Common/DataModel/MergePoints';
3+
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
4+
5+
// Test 1: Insert unique point
6+
test('vtkMergePoints: insertUniquePoint', (t) => {
7+
const mergePoints = vtkMergePoints.newInstance();
8+
const output = vtkPolyData.newInstance();
9+
const points = new Float32Array(0);
10+
output.getPoints().setData(points);
11+
mergePoints.setDataSet(output);
12+
mergePoints.setDivisions([10, 10, 10]);
13+
mergePoints.initPointInsertion(output.getPoints(), output.getBounds());
14+
mergePoints.buildLocator();
15+
16+
const p1 = [1.0, 2.0, 3.0]; // First unique point
17+
const res1 = mergePoints.insertUniquePoint(p1);
18+
t.ok(res1.inserted, 'First insertion should be successful');
19+
t.equal(res1.id, 0, 'First point ID should be 0');
20+
21+
const p2 = [1.0, 2.0, 3.0]; // Duplicate point
22+
const res2 = mergePoints.insertUniquePoint(p2);
23+
t.notOk(res2.inserted, 'Second insertion should not insert duplicate');
24+
t.equal(res2.id, 0, 'Should return same ID for duplicate point');
25+
26+
const p3 = [2.0, 3.0, 4.0]; // Different point
27+
const res3 = mergePoints.insertUniquePoint(p3);
28+
t.ok(res3.inserted, 'Different point should insert successfully');
29+
t.equal(res3.id, 1, 'Should return next ID');
30+
31+
const p4 = [5.0, 6.0, 7.0]; // Different point
32+
const res4 = mergePoints.insertUniquePoint(p4);
33+
t.ok(res4.inserted, 'Different point should insert successfully');
34+
t.equal(res4.id, 2, 'Should return next ID');
35+
t.equal(
36+
mergePoints.getPoints().getNumberOfPoints(),
37+
3,
38+
'Three unique points inserted'
39+
);
40+
41+
t.end();
42+
});
43+
44+
// Test 2: isInsertedPoint
45+
test('vtkMergePoints: isInsertedPoint', (t) => {
46+
const mergePoints = vtkMergePoints.newInstance();
47+
const output = vtkPolyData.newInstance();
48+
const points = new Float32Array(0);
49+
output.getPoints().setData(points);
50+
mergePoints.setDataSet(output);
51+
mergePoints.setDivisions([10, 10, 10]);
52+
mergePoints.initPointInsertion(output.getPoints(), output.getBounds());
53+
mergePoints.buildLocator();
54+
55+
t.equal(
56+
mergePoints.isInsertedPoint([0, 0, 0]),
57+
-1,
58+
'Non-inserted point returns -1'
59+
);
60+
61+
const res = mergePoints.insertUniquePoint([4.0, 5.0, 6.0]);
62+
t.equal(
63+
mergePoints.isInsertedPoint([4.0, 5.0, 6.0]),
64+
res.id,
65+
'Should return correct ID for inserted point'
66+
);
67+
68+
t.end();
69+
});
70+
71+
// Test 3: Duplicate insertions
72+
test('vtkMergePoints: Duplicate insertions', (t) => {
73+
const mergePoints = vtkMergePoints.newInstance();
74+
const output = vtkPolyData.newInstance();
75+
const points = new Float32Array(0);
76+
output.getPoints().setData(points);
77+
mergePoints.setDataSet(output);
78+
mergePoints.setDivisions([10, 10, 10]);
79+
mergePoints.initPointInsertion(output.getPoints(), output.getBounds());
80+
mergePoints.buildLocator();
81+
82+
const p1 = [1.0, 2.0, 3.0];
83+
const p2 = [1.0, 2.0, 3.0]; // Duplicate
84+
const p3 = [2.0, 3.0, 4.0]; // Unique
85+
86+
const res1 = mergePoints.insertUniquePoint(p1);
87+
t.ok(res1.inserted, 'First insertion should succeed');
88+
t.equal(res1.id, 0, 'First point ID should be 0');
89+
90+
const res2 = mergePoints.insertUniquePoint(p2);
91+
t.notOk(res2.inserted, 'Duplicate insertion should not insert again');
92+
t.equal(res2.id, res1.id, 'Should return same ID for duplicate point');
93+
94+
const res3 = mergePoints.insertUniquePoint(p3);
95+
t.ok(res3.inserted, 'Unique point should insert successfully');
96+
t.equal(res3.id, 1, 'Should return next ID for unique point');
97+
98+
t.end();
99+
});
100+
101+
// Test 4: Fails cleanly on invalid input
102+
test('vtkMergePoints: Invalid input handling', (t) => {
103+
const mergePoints = vtkMergePoints.newInstance();
104+
const output = vtkPolyData.newInstance();
105+
const points = new Float32Array(0);
106+
output.getPoints().setData(points);
107+
mergePoints.setDataSet(output);
108+
mergePoints.setDivisions([10, 10, 10]);
109+
mergePoints.initPointInsertion(output.getPoints(), output.getBounds());
110+
mergePoints.buildLocator();
111+
112+
const res1 = mergePoints.insertUniquePoint(null);
113+
t.notOk(res1.inserted, 'Should not insert null point');
114+
t.equal(res1.id, -1, 'Should return -1 for null point');
115+
116+
const res2 = mergePoints.insertUniquePoint(undefined);
117+
t.notOk(res2.inserted, 'Should not insert undefined point');
118+
t.equal(res2.id, -1, 'Should return -1 for undefined point');
119+
120+
const res3 = mergePoints.insertUniquePoint([1, 2]); // Invalid point
121+
t.notOk(res3.inserted, 'Should not insert malformed point');
122+
t.equal(res3.id, -1, 'Should return -1 for malformed point');
123+
124+
t.end();
125+
});
126+
127+
// Test 5: Spatial hashing with similar points
128+
test('vtkMergePoints: Bucket hashing', (t) => {
129+
const mergePoints = vtkMergePoints.newInstance();
130+
const output = vtkPolyData.newInstance();
131+
const points = new Float32Array(0);
132+
133+
output.getPoints().setData(points);
134+
mergePoints.setDataSet(output);
135+
mergePoints.setDivisions([10, 10, 10]);
136+
mergePoints.initPointInsertion(output.getPoints(), output.getBounds());
137+
mergePoints.buildLocator();
138+
139+
const p1 = [1.4, 2.6, 3.9];
140+
const p2 = [1.5, 2.7, 3.1];
141+
const p3 = [1.0, 2.0, 4.0];
142+
const p4 = [1.0, 2.0, 4.0];
143+
144+
const id1 = mergePoints.insertUniquePoint(p1).id;
145+
const id2 = mergePoints.insertUniquePoint(p2).id;
146+
const id3 = mergePoints.insertUniquePoint(p3).id;
147+
const id4 = mergePoints.insertUniquePoint(p4).id;
148+
149+
t.notEqual(id1, id2, 'Different point should return different ID');
150+
t.equal(id3, id4, 'Similar points should return same ID');
151+
152+
t.end();
153+
});

0 commit comments

Comments
 (0)