@@ -119,7 +119,11 @@ export function setMaxPoint(bounds, x, y, z) {
119
119
return xMax !== x || yMax !== y || zMax !== z ;
120
120
}
121
121
122
- export function inflate ( bounds , delta ) {
122
+ function inflate ( bounds , delta ) {
123
+ if ( delta == null ) {
124
+ // eslint-disable-next-line no-use-before-define
125
+ return minInflate ( bounds ) ;
126
+ }
123
127
bounds [ 0 ] -= delta ;
124
128
bounds [ 1 ] += delta ;
125
129
bounds [ 2 ] -= delta ;
@@ -129,6 +133,35 @@ export function inflate(bounds, delta) {
129
133
return bounds ;
130
134
}
131
135
136
+ function minInflate ( bounds ) {
137
+ const nonZero = [ 0 , 0 , 0 ] ;
138
+ let maxIdx = - 1 ;
139
+ let max = 0.0 ;
140
+ let w = 0.0 ;
141
+ for ( let i = 0 ; i < 3 ; ++ i ) {
142
+ w = bounds [ i * 2 + 1 ] - bounds [ i * 2 ] ;
143
+ if ( w > max ) {
144
+ max = w ;
145
+ maxIdx = i ;
146
+ }
147
+ nonZero [ i ] = w > 0.0 ? 1 : 0 ;
148
+ }
149
+
150
+ if ( maxIdx < 0 ) {
151
+ return inflate ( bounds , 0.5 ) ;
152
+ }
153
+
154
+ // Any zero width sides are bumped out 1% of max side
155
+ for ( let i = 0 ; i < 3 ; ++ i ) {
156
+ if ( ! nonZero [ i ] ) {
157
+ const d = 0.005 * max ;
158
+ bounds [ i * 2 ] -= d ;
159
+ bounds [ i * 2 + 1 ] += d ;
160
+ }
161
+ }
162
+ return bounds ;
163
+ }
164
+
132
165
export function scale ( bounds , sx , sy , sz ) {
133
166
if ( ! isValid ( bounds ) ) {
134
167
return false ;
@@ -616,6 +649,157 @@ export function cutWithPlane(bounds, origin, normal) {
616
649
return true ;
617
650
}
618
651
652
+ /**
653
+ * Clamp the divisions to ensure the total number doesn't exceed targetBins
654
+ * @param {Number } targetBins - Maximum number of bins allowed
655
+ * @param {Array } divs - Divisions array to adjust [divX, divY, divZ]
656
+ */
657
+ export function clampDivisions ( targetBins , divs ) {
658
+ for ( let i = 0 ; i < 3 ; ++ i ) {
659
+ divs [ i ] = divs [ i ] < 1 ? 1 : divs [ i ] ;
660
+ }
661
+
662
+ let numBins = divs [ 0 ] * divs [ 1 ] * divs [ 2 ] ;
663
+ while ( numBins > targetBins ) {
664
+ for ( let i = 0 ; i < 3 ; ++ i ) {
665
+ divs [ i ] = divs [ i ] > 1 ? divs [ i ] - 1 : 1 ;
666
+ }
667
+ numBins = divs [ 0 ] * divs [ 1 ] * divs [ 2 ] ;
668
+ }
669
+ }
670
+
671
+ /**
672
+ * Compute the number of divisions given the current bounding box and a
673
+ * target number of buckets/bins. Handles degenerate bounding boxes properly.
674
+ * @param {Bounds } bounds - The bounding box
675
+ * @param {Number } totalBins - Target number of bins
676
+ * @param {Array } divs - Output array to store divisions [divX, divY, divZ]
677
+ * @param {Array } [adjustedBounds] - Output array to store adjusted bounds if needed
678
+ * @returns {Number } The actual total number of bins
679
+ */
680
+ export function computeDivisions ( bounds , totalBins , divs , adjustedBounds = [ ] ) {
681
+ // This will always produce at least one bin
682
+ // eslint-disable-next-line no-param-reassign
683
+ totalBins = totalBins <= 0 ? 1 : totalBins ;
684
+
685
+ // First determine the maximum length of the side of the bounds. Keep track
686
+ // of zero width sides of the bounding box.
687
+ let numNonZero = 0 ;
688
+ const nonZero = [ 0 , 0 , 0 ] ;
689
+ let maxIdx = - 1 ;
690
+ let max = 0.0 ;
691
+ const lengths = getLengths ( bounds ) ;
692
+
693
+ // Use a finite tolerance when detecting zero width sides
694
+ const totLen = lengths [ 0 ] + lengths [ 1 ] + lengths [ 2 ] ;
695
+ const zeroDetectionTolerance = totLen * ( 0.001 / 3.0 ) ;
696
+
697
+ for ( let i = 0 ; i < 3 ; ++ i ) {
698
+ if ( lengths [ i ] > max ) {
699
+ maxIdx = i ;
700
+ max = lengths [ i ] ;
701
+ }
702
+ if ( lengths [ i ] > zeroDetectionTolerance ) {
703
+ nonZero [ i ] = 1 ;
704
+ numNonZero ++ ;
705
+ } else {
706
+ nonZero [ i ] = 0 ;
707
+ }
708
+ }
709
+
710
+ // Get min and max points
711
+ const minPoint = getMinPoint ( bounds ) ;
712
+ const maxPoint = getMaxPoint ( bounds ) ;
713
+
714
+ // If the bounding box is degenerate, then one bin of arbitrary size
715
+ if ( numNonZero < 1 ) {
716
+ divs [ 0 ] = 1 ;
717
+ divs [ 1 ] = 1 ;
718
+ divs [ 2 ] = 1 ;
719
+ adjustedBounds [ 0 ] = minPoint [ 0 ] - 0.5 ;
720
+ adjustedBounds [ 1 ] = maxPoint [ 0 ] + 0.5 ;
721
+ adjustedBounds [ 2 ] = minPoint [ 1 ] - 0.5 ;
722
+ adjustedBounds [ 3 ] = maxPoint [ 1 ] + 0.5 ;
723
+ adjustedBounds [ 4 ] = minPoint [ 2 ] - 0.5 ;
724
+ adjustedBounds [ 5 ] = maxPoint [ 2 ] + 0.5 ;
725
+ return 1 ;
726
+ }
727
+
728
+ // Compute the divisions roughly in proportion to the bounding box edge lengths
729
+ let f = totalBins ;
730
+ f /= nonZero [ 0 ] ? lengths [ 0 ] / totLen : 1.0 ;
731
+ f /= nonZero [ 1 ] ? lengths [ 1 ] / totLen : 1.0 ;
732
+ f /= nonZero [ 2 ] ? lengths [ 2 ] / totLen : 1.0 ;
733
+ f **= 1.0 / numNonZero ;
734
+
735
+ for ( let i = 0 ; i < 3 ; ++ i ) {
736
+ divs [ i ] = nonZero [ i ] ? Math . floor ( ( f * lengths [ i ] ) / totLen ) : 1 ;
737
+ divs [ i ] = divs [ i ] < 1 ? 1 : divs [ i ] ;
738
+ }
739
+
740
+ // Make sure that we do not exceed the totalBins
741
+ clampDivisions ( totalBins , divs ) ;
742
+
743
+ // Now compute the final bounds, making sure it is a non-zero volume
744
+ const delta = ( 0.5 * lengths [ maxIdx ] ) / divs [ maxIdx ] ;
745
+ for ( let i = 0 ; i < 3 ; ++ i ) {
746
+ if ( nonZero [ i ] ) {
747
+ adjustedBounds [ 2 * i ] = minPoint [ i ] ;
748
+ adjustedBounds [ 2 * i + 1 ] = maxPoint [ i ] ;
749
+ } else {
750
+ adjustedBounds [ 2 * i ] = minPoint [ i ] - delta ;
751
+ adjustedBounds [ 2 * i + 1 ] = maxPoint [ i ] + delta ;
752
+ }
753
+ }
754
+
755
+ return divs [ 0 ] * divs [ 1 ] * divs [ 2 ] ;
756
+ }
757
+
758
+ /**
759
+ * Calculate the squared distance from point x to the specified bounds.
760
+ * @param {Vector3 } x The point coordinates
761
+ * @param {Bounds } bounds The bounding box coordinates
762
+ * @returns {Number } The squared distance to the bounds
763
+ */
764
+ export function distance2ToBounds ( x , bounds ) {
765
+ // Are we within the bounds?
766
+ if (
767
+ x [ 0 ] >= bounds [ 0 ] &&
768
+ x [ 0 ] <= bounds [ 1 ] &&
769
+ x [ 1 ] >= bounds [ 2 ] &&
770
+ x [ 1 ] <= bounds [ 3 ] &&
771
+ x [ 2 ] >= bounds [ 4 ] &&
772
+ x [ 2 ] <= bounds [ 5 ]
773
+ ) {
774
+ return 0.0 ;
775
+ }
776
+
777
+ const deltas = [ 0.0 , 0.0 , 0.0 ] ;
778
+
779
+ // dx
780
+ if ( x [ 0 ] < bounds [ 0 ] ) {
781
+ deltas [ 0 ] = bounds [ 0 ] - x [ 0 ] ;
782
+ } else if ( x [ 0 ] > bounds [ 1 ] ) {
783
+ deltas [ 0 ] = x [ 0 ] - bounds [ 1 ] ;
784
+ }
785
+
786
+ // dy
787
+ if ( x [ 1 ] < bounds [ 2 ] ) {
788
+ deltas [ 1 ] = bounds [ 2 ] - x [ 1 ] ;
789
+ } else if ( x [ 1 ] > bounds [ 3 ] ) {
790
+ deltas [ 1 ] = x [ 1 ] - bounds [ 3 ] ;
791
+ }
792
+
793
+ // dz
794
+ if ( x [ 2 ] < bounds [ 4 ] ) {
795
+ deltas [ 2 ] = bounds [ 4 ] - x [ 2 ] ;
796
+ } else if ( x [ 2 ] > bounds [ 5 ] ) {
797
+ deltas [ 2 ] = x [ 2 ] - bounds [ 5 ] ;
798
+ }
799
+
800
+ return vtkMath . dot ( deltas , deltas ) ;
801
+ }
802
+
619
803
// ----------------------------------------------------------------------------
620
804
// Light Weight class
621
805
// ----------------------------------------------------------------------------
@@ -763,6 +947,14 @@ class BoundingBox {
763
947
contains ( otherBounds ) {
764
948
return intersects ( this . bounds , otherBounds ) ;
765
949
}
950
+
951
+ computeDivisions ( totalBins , divs , adjustedBounds = [ ] ) {
952
+ return computeDivisions ( this . bounds , totalBins , divs , adjustedBounds ) ;
953
+ }
954
+
955
+ distance2ToBounds ( x ) {
956
+ return distance2ToBounds ( x , this . bounds ) ;
957
+ }
766
958
}
767
959
768
960
function newInstance ( initialValues ) {
@@ -809,6 +1001,9 @@ export const STATIC = {
809
1001
intersects,
810
1002
containsPoint,
811
1003
contains,
1004
+ computeDivisions,
1005
+ clampDivisions,
1006
+ distance2ToBounds,
812
1007
INIT_BOUNDS ,
813
1008
} ;
814
1009
0 commit comments