Skip to content

Commit a096407

Browse files
authored
Region: Make Region bounding volume generation more robust (#1395)
* Fix region obb generation * Updates to functions * Remove points * Add some epsilon for boundaries * Remove test from ellipsoid * Add tests, working implementtion * Update tests * Test simplification * Update tests * Tests update * Updates tests * Improve sphere generation * Update region OBB * PURE annotations * lint fixes * Fix test import * Comments update * remove comment
1 parent 4d25285 commit a096407

4 files changed

Lines changed: 463 additions & 163 deletions

File tree

example/three/ellipsoid.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ const params = {
2828

2929
displaySphereHelper: false,
3030
displayBoxHelper: false,
31-
displayPoints: false,
3231

3332
};
3433

src/three/renderer/math/EllipsoidRegion.js

Lines changed: 184 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,35 @@
1-
import { MathUtils, Matrix4 } from 'three';
2-
import { Vector3 } from 'three';
1+
import { Matrix4, Vector3, Box3 } from 'three';
32
import { Ellipsoid } from './Ellipsoid.js';
43

4+
// bounds are lightly inflated to account for floating point error
5+
const INFLATE_EPSILON = 1e-13;
56
const PI = Math.PI;
67
const HALF_PI = PI / 2;
78

8-
const _orthoX = new Vector3();
9-
const _orthoY = new Vector3();
10-
const _orthoZ = new Vector3();
11-
const _invMatrix = new Matrix4();
9+
const _orthoX = /* @__PURE__*/ new Vector3();
10+
const _orthoY = /* @__PURE__*/ new Vector3();
11+
const _orthoZ = /* @__PURE__*/ new Vector3();
12+
const _vec = /* @__PURE__*/ new Vector3();
13+
const _invMatrix = /* @__PURE__*/ new Matrix4();
14+
const _box = /* @__PURE__*/ new Box3();
15+
const _matrix = /* @__PURE__*/ new Matrix4();
1216

13-
let _poolIndex = 0;
14-
const _pointsPool = [];
15-
function getVector( usePool = false ) {
17+
function expandSphereRadiusSquared( vec, target ) {
1618

17-
if ( ! usePool ) {
18-
19-
return new Vector3();
20-
21-
}
22-
23-
if ( ! _pointsPool[ _poolIndex ] ) {
24-
25-
_pointsPool[ _poolIndex ] = new Vector3();
26-
27-
}
28-
29-
_poolIndex ++;
30-
return _pointsPool[ _poolIndex - 1 ];
19+
target.radius = Math.max( target.radius, vec.distanceToSquared( target.center ) );
3120

3221
}
3322

34-
function resetPool() {
23+
function isTriaxial( radii ) {
3524

36-
_poolIndex = 0;
25+
return radii.x !== radii.y;
3726

3827
}
3928

4029
export class EllipsoidRegion extends Ellipsoid {
4130

4231
constructor(
43-
x, y, z,
32+
x = 1, y = 1, z = 1,
4433
latStart = - HALF_PI, latEnd = HALF_PI,
4534
lonStart = 0, lonEnd = 2 * PI,
4635
heightStart = 0, heightEnd = 0
@@ -56,122 +45,224 @@ export class EllipsoidRegion extends Ellipsoid {
5645

5746
}
5847

59-
_getPoints( usePool = false ) {
48+
getBoundingBox( box, matrix ) {
49+
50+
if ( isTriaxial( this.radius ) ) {
51+
52+
console.warn( 'EllipsoidRegion: Triaxial ellipsoids are not supported.' );
53+
54+
}
6055

6156
const {
6257
latStart, latEnd,
6358
lonStart, lonEnd,
6459
heightStart, heightEnd,
6560
} = this;
6661

67-
const midLat = MathUtils.mapLinear( 0.5, 0, 1, latStart, latEnd );
68-
const midLon = MathUtils.mapLinear( 0.5, 0, 1, lonStart, lonEnd );
62+
const latMid = ( latStart + latEnd ) * 0.5;
63+
const lonMid = ( lonStart + lonEnd ) * 0.5;
64+
const allAboveEquator = latStart > 0.0;
65+
const allBelowEquator = latEnd < 0.0;
6966

70-
const lonOffset = Math.floor( lonStart / HALF_PI ) * HALF_PI;
71-
const latlon = [
72-
[ - PI / 2, 0 ],
73-
[ PI / 2, 0 ],
74-
[ 0, lonOffset ],
75-
[ 0, lonOffset + PI / 2 ],
76-
[ 0, lonOffset + PI ],
77-
[ 0, lonOffset + 3 * PI / 2 ],
67+
let nearEquatorLat;
68+
if ( allAboveEquator ) {
7869

79-
[ latStart, lonEnd ],
80-
[ latEnd, lonEnd ],
81-
[ latStart, lonStart ],
82-
[ latEnd, lonStart ],
70+
nearEquatorLat = latStart;
8371

84-
[ 0, lonStart ],
85-
[ 0, lonEnd ],
72+
} else if ( allBelowEquator ) {
8673

87-
[ midLat, midLon ],
88-
[ latStart, midLon ],
89-
[ latEnd, midLon ],
90-
[ midLat, lonStart ],
91-
[ midLat, lonEnd ],
74+
nearEquatorLat = latEnd;
9275

93-
];
76+
} else {
9477

95-
const target = [];
96-
const total = latlon.length;
78+
nearEquatorLat = 0;
9779

98-
for ( let z = 0; z <= 1; z ++ ) {
80+
}
9981

100-
const height = MathUtils.mapLinear( z, 0, 1, heightStart, heightEnd );
101-
for ( let i = 0, l = total; i < l; i ++ ) {
82+
// measure the extents
83+
const { min, max } = box;
84+
min.setScalar( Infinity );
85+
max.setScalar( - Infinity );
86+
if ( lonEnd - lonStart <= PI ) {
87+
88+
// extract the axes
89+
this.getCartographicToNormal( latMid, lonMid, _orthoZ );
90+
_orthoY.set( 0, 0, 1 );
91+
_orthoX.crossVectors( _orthoY, _orthoZ ).normalize();
92+
_orthoY.crossVectors( _orthoZ, _orthoX ).normalize();
93+
94+
// construct the frame
95+
matrix.makeBasis( _orthoX, _orthoY, _orthoZ );
96+
_invMatrix.copy( matrix ).invert();
97+
98+
// extract x
99+
// check the most bowing point near the equator relative to the frame
100+
this.getCartographicToPosition( nearEquatorLat, lonStart, heightEnd, _vec ).applyMatrix4( _invMatrix );
101+
max.x = Math.abs( _vec.x );
102+
min.x = - max.x;
103+
104+
// extract y
105+
// check corners and mid points for the top
106+
this.getCartographicToPosition( latEnd, lonStart, heightEnd, _vec ).applyMatrix4( _invMatrix );
107+
max.y = _vec.y;
108+
109+
this.getCartographicToPosition( latEnd, lonMid, heightEnd, _vec ).applyMatrix4( _invMatrix );
110+
max.y = Math.max( _vec.y, max.y );
111+
112+
// check corners and mid points for the bottom
113+
this.getCartographicToPosition( latStart, lonStart, heightEnd, _vec ).applyMatrix4( _invMatrix );
114+
min.y = _vec.y;
115+
116+
this.getCartographicToPosition( latStart, lonMid, heightEnd, _vec ).applyMatrix4( _invMatrix );
117+
min.y = Math.min( _vec.y, min.y );
118+
119+
// extract z
120+
// check center point
121+
this.getCartographicToPosition( latMid, lonMid, heightEnd, _vec ).applyMatrix4( _invMatrix );
122+
max.z = _vec.z;
123+
124+
// check top and bottom reverse points
125+
this.getCartographicToPosition( latStart, lonStart, heightStart, _vec ).applyMatrix4( _invMatrix );
126+
min.z = _vec.z;
127+
128+
this.getCartographicToPosition( latEnd, lonStart, heightStart, _vec ).applyMatrix4( _invMatrix );
129+
min.z = Math.min( _vec.z, min.z );
130+
131+
} else {
102132

103-
const [ lat, lon ] = latlon[ i ];
104-
if ( lat >= latStart && lat <= latEnd && lon >= lonStart && lon <= lonEnd ) {
133+
// extract a vector towards the middle of the region
134+
this.getCartographicToPosition( nearEquatorLat, lonMid, heightEnd, _orthoZ );
135+
_orthoZ.z = 0;
136+
if ( _orthoZ.length() < 1e-10 ) {
105137

106-
const v = getVector( usePool );
107-
target.push( v );
108-
this.getCartographicToPosition( lat, lon, height, v );
138+
_orthoZ.set( 1, 0, 0 );
109139

110-
}
140+
} else {
141+
142+
_orthoZ.normalize();
111143

112144
}
113145

146+
_orthoY.set( 0, 0, 1 );
147+
_orthoX.crossVectors( _orthoZ, _orthoY ).normalize();
148+
149+
// construct the OBB frame
150+
matrix.makeBasis( _orthoX, _orthoY, _orthoZ );
151+
_invMatrix.copy( matrix ).invert();
152+
153+
// x extents
154+
// find the furthest point rotated 90 degrees from the center of the region
155+
this.getCartographicToPosition( nearEquatorLat, lonMid + HALF_PI, heightEnd, _vec ).applyMatrix4( _invMatrix );
156+
max.x = Math.abs( _vec.x );
157+
min.x = - max.x;
158+
159+
// y extents
160+
// measure the top of the region, accounting for the diagonal tilt of the edge
161+
this.getCartographicToPosition( latEnd, 0, allBelowEquator ? heightStart : heightEnd, _vec ).applyMatrix4( _invMatrix );
162+
max.y = _vec.y;
163+
164+
// measure the bottom of the region, accounting for the diagonal tilt of the edge
165+
this.getCartographicToPosition( latStart, 0, allAboveEquator ? heightStart : heightEnd, _vec ).applyMatrix4( _invMatrix );
166+
min.y = _vec.y;
167+
168+
// z extends
169+
// measure the furthest point at the center of the region
170+
this.getCartographicToPosition( nearEquatorLat, lonMid, heightEnd, _vec ).applyMatrix4( _invMatrix );
171+
max.z = _vec.z;
172+
173+
// measure the opposite end, which is guaranteed to be at the furthest extents since this lon region extents is > PI
174+
this.getCartographicToPosition( nearEquatorLat, lonEnd, heightEnd, _vec ).applyMatrix4( _invMatrix );
175+
min.z = _vec.z;
176+
114177
}
115178

116-
return target;
179+
// center the frame
180+
box.getCenter( _vec );
181+
box.min.sub( _vec ).multiplyScalar( 1 + INFLATE_EPSILON );
182+
box.max.sub( _vec ).multiplyScalar( 1 + INFLATE_EPSILON );
183+
184+
_vec.applyMatrix4( matrix );
185+
matrix.setPosition( _vec );
117186

118187
}
119188

120-
getBoundingBox( box, matrix ) {
189+
getBoundingSphere( sphere ) {
190+
191+
if ( isTriaxial( this.radius ) ) {
192+
193+
console.warn( 'EllipsoidRegion: Triaxial ellipsoids are not supported.' );
121194

122-
resetPool();
195+
}
196+
197+
// TODO: this could be optimized or the OBB could be generated at the same time since
198+
// a lot of the the points are reused
199+
200+
// use the OBB function to get a reasonable center
201+
this.getBoundingBox( _box, _matrix );
202+
sphere.center.setFromMatrixPosition( _matrix );
203+
sphere.radius = 0;
123204

124205
const {
125206
latStart, latEnd,
126207
lonStart, lonEnd,
208+
heightStart, heightEnd,
127209
} = this;
128210

129-
const latRange = latEnd - latStart;
130-
if ( latRange < PI / 2 ) {
211+
const latMid = ( latStart + latEnd ) * 0.5;
212+
const lonMid = ( lonStart + lonEnd ) * 0.5;
213+
const allAboveEquator = latStart > 0.0;
214+
const allBelowEquator = latEnd < 0.0;
131215

132-
// get the midway point for the region
133-
const midLat = MathUtils.mapLinear( 0.5, 0, 1, latStart, latEnd );
134-
const midLon = MathUtils.mapLinear( 0.5, 0, 1, lonStart, lonEnd );
216+
let nearEquatorLat;
217+
if ( allAboveEquator ) {
135218

136-
// get the frame matrix for the box - works well for smaller regions
137-
this.getCartographicToNormal( midLat, midLon, _orthoZ );
138-
_orthoY.set( 0, 0, 1 );
139-
_orthoX.crossVectors( _orthoY, _orthoZ );
140-
_orthoY.crossVectors( _orthoX, _orthoZ );
141-
matrix.makeBasis( _orthoX, _orthoY, _orthoZ );
219+
nearEquatorLat = latStart;
220+
221+
} else if ( allBelowEquator ) {
222+
223+
nearEquatorLat = latEnd;
142224

143225
} else {
144226

145-
_orthoX.set( 1, 0, 0 );
146-
_orthoY.set( 0, 1, 0 );
147-
_orthoZ.set( 0, 0, 1 );
148-
matrix.makeBasis( _orthoX, _orthoY, _orthoZ );
227+
nearEquatorLat = 0;
149228

150229
}
151230

152-
// transform the points into the local frame
153-
_invMatrix.copy( matrix ).invert();
231+
// lon start extremity
232+
this.getCartographicToPosition( nearEquatorLat, lonStart, heightEnd, _vec );
233+
expandSphereRadiusSquared( _vec, sphere );
154234

155-
const points = this._getPoints( true );
156-
for ( let i = 0, l = points.length; i < l; i ++ ) {
235+
// check corners and mid points for the top
236+
this.getCartographicToPosition( latEnd, lonStart, heightEnd, _vec );
237+
expandSphereRadiusSquared( _vec, sphere );
157238

158-
points[ i ].applyMatrix4( _invMatrix );
239+
this.getCartographicToPosition( latEnd, lonMid, heightEnd, _vec );
240+
expandSphereRadiusSquared( _vec, sphere );
159241

160-
}
242+
// check corners and mid points for the bottom
243+
this.getCartographicToPosition( latStart, lonStart, heightEnd, _vec );
244+
expandSphereRadiusSquared( _vec, sphere );
161245

162-
// init the box
163-
box.makeEmpty();
164-
box.setFromPoints( points );
246+
this.getCartographicToPosition( latStart, lonMid, heightEnd, _vec );
247+
expandSphereRadiusSquared( _vec, sphere );
165248

166-
}
249+
// check center extremity
250+
this.getCartographicToPosition( latMid, lonMid, heightEnd, _vec );
251+
expandSphereRadiusSquared( _vec, sphere );
167252

168-
getBoundingSphere( sphere, center ) {
253+
// check lower height extremity
254+
this.getCartographicToPosition( latStart, lonStart, heightStart, _vec );
255+
expandSphereRadiusSquared( _vec, sphere );
169256

170-
resetPool();
257+
// check 90 degree offset if range is larger than PI
258+
if ( lonEnd - lonStart > PI ) {
259+
260+
this.getCartographicToPosition( nearEquatorLat, lonMid + PI, heightEnd, _vec );
261+
expandSphereRadiusSquared( _vec, sphere );
262+
263+
}
171264

172-
const points = this._getPoints( true );
173-
sphere.makeEmpty();
174-
sphere.setFromPoints( points, center );
265+
sphere.radius = Math.sqrt( sphere.radius ) * ( 1 + INFLATE_EPSILON );
175266

176267
}
177268

0 commit comments

Comments
 (0)