QuickHull3.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  1. import { Vertex } from './Vertex';
  2. import { VertexList } from './VertexList';
  3. import { Face } from './Face';
  4. import { Vector3 } from '../Vector3';
  5. import { Line3 } from '../Line3';
  6. import { Plane } from '../Plane';
  7. import { Visible, NonConvex, Deleted } from '../../constants';
  8. import { MergeNonConvexLargerFace, MergeNonConvex } from '../../constants';
  9. /**
  10. * @author Mugen87 / https://github.com/Mugen87
  11. *
  12. */
  13. function QuickHull3() {
  14. this.tolerance = - 1;
  15. this.faces = [];
  16. this.newFaces = [];
  17. this.assigned = new VertexList();
  18. this.unassigned = new VertexList();
  19. this.vertices = []; // vertices of the hull (internal representation of given points)
  20. }
  21. Object.assign( QuickHull3.prototype, {
  22. setFromPoints: function ( points ) {
  23. if ( Array.isArray( points ) !== true ) {
  24. console.error( 'THREE.QuickHull3: Points parameter is not an array.' );
  25. }
  26. if ( points.length < 4 ) {
  27. console.error( 'THREE.QuickHull3: The algorithm needs at least four points.' );
  28. }
  29. this.makeEmpty();
  30. for ( var i = 0, l = points.length; i < l; i ++ ) {
  31. this.vertices.push( new Vertex( points[ i ] ) );
  32. }
  33. this.compute();
  34. return this;
  35. },
  36. setFromObject: function ( object ) {
  37. var scope = this;
  38. this.makeEmpty();
  39. object.updateMatrixWorld( true );
  40. object.traverse( function ( node ) {
  41. var i, l, point;
  42. var geometry = node.geometry;
  43. if ( geometry !== undefined ) {
  44. if ( geometry.isGeometry ) {
  45. var vertices = geometry.vertices;
  46. for ( i = 0, l = vertices.length; i < l; i ++ ) {
  47. point = vertices[ i ].clone();
  48. point.applyMatrix4( node.matrixWorld );
  49. scope.vertices.push( new Vertex( point ) );
  50. }
  51. } else if ( geometry.isBufferGeometry ) {
  52. var attribute = geometry.attributes.position;
  53. if ( attribute !== undefined ) {
  54. for ( i = 0, l = attribute.count; i < l; i ++ ) {
  55. point = new Vector3();
  56. point.fromBufferAttribute( attribute, i ).applyMatrix4( node.matrixWorld );
  57. scope.vertices.push( new Vertex( point ) );
  58. }
  59. }
  60. }
  61. }
  62. } );
  63. this.compute();
  64. return this;
  65. },
  66. makeEmpty: function () {
  67. this.faces = [];
  68. this.vertices = [];
  69. return this;
  70. },
  71. // Adds a 'vertex' to the 'assigned' list of vertices and assigns it to the given face.
  72. addVertexToFace: function ( vertex, face ) {
  73. vertex.face = face;
  74. if ( face.outside === null ) {
  75. this.assigned.append( vertex );
  76. } else {
  77. this.assigned.insertBefore( face.outside, vertex );
  78. }
  79. face.outside = vertex;
  80. return this;
  81. },
  82. // Removes 'vertex' for the 'assigned' list of vertices.
  83. // It also makes sure that the link from 'face' to the first vertex it sees in 'assigned' is linked correctly after the removal
  84. removeVertexFromFace: function ( vertex, face ) {
  85. if ( vertex === face.outside ) {
  86. // fix face.outside link
  87. if ( vertex.next !== null && vertex.next.face === face ) {
  88. // face has at least 2 outside vertices, move the 'outside' reference
  89. face.outside = vertex.next;
  90. } else {
  91. // vertex was the only outside vertex that face had
  92. face.outside = null;
  93. }
  94. }
  95. this.assigned.remove( vertex );
  96. },
  97. // Removes all the visible vertices that 'face' is able to see which are stored in the 'assigned' vertext list
  98. removeAllVerticesFromFace: function ( face ) {
  99. if ( face.outside !== null ) {
  100. // reference to the first and last vertex of this face
  101. var start = face.outside;
  102. var end = face.outside;
  103. while ( end.next !== null && end.next.face === face ) {
  104. end = end.next;
  105. }
  106. this.assigned.removeSubList( start, end );
  107. // fix references
  108. start.prev = end.next = null;
  109. face.outside = null;
  110. return start;
  111. }
  112. },
  113. // Removes all the visible vertices that 'face' is able to see
  114. // If 'absorbingFace' doesn't exist then all the removed vertices will be added to the 'unassigned' vertex list.
  115. // If 'absorbingFace' exists then this method will assign all the vertices of 'face' that can see 'absorbingFace',
  116. // if a vertex cannot see 'absorbingFace' it's added to the 'unassigned' vertex list.
  117. deleteFaceVertices: function ( face, absorbingFace ) {
  118. var faceVertices = this.removeAllVerticesFromFace( face );
  119. if ( faceVertices !== undefined ) {
  120. if ( absorbingFace === undefined ) {
  121. // mark the vertices to be reassigned to some other face
  122. this.unassigned.appendChain( faceVertices );
  123. } else {
  124. // if there's an absorbing face try to assign as many vertices as possible to it
  125. var vertex = faceVertices;
  126. do {
  127. // we need to buffer the subsequent vertex at this point because the 'vertex.next' reference
  128. // will be changed by upcoming method calls
  129. var nextVertex = vertex.next;
  130. var distance = absorbingFace.distanceToPoint( vertex.point );
  131. // check if 'vertex' is able to see 'absorbingFace'
  132. if ( distance > this.tolerance ) {
  133. this.addVertexToFace( vertex, absorbingFace );
  134. } else {
  135. this.unassigned.append( vertex );
  136. }
  137. // now assign next vertex
  138. vertex = nextVertex;
  139. } while ( vertex !== null );
  140. }
  141. }
  142. },
  143. // Reassigns as many vertices as possible from the unassigned list to the new faces
  144. resolveUnassignedPoints: function ( newFaces ) {
  145. if ( this.unassigned.isEmpty() === false ) {
  146. var vertex = this.unassigned.first();
  147. do {
  148. // buffer 'next' reference, see .deleteFaceVertices()
  149. var nextVertex = vertex.next;
  150. var maxDistance = this.tolerance;
  151. var maxFace = null;
  152. for ( var i = 0; i < newFaces.length; i ++ ) {
  153. var face = newFaces[ i ];
  154. if ( face.mark === Visible ) {
  155. var distance = face.distanceToPoint( vertex.point );
  156. if ( distance > maxDistance ) {
  157. maxDistance = distance;
  158. maxFace = face;
  159. }
  160. if ( maxDistance > 1000 * this.tolerance ) break;
  161. }
  162. }
  163. // 'maxFace' can be null e.g. if there are identical vertices
  164. if ( maxFace !== null ) {
  165. this.addVertexToFace( vertex, maxFace );
  166. }
  167. vertex = nextVertex;
  168. } while ( vertex !== null );
  169. }
  170. },
  171. // Computes the extremes of a tetrahedron which will be the initial hull
  172. computeExtremes: function () {
  173. var min = new Vector3();
  174. var max = new Vector3();
  175. var minVertices = [];
  176. var maxVertices = [];
  177. var i, l, j;
  178. // initially assume that the first vertex is the min/max
  179. for ( i = 0; i < 3; i ++ ) {
  180. minVertices[ i ] = maxVertices[ i ] = this.vertices[ 0 ];
  181. }
  182. min.copy( this.vertices[ 0 ].point );
  183. max.copy( this.vertices[ 0 ].point );
  184. // compute the min/max vertex on all six directions
  185. for ( i = 0, l = this.vertices.length; i < l ; i ++ ) {
  186. var vertex = this.vertices[ i ];
  187. var point = vertex.point;
  188. // update the min coordinates
  189. for ( j = 0; j < 3; j ++ ) {
  190. if ( point.getComponent( j ) < min.getComponent( j ) ) {
  191. min.setComponent( j, point.getComponent( j ) );
  192. minVertices[ j ] = vertex;
  193. }
  194. }
  195. // update the max coordinates
  196. for ( j = 0; j < 3; j ++ ) {
  197. if ( point.getComponent( j ) > max.getComponent( j ) ) {
  198. max.setComponent( j, point.getComponent( j ) );
  199. maxVertices[ j ] = vertex;
  200. }
  201. }
  202. }
  203. // use min/max vectors to compute epsilon
  204. this.tolerance = 3 * Number.EPSILON * (
  205. Math.max( Math.abs( min.x ), Math.abs( max.x ) ) +
  206. Math.max( Math.abs( min.y ), Math.abs( max.y ) ) +
  207. Math.max( Math.abs( min.z ), Math.abs( max.z ) )
  208. );
  209. return { min: minVertices, max: maxVertices };
  210. },
  211. // Computes the initial tetrahedron assigning to its faces all the points that are candidates to form part of the hull
  212. computeInitialHull: function () {
  213. var line3, plane, closestPoint;
  214. return function computeInitialHull () {
  215. if ( line3 === undefined ) {
  216. line3 = new Line3();
  217. plane = new Plane();
  218. closestPoint = new Vector3();
  219. }
  220. var vertex, vertices = this.vertices;
  221. var extremes = this.computeExtremes();
  222. var min = extremes.min;
  223. var max = extremes.max;
  224. var v0, v1, v2, v3;
  225. var i, l, j;
  226. // 1. Find the two vertices 'v0' and 'v1' with the greatest 1d separation
  227. // (max.x - min.x)
  228. // (max.y - min.y)
  229. // (max.z - min.z)
  230. var distance, maxDistance = 0;
  231. var index = 0;
  232. for ( i = 0; i < 3; i ++ ) {
  233. distance = max[ i ].point.getComponent( i ) - min[ i ].point.getComponent( i );
  234. if ( distance > maxDistance ) {
  235. maxDistance = distance;
  236. index = i;
  237. }
  238. }
  239. v0 = min[ index ];
  240. v1 = max[ index ];
  241. // 2. The next vertex 'v2' is the one farthest to the line formed by 'v0' and 'v1'
  242. maxDistance = 0;
  243. line3.set( v0.point, v1.point );
  244. for ( i = 0, l = this.vertices.length; i < l; i ++ ) {
  245. vertex = vertices[ i ];
  246. if ( vertex !== v0 && vertex !== v1 ) {
  247. line3.closestPointToPoint( vertex.point, true, closestPoint );
  248. distance = closestPoint.distanceToSquared( vertex.point );
  249. if ( distance > maxDistance ) {
  250. maxDistance = distance;
  251. v2 = vertex;
  252. }
  253. }
  254. }
  255. // 3. The next vertex 'v3' is the one farthest to the plane 'v0', 'v1', 'v2'
  256. maxDistance = 0;
  257. plane.setFromCoplanarPoints( v0.point, v1.point, v2.point );
  258. for ( i = 0, l = this.vertices.length; i < l; i ++ ) {
  259. vertex = vertices[ i ];
  260. if ( vertex !== v0 && vertex !== v1 && vertex !== v2 ) {
  261. distance = Math.abs( plane.distanceToPoint( vertex.point ) );
  262. if ( distance > maxDistance ) {
  263. maxDistance = distance;
  264. v3 = vertex;
  265. }
  266. }
  267. }
  268. var faces = [];
  269. if ( plane.distanceToPoint( v3.point ) < 0 ) {
  270. // the face is not able to see the point so 'plane.normal' is pointing outside the tetrahedron
  271. faces.push(
  272. Face.create( v0, v1, v2 ),
  273. Face.create( v3, v1, v0 ),
  274. Face.create( v3, v2, v1 ),
  275. Face.create( v3, v0, v2 )
  276. );
  277. // set the twin edge
  278. for ( i = 0; i < 3; i ++ ) {
  279. j = ( i + 1 ) % 3;
  280. // join face[ i ] i > 0, with the first face
  281. faces[ i + 1 ].getEdge( 2 ).setTwin( faces[ 0 ].getEdge( j ) );
  282. // join face[ i ] with face[ i + 1 ], 1 <= i <= 3
  283. faces[ i + 1 ].getEdge( 1 ).setTwin( faces[ j + 1 ].getEdge( 0 ) );
  284. }
  285. } else {
  286. // the face is able to see the point so 'plane.normal' is pointing inside the tetrahedron
  287. faces.push(
  288. Face.create( v0, v2, v1 ),
  289. Face.create( v3, v0, v1 ),
  290. Face.create( v3, v1, v2 ),
  291. Face.create( v3, v2, v0 )
  292. );
  293. // set the twin edge
  294. for ( i = 0; i < 3; i ++ ) {
  295. j = ( i + 1 ) % 3;
  296. // join face[ i ] i > 0, with the first face
  297. faces[ i + 1 ].getEdge( 2 ).setTwin( faces[ 0 ].getEdge( ( 3 - i ) % 3 ) );
  298. // join face[ i ] with face[ i + 1 ]
  299. faces[ i + 1 ].getEdge( 0 ).setTwin( faces[ j + 1 ].getEdge( 1 ) );
  300. }
  301. }
  302. // the initial hull is the tetrahedron
  303. for ( i = 0; i < 4; i ++ ) {
  304. this.faces.push( faces[ i ] );
  305. }
  306. // initial assignment of vertices to the faces of the tetrahedron
  307. for ( i = 0, l = vertices.length; i < l; i ++ ) {
  308. vertex = vertices[i];
  309. if ( vertex !== v0 && vertex !== v1 && vertex !== v2 && vertex !== v3 ) {
  310. maxDistance = this.tolerance;
  311. var maxFace = null;
  312. for ( j = 0; j < 4; j ++ ) {
  313. distance = this.faces[ j ].distanceToPoint( vertex.point );
  314. if ( distance > maxDistance ) {
  315. maxDistance = distance;
  316. maxFace = this.faces[ j ];
  317. }
  318. }
  319. if ( maxFace !== null ) {
  320. this.addVertexToFace( vertex, maxFace );
  321. }
  322. }
  323. }
  324. };
  325. }(),
  326. // Removes inactive faces
  327. reindexFaces: function () {
  328. var activeFaces = [];
  329. for ( var i = 0; i < this.faces.length; i ++ ) {
  330. var face = this.faces[ i ];
  331. if ( face.mark === Visible ) {
  332. activeFaces.push( face );
  333. }
  334. }
  335. this.faces = activeFaces;
  336. },
  337. // Finds the next vertex to make faces with the current hull
  338. nextVertexToAdd: function () {
  339. // if the 'assigned' list of vertices is empty, no vertices are left. return with 'undefined'
  340. if ( this.assigned.isEmpty() === false ) {
  341. var eyeVertex, maxDistance = 0;
  342. // grap the first available face and start with the first visible vertex of that face
  343. var eyeFace = this.assigned.first().face;
  344. var vertex = eyeFace.outside;
  345. // now calculate the farthest vertex that face can see
  346. do {
  347. var distance = eyeFace.distanceToPoint( vertex.point );
  348. if ( distance > maxDistance ) {
  349. maxDistance = distance;
  350. eyeVertex = vertex;
  351. }
  352. vertex = vertex.next;
  353. } while ( vertex !== null && vertex.face === eyeFace );
  354. return eyeVertex;
  355. }
  356. },
  357. // Computes a chain of half edges in ccw order called the 'horizon'.
  358. // For an edge to be part of the horizon it must join a face that can see
  359. // 'eyePoint' and a face that cannot see 'eyePoint'.
  360. //
  361. // - eyePoint: The coordinates of a point
  362. // - crossEdge: The edge used to jump to the current 'face'
  363. // - face: The current face being tested
  364. // - horizon: The edges that form part of the horizon in ccw order
  365. computeHorizon: function ( eyePoint, crossEdge, face, horizon ) {
  366. // moves face's vertices to the 'unassigned' vertex list
  367. this.deleteFaceVertices( face );
  368. face.mark = Deleted;
  369. var edge;
  370. if ( crossEdge === null ) {
  371. edge = crossEdge = face.getEdge( 0 );
  372. } else {
  373. // start from the next edge since 'crossEdge' was already analyzed
  374. // (actually 'crossEdge.twin' was the edge who called this method recursively)
  375. edge = crossEdge.next;
  376. }
  377. do {
  378. var twinEdge = edge.twin;
  379. var oppositeFace = twinEdge.face;
  380. if ( oppositeFace.mark === Visible ) {
  381. if ( oppositeFace.distanceToPoint( eyePoint ) > this.tolerance ) {
  382. // the opposite face can see the vertex, so proceed with next edge
  383. this.computeHorizon( eyePoint, twinEdge, oppositeFace, horizon );
  384. } else {
  385. // the opposite face can't see the vertex, so this edge is part of the horizon
  386. horizon.push( edge );
  387. }
  388. edge = edge.next;
  389. }
  390. } while ( edge !== crossEdge );
  391. },
  392. // Creates a face with the vertices 'eyeVertex.point', 'horizonEdge.tail' and 'horizonEdge.head' in ccw order
  393. addAdjoiningFace: function ( eyeVertex, horizonEdge ) {
  394. // all the half edges are created in ccw order thus the face is always pointing outside the hull
  395. var face = Face.create( eyeVertex, horizonEdge.tail(), horizonEdge.head() );
  396. this.faces.push( face );
  397. // join face.getEdge( - 1 ) with the horizon's opposite edge face.getEdge( - 1 ) = face.getEdge( 2 )
  398. face.getEdge( - 1 ).setTwin( horizonEdge.twin );
  399. return face.getEdge( 0 ); // the half edge whose vertex is the eyeVertex
  400. },
  401. // Adds 'horizon.length' faces to the hull, each face will be linked with the
  402. // horizon opposite face and the face on the left/right
  403. addNewFaces: function ( eyeVertex, horizon ) {
  404. this.newFaces = [];
  405. var firstSideEdge = null;
  406. var previousSideEdge = null;
  407. for ( var i = 0; i < horizon.length; i ++ ) {
  408. var horizonEdge = horizon[ i ];
  409. // returns the right side edge
  410. var sideEdge = this.addAdjoiningFace( eyeVertex, horizonEdge );
  411. if ( firstSideEdge === null ) {
  412. firstSideEdge = sideEdge;
  413. } else {
  414. // joins face.getEdge( 1 ) with previousFace.getEdge( 0 )
  415. sideEdge.next.setTwin( previousSideEdge );
  416. }
  417. this.newFaces.push( sideEdge.face );
  418. previousSideEdge = sideEdge;
  419. }
  420. // perform final join of new faces
  421. firstSideEdge.next.setTwin( previousSideEdge );
  422. },
  423. // Computes the distance from 'edge' opposite face's centroid to 'edge.face'
  424. oppositeFaceDistance: function ( edge ) {
  425. // The result is:
  426. //
  427. // - a positive number when the midpoint of the opposite face is above the face i.e. when the faces are concave
  428. // - a negative number when the midpoint of the opposite face is below the face i.e. when the faces are convex
  429. return edge.face.distanceToPoint( edge.twin.face.midpoint );
  430. },
  431. // Merges a face with none/any/all its neighbors according to the given strategy
  432. doAdjacentMerge: function ( face, mergeType ) {
  433. var edge = face.edge;
  434. var convex = true;
  435. do {
  436. var oppositeFace = edge.twin.face;
  437. var merge = false;
  438. if ( mergeType === MergeNonConvex ) {
  439. if ( this.oppositeFaceDistance( edge ) > - this.tolerance ||
  440. this.oppositeFaceDistance( edge.twin ) > - this.tolerance ) {
  441. merge = true;
  442. }
  443. } else {
  444. if ( face.area > oppositeFace.area ) {
  445. if ( this.oppositeFaceDistance( edge ) > - this.tolerance ) {
  446. merge = true;
  447. } else if ( this.oppositeFaceDistance( edge.twin ) > - this.tolerance ) {
  448. convex = false;
  449. }
  450. } else {
  451. if ( this.oppositeFaceDistance( edge.twin ) > - this.tolerance ) {
  452. merge = true;
  453. } else if ( this.oppositeFaceDistance( edge ) > - this.tolerance ) {
  454. convex = false;
  455. }
  456. }
  457. if ( merge === true ) {
  458. var discardedFaces = face.mergeAdjacentFaces( edge, [] );
  459. for ( var i = 0; i < discardedFaces.length; i ++ ) {
  460. this.deleteFaceVertices( discardedFaces[ i ], face );
  461. }
  462. return true;
  463. }
  464. }
  465. edge = edge.next;
  466. } while ( edge !== face.edge );
  467. if ( convex === false ) {
  468. face.mark = NonConvex;
  469. }
  470. return false;
  471. },
  472. // Adds a vertex to the hull
  473. addVertexToHull: function ( eyeVertex ) {
  474. var horizon = [];
  475. var i, face;
  476. this.unassigned.clear();
  477. // remove 'eyeVertex' from 'eyeVertex.face' so that it can't be added to the 'unassigned' vertex list
  478. this.removeVertexFromFace( eyeVertex, eyeVertex.face );
  479. this.computeHorizon( eyeVertex.point, null, eyeVertex.face, horizon );
  480. this.addNewFaces( eyeVertex, horizon );
  481. // first merge pass.
  482. // do the merge with respect to the larger face
  483. for ( i = 0; i < this.newFaces.length; i ++ ) {
  484. face = this.newFaces[ i ];
  485. if ( face.mark === Visible ) {
  486. // while ( this.doAdjacentMerge( face, MergeNonConvexLargerFace ) ) {}
  487. }
  488. }
  489. // second merge pass.
  490. // do the merge on non convex faces (a face is marked as non convex in the first pass)
  491. for ( i = 0; i < this.newFaces.length; i ++ ) {
  492. face = this.newFaces[ i ];
  493. if ( face.mark === NonConvex ) {
  494. face.mark = Visible;
  495. // while ( this.doAdjacentMerge( face, MergeNonConvex ) ) {}
  496. }
  497. }
  498. // reassign 'unassigned' vertices to the new faces
  499. this.resolveUnassignedPoints( this.newFaces );
  500. },
  501. cleanup: function () {
  502. this.assigned.clear();
  503. this.unassigned.clear();
  504. this.newFaces = [];
  505. },
  506. compute: function () {
  507. var vertex;
  508. this.computeInitialHull();
  509. // add all available vertices gradually to the hull
  510. while ( ( vertex = this.nextVertexToAdd() ) !== undefined ) {
  511. this.addVertexToHull( vertex );
  512. }
  513. this.reindexFaces();
  514. this.cleanup();
  515. }
  516. } );
  517. export { QuickHull3 };