@kitware/vtk.js 34.9.1 → 34.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,751 @@
1
+ import { m as macro } from '../../macros2.js';
2
+ import vtkCellArray from '../Core/CellArray.js';
3
+ import { f as vtkMath } from '../Core/Math/index.js';
4
+ import vtkPoints from '../Core/Points.js';
5
+ import vtkAbstractPointLocator from './AbstractPointLocator.js';
6
+ import vtkBoundingBox from './BoundingBox.js';
7
+
8
+ const {
9
+ vtkErrorMacro
10
+ } = macro;
11
+
12
+ // ----------------------------------------------------------------------------
13
+ // vtkPointLocator methods
14
+ // ----------------------------------------------------------------------------
15
+
16
+ function vtkPointLocator(publicAPI, model) {
17
+ // Set our className
18
+ model.classHierarchy.push('vtkPointLocator');
19
+
20
+ /**
21
+ * Calculate the squared distance from point x to the bucket "nei".
22
+ * @param {Vector3} x The point coordinates
23
+ * @param {Vector3} nei The bucket coordinates
24
+ * @returns {Number} The squared distance to the bucket
25
+ */
26
+ function distance2ToBucket(x, nei) {
27
+ // Compute bucket bounds
28
+ const bounds = [nei[0] * model.HX + model.BX, (nei[0] + 1) * model.HX + model.BX, nei[1] * model.HY + model.BY, (nei[1] + 1) * model.HY + model.BY, nei[2] * model.HZ + model.BZ, (nei[2] + 1) * model.HZ + model.BZ];
29
+ return vtkBoundingBox.distance2ToBounds(x, bounds);
30
+ }
31
+
32
+ /**
33
+ * Get the neighboring buckets for a given bucket index.
34
+ * @param {Number[]} ijk The bucket index
35
+ * @param {Number[]} ndivs The number of divisions in each dimension
36
+ * @param {Number} level The level of neighbors to retrieve
37
+ * @returns An array of neighboring bucket indices
38
+ */
39
+ function getBucketNeighbors(ijk, ndivs, level) {
40
+ const buckets = [];
41
+ if (level === 0) {
42
+ buckets.push([...ijk]);
43
+ return buckets;
44
+ }
45
+ const minLevel = [];
46
+ const maxLevel = [];
47
+ for (let i = 0; i < 3; i++) {
48
+ const min = ijk[i] - level;
49
+ const max = ijk[i] + level;
50
+ minLevel[i] = min > 0 ? min : 0;
51
+ maxLevel[i] = max < ndivs[i] - 1 ? max : ndivs[i] - 1;
52
+ }
53
+ for (let i = minLevel[0]; i <= maxLevel[0]; i++) {
54
+ for (let j = minLevel[1]; j <= maxLevel[1]; j++) {
55
+ for (let k = minLevel[2]; k <= maxLevel[2]; k++) {
56
+ if (i === ijk[0] + level || i === ijk[0] - level || j === ijk[1] + level || j === ijk[1] - level || k === ijk[2] + level || k === ijk[2] - level) {
57
+ buckets.push([i, j, k]);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ return buckets;
63
+ }
64
+
65
+ /**
66
+ * Calculate the overlapping buckets for a given point and distance.
67
+ * @param {Vector3} x The point coordinates
68
+ * @param {*} ijk The bucket index
69
+ * @param {*} dist The search distance
70
+ * @param {*} level The level of detail
71
+ * @returns An array of overlapping bucket indices
72
+ */
73
+ function getOverlappingBuckets(x, ijk, dist, level) {
74
+ const buckets = [];
75
+ const xBounds = [x[0], x[0], x[1], x[1], x[2], x[2]];
76
+ const bbox = vtkBoundingBox.newInstance();
77
+ bbox.setBounds(xBounds);
78
+ bbox.inflate(dist);
79
+ const ijkBounds = [ijk[0], ijk[0], ijk[1], ijk[1], ijk[2], ijk[2]];
80
+ const ijkBox = vtkBoundingBox.newInstance();
81
+ ijkBox.setBounds(ijkBounds);
82
+ ijkBox.inflate(level);
83
+ const minLevel = publicAPI.getBucketIndices([bbox.getBounds()[0], bbox.getBounds()[2], bbox.getBounds()[4]]);
84
+ const maxLevel = publicAPI.getBucketIndices([bbox.getBounds()[1], bbox.getBounds()[3], bbox.getBounds()[5]]);
85
+
86
+ // Iterate through potential buckets
87
+ for (let i = minLevel[0]; i <= maxLevel[0]; i++) {
88
+ for (let j = minLevel[1]; j <= maxLevel[1]; j++) {
89
+ for (let k = minLevel[2]; k <= maxLevel[2]; k++) {
90
+ if (!ijkBox.containsPoint(i, j, k)) {
91
+ buckets.push([i, j, k]);
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return buckets;
97
+ }
98
+
99
+ /**
100
+ * Get the bucket index for a given bucket coordinate.
101
+ * @param {Number[]} ijk The bucket index
102
+ * @returns The bucket index
103
+ */
104
+ function getBucketIndex(ijk) {
105
+ return ijk[0] + ijk[1] * model.XD + ijk[2] * model.sliceSize;
106
+ }
107
+
108
+ /**
109
+ * Get the bucket indices for a given point.
110
+ * @param {Vector3} point The point coordinates
111
+ * @returns The bucket indices
112
+ */
113
+ publicAPI.getBucketIndices = point => {
114
+ const ix = Math.floor((point[0] - model.BX) * model.FX);
115
+ const iy = Math.floor((point[1] - model.BY) * model.FY);
116
+ const iz = Math.floor((point[2] - model.BZ) * model.FZ);
117
+ const ijk = [];
118
+ ijk[0] = Math.max(0, Math.min(ix, model.XD - 1));
119
+ ijk[1] = Math.max(0, Math.min(iy, model.YD - 1));
120
+ ijk[2] = Math.max(0, Math.min(iz, model.ZD - 1));
121
+ return ijk;
122
+ };
123
+
124
+ /**
125
+ * Get the bucket index for a given point.
126
+ * @param {Vector3} point The point coordinates
127
+ * @returns The bucket index
128
+ */
129
+ publicAPI.getBucketIndex = point => {
130
+ const ijk = publicAPI.getBucketIndices(point);
131
+ return getBucketIndex(ijk);
132
+ };
133
+
134
+ /**
135
+ * Build the locator from the input dataset.
136
+ */
137
+ publicAPI.buildLocator = () => {
138
+ model.level = 1;
139
+ const bounds = model.dataSet.getBounds();
140
+ const numPts = model.dataSet.getNumberOfPoints();
141
+ let numBuckets = Math.ceil(numPts / model.numberOfPointsPerBucket);
142
+ const ndivs = [0, 0, 0];
143
+ const bbox = vtkBoundingBox.newInstance();
144
+ bbox.setBounds(bounds);
145
+ if (model.automatic) {
146
+ bbox.computeDivisions(numBuckets, ndivs, model.bounds);
147
+ } else {
148
+ model.bounds = bbox.inflate();
149
+ for (let i = 0; i < 3; i++) {
150
+ ndivs[i] = Math.max(1, model.divisions[i]);
151
+ }
152
+ }
153
+ model.divisions = ndivs;
154
+ numBuckets = ndivs[0] * ndivs[1] * ndivs[2];
155
+ model.numberOfBuckets = numBuckets;
156
+
157
+ // Compute width of bucket in three directions
158
+ for (let i = 0; i < 3; ++i) {
159
+ model.H[i] = (model.bounds[2 * i + 1] - model.bounds[2 * i]) / ndivs[i];
160
+ }
161
+ model.hashTable.clear();
162
+ publicAPI.computePerformanceFactors();
163
+ for (let i = 0; i < numPts; ++i) {
164
+ const pt = model.dataSet.getPoints().getPoint(i);
165
+ const key = publicAPI.getBucketIndex(pt);
166
+ if (!model.hashTable.has(key)) {
167
+ model.hashTable.set(key, []); // Initialize bucket if it doesn't exist
168
+ }
169
+
170
+ const bucket = model.hashTable.get(key);
171
+ bucket.push(i);
172
+ }
173
+ };
174
+ publicAPI.initialize = () => {
175
+ model.points = null;
176
+ publicAPI.freeSearchStructure();
177
+ };
178
+
179
+ /**
180
+ * Initialize point insertion.
181
+ * @param {*} points The points to insert
182
+ * @param {Bounds} bounds The bounds for the points
183
+ * @param {Number} estNumPts Estimated number of points for insertion
184
+ */
185
+ publicAPI.initPointInsertion = function (points, bounds) {
186
+ let estNumPts = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
187
+ if (points == null) {
188
+ vtkErrorMacro('A valid vtkPoints object is required for point insertion');
189
+ return false;
190
+ }
191
+ if (!bounds || bounds.length !== 6) {
192
+ vtkErrorMacro('A valid bounds array of length 6 is required');
193
+ return false;
194
+ }
195
+ if (!points) {
196
+ vtkErrorMacro('A valid vtkPoints is required for point insertion');
197
+ return false;
198
+ }
199
+ publicAPI.freeSearchStructure();
200
+ model.insertionPointId = 0;
201
+ model.points = points;
202
+ model.points.setNumberOfComponents(3);
203
+ model.points.initialize();
204
+ let numBuckets = 0;
205
+ const ndivs = [0, 0, 0];
206
+ const bbox = vtkBoundingBox.newInstance();
207
+ bbox.setBounds(bounds);
208
+ if (model.automatic && estNumPts > 0) {
209
+ numBuckets = Math.ceil(estNumPts / model.numberOfPointsPerBucket);
210
+ bbox.computeDivisions(numBuckets, ndivs, model.bounds);
211
+ } else {
212
+ model.bounds = bbox.inflate();
213
+ for (let i = 0; i < 3; i++) {
214
+ ndivs[i] = Math.max(1, model.divisions[i]);
215
+ }
216
+ }
217
+ model.divisions = ndivs;
218
+ numBuckets = ndivs[0] * ndivs[1] * ndivs[2];
219
+ model.numberOfBuckets = numBuckets;
220
+
221
+ // Compute width of bucket in three directions
222
+ for (let i = 0; i < 3; ++i) {
223
+ model.H[i] = (model.bounds[2 * i + 1] - model.bounds[2 * i]) / ndivs[i];
224
+ }
225
+ model.insertionTol2 = model.tolerance * model.tolerance;
226
+ let maxDivs = 0;
227
+ let hmin = Number.MAX_VALUE;
228
+ for (let i = 0; i < 3; i++) {
229
+ hmin = model.H[i] < hmin ? model.H[i] : hmin;
230
+ maxDivs = maxDivs > model.divisions[i] ? maxDivs : model.divisions[i];
231
+ }
232
+ model.insertionLevel = Math.ceil(model.tolerance / hmin);
233
+ model.insertionLevel = model.insertionLevel > maxDivs ? maxDivs : model.insertionLevel;
234
+ publicAPI.computePerformanceFactors();
235
+ return true;
236
+ };
237
+
238
+ /**
239
+ * Insert a point into the point locator.
240
+ * If the point is already present, it returns the existing ID.
241
+ * Otherwise, it inserts the point and returns a new ID.
242
+ *
243
+ * @param {Number} ptId The index of the point to insert.
244
+ * @param {Vector3} x The point to insert.
245
+ * @returns {IInsertPointResult} An object indicating if the point was inserted and its ID.
246
+ */
247
+ publicAPI.insertPoint = (ptId, x) => {
248
+ const key = publicAPI.getBucketIndex(x);
249
+ if (!model.hashTable.has(key)) {
250
+ model.hashTable.set(key, []);
251
+ }
252
+ const bucket = model.hashTable.get(key);
253
+ bucket.push(ptId);
254
+ model.points.insertPoint(ptId, x);
255
+ return {
256
+ inserted: true,
257
+ id: ptId
258
+ };
259
+ };
260
+
261
+ /**
262
+ * Insert a point into the point locator.
263
+ * If the point is already present, it returns the existing ID.
264
+ * Otherwise, it inserts the point and returns a new ID.
265
+ *
266
+ * @param {Vector3} x The point to insert.
267
+ * @returns {IInsertPointResult} An object indicating if the point was inserted and its ID.
268
+ */
269
+ publicAPI.insertNextPoint = x => {
270
+ const key = publicAPI.getBucketIndex(x);
271
+ if (!model.hashTable.has(key)) {
272
+ model.hashTable.set(key, []);
273
+ }
274
+ const bucket = model.hashTable.get(key);
275
+ bucket.push(model.insertionPointId);
276
+ model.points.insertPoint(model.insertionPointId, x);
277
+ return {
278
+ inserted: true,
279
+ id: model.insertionPointId++
280
+ };
281
+ };
282
+
283
+ /**
284
+ * Insert a point into the point locator.
285
+ * If the point is already present, it returns the existing ID.
286
+ * Otherwise, it inserts the point and returns a new ID.
287
+ *
288
+ * @param {Vector3} x The point to insert.
289
+ * @returns {IInsertPointResult} An object indicating if the point was inserted and its ID.
290
+ */
291
+ publicAPI.insertUniquePoint = x => {
292
+ const ptId = publicAPI.isInsertedPoint(x);
293
+ if (ptId > -1) {
294
+ // Point already exists
295
+ return {
296
+ inserted: false,
297
+ id: ptId
298
+ };
299
+ }
300
+ // Insert new point
301
+ const ret = publicAPI.insertNextPoint(x);
302
+ return ret;
303
+ };
304
+
305
+ /**
306
+ * Check if a point is already inserted in the point locator.
307
+ *
308
+ * @param {Vector3} x The point to check.
309
+ * @returns {Number} The ID of the point if it exists, otherwise -1.
310
+ */
311
+ publicAPI.isInsertedPoint = x => {
312
+ const ijk = publicAPI.getBucketIndices(x);
313
+
314
+ // The number and level of neighbors to search depends upon the tolerance and the bucket width.
315
+ // Here, we use InsertionLevel (default to 1 if not set)
316
+ const insertionLevel = model.insertionLevel ?? 1;
317
+ const numDivs = model.divisions;
318
+ for (let lvtk = 0; lvtk <= insertionLevel; lvtk++) {
319
+ const buckets = getBucketNeighbors(ijk, numDivs, lvtk);
320
+ for (let i = 0; i < buckets.length; i++) {
321
+ const nei = buckets[i];
322
+ const key = getBucketIndex(nei);
323
+ const bucket = model.hashTable.get(key);
324
+ if (bucket) {
325
+ for (let j = 0; j < bucket.length; j++) {
326
+ const ptId = bucket[j];
327
+ const pt = model.points.getPoint(ptId);
328
+ if (vtkMath.distance2BetweenPoints(x, pt) <= model.insertionTol2) {
329
+ return ptId;
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+ return -1;
336
+ };
337
+
338
+ /**
339
+ * Find the closest point to a given point.
340
+ *
341
+ * @param {Vector3} x The point coordinates
342
+ * @returns The id of the closest point or -1 if not found
343
+ */
344
+ publicAPI.findClosestPoint = x => {
345
+ publicAPI.buildLocator();
346
+ const ijk = publicAPI.getBucketIndices(x);
347
+ const numDivs = model.divisions;
348
+ let minDist2 = Number.MAX_VALUE;
349
+ let closest = -1;
350
+ const maxLevel = Math.max(...numDivs);
351
+ for (let level = 0; level < maxLevel && closest === -1; level++) {
352
+ const neighbors = getBucketNeighbors(ijk, numDivs, level);
353
+ for (let n = 0; n < neighbors.length; n++) {
354
+ const key = getBucketIndex(neighbors[n]);
355
+ const bucket = model.hashTable.get(key);
356
+ if (bucket) {
357
+ for (let b = 0; b < bucket.length; b++) {
358
+ const ptId = bucket[b];
359
+ const pt = model.dataSet.getPoints().getPoint(ptId);
360
+ const dist2 = vtkMath.distance2BetweenPoints(x, pt);
361
+ if (dist2 < minDist2) {
362
+ minDist2 = dist2;
363
+ closest = ptId;
364
+ }
365
+ }
366
+ }
367
+ }
368
+ }
369
+ return closest;
370
+ };
371
+
372
+ /**
373
+ * Find the closest point within a specified radius.
374
+ *
375
+ * @param {Number} radius The search radius
376
+ * @param {Vector3} x The point coordinates
377
+ * @param {Number} inputDataLength The length of the input data
378
+ * @returns {IFindClosestPointResult} The closest point result
379
+ */
380
+ publicAPI.findClosestPointWithinRadius = function (radius, x) {
381
+ let inputDataLength = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
382
+ publicAPI.buildLocator();
383
+ let closest = -1;
384
+ const radius2 = radius * radius;
385
+ let minDist2 = 1.01 * radius2;
386
+ let dist2 = -1.0;
387
+ const ijk = publicAPI.getBucketIndices(x);
388
+ const key = getBucketIndex(ijk);
389
+ const bucket = model.hashTable.get(key);
390
+ if (bucket) {
391
+ for (let j = 0; j < bucket.length; j++) {
392
+ const ptId = bucket[j];
393
+ const pt = model.dataSet.getPoints().getPoint(ptId);
394
+ const d2 = vtkMath.distance2BetweenPoints(x, pt);
395
+ if (d2 < minDist2) {
396
+ closest = ptId;
397
+ minDist2 = d2;
398
+ dist2 = d2;
399
+ }
400
+ }
401
+ }
402
+
403
+ // Now, search only those buckets that are within a radius.
404
+ let refinedRadius;
405
+ let refinedRadius2;
406
+ if (minDist2 < radius2) {
407
+ refinedRadius = Math.sqrt(dist2);
408
+ refinedRadius2 = dist2;
409
+ } else {
410
+ refinedRadius = radius;
411
+ refinedRadius2 = radius2;
412
+ }
413
+
414
+ // Optionally restrict radius by inputDataLength and bounds
415
+ if (inputDataLength !== 0.0) {
416
+ const distance2ToDataBounds = vtkBoundingBox.distance2ToBounds(x, model.bounds);
417
+ const maxDistance = Math.sqrt(distance2ToDataBounds) + inputDataLength;
418
+ if (refinedRadius > maxDistance) {
419
+ refinedRadius = maxDistance;
420
+ refinedRadius2 = maxDistance * maxDistance;
421
+ }
422
+ }
423
+
424
+ // Compute radius levels for each dimension
425
+ const radiusLevels = [0, 0, 0];
426
+ for (let i = 0; i < 3; i++) {
427
+ radiusLevels[i] = Math.floor(refinedRadius / model.H[i]);
428
+ if (radiusLevels[i] > model.divisions[i] / 2) {
429
+ radiusLevels[i] = Math.floor(model.divisions[i] / 2);
430
+ }
431
+ }
432
+ let radiusLevel = Math.max(...radiusLevels);
433
+ if (radiusLevel === 0) {
434
+ radiusLevel = 1;
435
+ }
436
+ for (let ii = radiusLevel; ii >= 1; ii--) {
437
+ const currentRadius = refinedRadius;
438
+
439
+ // Build up a list of buckets that are arranged in rings
440
+ const buckets = getOverlappingBuckets(x, ijk, refinedRadius / ii, ii);
441
+ for (let i = 0; i < buckets.length; i++) {
442
+ const nei = buckets[i];
443
+ const d2ToBucket = distance2ToBucket(x, nei);
444
+ if (d2ToBucket < refinedRadius2) {
445
+ const key1 = getBucketIndex(nei);
446
+ const bucket1 = model.hashTable.get(key1);
447
+ if (bucket1) {
448
+ for (let j = 0; j < bucket1.length; j++) {
449
+ const ptId = bucket1[j];
450
+ const pt = model.dataSet.getPoints().getPoint(ptId);
451
+ if (pt) {
452
+ const d2 = vtkMath.distance2BetweenPoints(x, pt);
453
+ if (d2 < minDist2) {
454
+ closest = ptId;
455
+ minDist2 = d2;
456
+ refinedRadius = Math.sqrt(minDist2);
457
+ refinedRadius2 = minDist2;
458
+ dist2 = d2;
459
+ }
460
+ }
461
+ }
462
+ }
463
+ }
464
+ }
465
+
466
+ // Update ii according to refined radius
467
+ if (refinedRadius < currentRadius && ii > 2) {
468
+ ii = Math.floor(ii * (refinedRadius / currentRadius)) + 1;
469
+ if (ii < 2) {
470
+ ii = 2;
471
+ }
472
+ }
473
+ }
474
+ if (closest !== -1 && minDist2 <= radius * radius) {
475
+ dist2 = minDist2;
476
+ } else {
477
+ closest = -1;
478
+ }
479
+ return {
480
+ id: closest,
481
+ dist2
482
+ };
483
+ };
484
+
485
+ /**
486
+ * Find the closest inserted point to the given coordinates.
487
+ * @param {Vector3} x The query point
488
+ * @returns {Number} The id of the closest inserted point or -1 if not found
489
+ */
490
+ publicAPI.findClosestInsertedPoint = x => {
491
+ // Check if point is within bounds
492
+ for (let i = 0; i < 3; i++) {
493
+ if (x[i] < model.bounds[2 * i] || x[i] > model.bounds[2 * i + 1]) {
494
+ return -1;
495
+ }
496
+ }
497
+ const ijk = publicAPI.getBucketIndices(x);
498
+ const numDivs = model.divisions;
499
+ let closest = -1;
500
+ let minDist2 = Number.MAX_VALUE;
501
+ let level = 0;
502
+ const maxLevel = Math.max(numDivs[0], numDivs[1], numDivs[2]);
503
+ const points = model.points;
504
+
505
+ // Search buckets and neighbors until closest found
506
+ for (; closest === -1 && level < maxLevel; level++) {
507
+ const neighbors = getBucketNeighbors(ijk, numDivs, level);
508
+ for (let i = 0; i < neighbors.length; i++) {
509
+ const nei = neighbors[i];
510
+ const cno = nei[0] + nei[1] * model.XD + nei[2] * model.sliceSize;
511
+ const bucket = model.hashTable.get(cno);
512
+ if (bucket) {
513
+ for (let j = 0; j < bucket.length; j++) {
514
+ const ptId = bucket[j];
515
+ const pt = points.getPoint(ptId);
516
+ const dist2 = vtkMath.distance2BetweenPoints(x, pt);
517
+ if (dist2 < minDist2) {
518
+ closest = ptId;
519
+ minDist2 = dist2;
520
+ }
521
+ }
522
+ }
523
+ }
524
+ }
525
+
526
+ // Refine: search next level neighbors that could be closer
527
+ const refineNeighbors = getBucketNeighbors(ijk, numDivs, level);
528
+ for (let i = 0; i < refineNeighbors.length; i++) {
529
+ const nei = refineNeighbors[i];
530
+ // Only consider neighbors that could possibly be closer
531
+ let dist2 = 0;
532
+ for (let j = 0; j < 3; j++) {
533
+ if (ijk[j] !== nei[j]) {
534
+ const MULTIPLES = ijk[j] > nei[j] ? nei[j] + 1 : nei[j];
535
+ const diff = model.bounds[2 * j] + MULTIPLES * model.H[j] - x[j];
536
+ dist2 += diff * diff;
537
+ }
538
+ }
539
+ if (dist2 < minDist2) {
540
+ const cno = nei[0] + nei[1] * model.XD + nei[2] * model.sliceSize;
541
+ const bucket = model.hashTable.get(cno);
542
+ if (bucket) {
543
+ for (let j = 0; j < bucket.length; j++) {
544
+ const ptId = bucket[j];
545
+ const pt = points.getPoint(ptId);
546
+ const d2 = vtkMath.distance2BetweenPoints(x, pt);
547
+ if (d2 < minDist2) {
548
+ closest = ptId;
549
+ minDist2 = d2;
550
+ }
551
+ }
552
+ }
553
+ }
554
+ }
555
+ return closest;
556
+ };
557
+
558
+ /**
559
+ * Get the points in the specified bucket.
560
+ * @param {*} x The point coordinates
561
+ * @returns {Number[]} The points in the bucket
562
+ */
563
+ publicAPI.getPointsInBucket = x => {
564
+ publicAPI.buildLocator();
565
+ const key = publicAPI.getBucketIndex(x);
566
+ const bucket = model.hashTable.get(key);
567
+ if (!bucket) return []; // No points in this bucket
568
+ return bucket;
569
+ };
570
+
571
+ /**
572
+ * Free the search structure and reset the locator.
573
+ */
574
+ publicAPI.freeSearchStructure = () => {
575
+ model.hashTable.clear();
576
+ model.points = vtkPoints.newInstance();
577
+ model.divisions = [50, 50, 50];
578
+ vtkMath.uninitializeBounds(model.bounds);
579
+ };
580
+
581
+ /**
582
+ * Compute performance factors based on the current model state.
583
+ */
584
+ publicAPI.computePerformanceFactors = () => {
585
+ model.HX = model.H[0];
586
+ model.HY = model.H[1];
587
+ model.HZ = model.H[2];
588
+ model.FX = 1.0 / model.H[0];
589
+ model.FY = 1.0 / model.H[1];
590
+ model.FZ = 1.0 / model.H[2];
591
+ model.BX = model.bounds[0];
592
+ model.BY = model.bounds[2];
593
+ model.BZ = model.bounds[4];
594
+ model.XD = model.divisions[0];
595
+ model.YD = model.divisions[1];
596
+ model.ZD = model.divisions[2];
597
+ model.sliceSize = model.divisions[0] * model.divisions[1];
598
+ };
599
+
600
+ /**
601
+ * Generate a polydata representation of the point locator.
602
+ *
603
+ * @param {vtkPolyData} polydata The polydata to generate representation for
604
+ */
605
+ publicAPI.generateRepresentation = polydata => {
606
+ if (!model.hashTable || model.hashTable.size === 0) {
607
+ vtkErrorMacro("Can't build representation, no data provided!");
608
+ return;
609
+ }
610
+ const facePts = [];
611
+ facePts.length = 4;
612
+
613
+ // Helper to add a face to polydata
614
+ function generateFace(face, i, j, k, pts, polys) {
615
+ // Compute the 8 corners of the bucket
616
+ const x0 = model.bounds[0] + i * model.HX;
617
+ const y0 = model.bounds[2] + j * model.HY;
618
+ const z0 = model.bounds[4] + k * model.HZ;
619
+ const x1 = x0 + model.HX;
620
+ const y1 = y0 + model.HY;
621
+ const z1 = z0 + model.HZ;
622
+
623
+ // Each face is defined by 4 points (quad)
624
+ // axis: 0=x, 1=y, 2=z
625
+ if (face === 0) {
626
+ // yz plane
627
+ facePts[0] = [x0, y0, z0];
628
+ facePts[1] = [x0, y1, z0];
629
+ facePts[2] = [x0, y1, z1];
630
+ facePts[3] = [x0, y0, z1];
631
+ } else if (face === 1) {
632
+ // xz plane
633
+ facePts[0] = [x0, y0, z0];
634
+ facePts[1] = [x1, y0, z0];
635
+ facePts[2] = [x1, y0, z1];
636
+ facePts[3] = [x0, y0, z1];
637
+ } else if (face === 2) {
638
+ // xy plane
639
+ facePts[0] = [x0, y0, z0];
640
+ facePts[1] = [x1, y0, z0];
641
+ facePts[2] = [x1, y1, z0];
642
+ facePts[3] = [x0, y1, z0];
643
+ }
644
+
645
+ // Add points to pts and get their ids
646
+ const ptIds = facePts.map(pt => pts.insertNextPoint(...pt));
647
+ // Add quad to polys
648
+ polys.insertNextCell([ptIds[0], ptIds[1], ptIds[2], ptIds[3]]);
649
+ }
650
+
651
+ // Prepare points and polys
652
+ const pts = vtkPoints.newInstance();
653
+ pts.allocate(5000);
654
+ const polys = vtkCellArray.newInstance();
655
+ polys.allocate(2048);
656
+ // We'll use a Set to avoid duplicate faces
657
+ const divisions = model.divisions;
658
+ const sliceSize = divisions[0] * divisions[1];
659
+
660
+ // Helper to check if a bucket exists
661
+ function hasBucket(i, j, k) {
662
+ if (i < 0 || i >= divisions[0] || j < 0 || j >= divisions[1] || k < 0 || k >= divisions[2]) {
663
+ return false;
664
+ }
665
+ const idx = i + j * divisions[0] + k * sliceSize;
666
+ return model.hashTable.has(idx);
667
+ }
668
+
669
+ // Loop over all buckets, creating appropriate faces
670
+ for (let k = 0; k < divisions[2]; k++) {
671
+ for (let j = 0; j < divisions[1]; j++) {
672
+ for (let i = 0; i < divisions[0]; i++) {
673
+ const idx = i + j * divisions[0] + k * sliceSize;
674
+ const inside = model.hashTable.has(idx);
675
+
676
+ // For each axis (0=x, 1=y, 2=z)
677
+ for (let axis = 0; axis < 3; axis++) {
678
+ let ni = i;
679
+ let nj = j;
680
+ let nk = k;
681
+ if (axis === 0) ni = i - 1;
682
+ if (axis === 1) nj = j - 1;
683
+ if (axis === 2) nk = k - 1;
684
+ const neighborInside = hasBucket(ni, nj, nk);
685
+
686
+ // If neighbor is out of bounds
687
+ if (ni < 0 || nj < 0 || nk < 0) {
688
+ if (inside) {
689
+ generateFace(axis, i, j, k, pts, polys);
690
+ }
691
+ } else if (neighborInside && !inside || !neighborInside && inside) {
692
+ generateFace(axis, i, j, k, pts, polys);
693
+ }
694
+ }
695
+
696
+ // Positive boundary faces
697
+ if (i + 1 >= divisions[0] && inside) {
698
+ generateFace(0, i + 1, j, k, pts, polys);
699
+ }
700
+ if (j + 1 >= divisions[1] && inside) {
701
+ generateFace(1, i, j + 1, k, pts, polys);
702
+ }
703
+ if (k + 1 >= divisions[2] && inside) {
704
+ generateFace(2, i, j, k + 1, pts, polys);
705
+ }
706
+ }
707
+ }
708
+ }
709
+ polydata.setPoints(pts);
710
+ polydata.setPolys(polys);
711
+ // polydata.squeeze();
712
+ };
713
+ }
714
+
715
+ // ----------------------------------------------------------------------------
716
+ // Object factory
717
+ // ----------------------------------------------------------------------------
718
+
719
+ function defaultValues(initialValues) {
720
+ return {
721
+ divisions: [50, 50, 50],
722
+ numberOfPointsPerBucket: 3,
723
+ bounds: [0, 0, 0, 0, 0, 0],
724
+ tolerance: 0.001,
725
+ automatic: true,
726
+ ...initialValues
727
+ };
728
+ }
729
+ function extend(publicAPI, model) {
730
+ let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
731
+ vtkAbstractPointLocator.extend(publicAPI, model, defaultValues(initialValues));
732
+ macro.setGet(publicAPI, model, ['numberOfPointsPerBucket', 'points']);
733
+ macro.setGetArray(publicAPI, model, ['divisions'], 3);
734
+ vtkMath.uninitializeBounds(model.bounds);
735
+ model.points = model.points || vtkPoints.newInstance();
736
+ model.hashTable = new Map();
737
+ model.H = [0, 0, 0]; // Bucket sizes in three dimensions
738
+ model.insertionPointId = 0; // ID for next point to be inserted
739
+ model.insertionTol2 = 0.0001; // Tolerance squared for point insertion
740
+ model.insertionLevel = 0; // Level of neighbors to search for insertion
741
+ vtkPointLocator(publicAPI, model);
742
+ }
743
+ const newInstance = macro.newInstance(extend, 'vtkPointLocator');
744
+
745
+ // ----------------------------------------------------------------------------
746
+ var vtkPointLocator$1 = {
747
+ newInstance,
748
+ extend
749
+ };
750
+
751
+ export { vtkPointLocator$1 as default, extend, newInstance };