1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696 |
- import {
- Line3,
- Plane,
- Triangle,
- Vector3
- } from 'three';
- const Visible = 0;
- const Deleted = 1;
- const _v1 = new Vector3();
- const _line3 = new Line3();
- const _plane = new Plane();
- const _closestPoint = new Vector3();
- const _triangle = new Triangle();
- /**
- * Can be used to compute the convex hull in 3D space for a given set of points. It
- * is primarily intended for {@link ConvexGeometry}.
- *
- * This Quickhull 3D implementation is a port of [quickhull3d]{@link https://github.com/maurizzzio/quickhull3d/}
- * by Mauricio Poppe.
- *
- * @three_import import { ConvexHull } from 'three/addons/math/ConvexHull.js';
- */
- class ConvexHull {
- /**
- * Constructs a new convex hull.
- */
- constructor() {
- this.tolerance = - 1;
- this.faces = []; // the generated faces of the convex hull
- this.newFaces = []; // this array holds the faces that are generated within a single iteration
- // the vertex lists work as follows:
- //
- // let 'a' and 'b' be 'Face' instances
- // let 'v' be points wrapped as instance of 'Vertex'
- //
- // [v, v, ..., v, v, v, ...]
- // ^ ^
- // | |
- // a.outside b.outside
- //
- this.assigned = new VertexList();
- this.unassigned = new VertexList();
- this.vertices = []; // vertices of the hull (internal representation of given geometry data)
- }
- /**
- * Computes to convex hull for the given array of points.
- *
- * @param {Array<Vector3>} points - The array of points in 3D space.
- * @return {ConvexHull} A reference to this convex hull.
- */
- setFromPoints( points ) {
- // The algorithm needs at least four points.
- if ( points.length >= 4 ) {
- this.makeEmpty();
- for ( let i = 0, l = points.length; i < l; i ++ ) {
- this.vertices.push( new VertexNode( points[ i ] ) );
- }
- this._compute();
- }
- return this;
- }
- /**
- * Computes the convex hull of the given 3D object (including its descendants),
- * accounting for the world transforms of both the 3D object and its descendants.
- *
- * @param {Object3D} object - The 3D object to compute the convex hull for.
- * @return {ConvexHull} A reference to this convex hull.
- */
- setFromObject( object ) {
- const points = [];
- object.updateMatrixWorld( true );
- object.traverse( function ( node ) {
- const geometry = node.geometry;
- if ( geometry !== undefined ) {
- const attribute = geometry.attributes.position;
- if ( attribute !== undefined ) {
- for ( let i = 0, l = attribute.count; i < l; i ++ ) {
- const point = new Vector3();
- point.fromBufferAttribute( attribute, i ).applyMatrix4( node.matrixWorld );
- points.push( point );
- }
- }
- }
- } );
- return this.setFromPoints( points );
- }
- /**
- * Returns `true` if the given point lies in the convex hull.
- *
- * @param {Vector3} point - The point to test.
- * @return {boolean} Whether the given point lies in the convex hull or not.
- */
- containsPoint( point ) {
- const faces = this.faces;
- for ( let i = 0, l = faces.length; i < l; i ++ ) {
- const face = faces[ i ];
- // compute signed distance and check on what half space the point lies
- if ( face.distanceToPoint( point ) > this.tolerance ) return false;
- }
- return true;
- }
- /**
- * Computes the intersections point of the given ray and this convex hull.
- *
- * @param {Ray} ray - The ray to test.
- * @param {Vector3} target - The target vector that is used to store the method's result.
- * @return {Vector3|null} The intersection point. Returns `null` if not intersection was detected.
- */
- intersectRay( ray, target ) {
- // based on "Fast Ray-Convex Polyhedron Intersection" by Eric Haines, GRAPHICS GEMS II
- const faces = this.faces;
- let tNear = - Infinity;
- let tFar = Infinity;
- for ( let i = 0, l = faces.length; i < l; i ++ ) {
- const face = faces[ i ];
- // interpret faces as planes for the further computation
- const vN = face.distanceToPoint( ray.origin );
- const vD = face.normal.dot( ray.direction );
- // if the origin is on the positive side of a plane (so the plane can "see" the origin) and
- // the ray is turned away or parallel to the plane, there is no intersection
- if ( vN > 0 && vD >= 0 ) return null;
- // compute the distance from the ray’s origin to the intersection with the plane
- const t = ( vD !== 0 ) ? ( - vN / vD ) : 0;
- // only proceed if the distance is positive. a negative distance means the intersection point
- // lies "behind" the origin
- if ( t <= 0 ) continue;
- // now categorized plane as front-facing or back-facing
- if ( vD > 0 ) {
- // plane faces away from the ray, so this plane is a back-face
- tFar = Math.min( t, tFar );
- } else {
- // front-face
- tNear = Math.max( t, tNear );
- }
- if ( tNear > tFar ) {
- // if tNear ever is greater than tFar, the ray must miss the convex hull
- return null;
- }
- }
- // evaluate intersection point
- // always try tNear first since its the closer intersection point
- if ( tNear !== - Infinity ) {
- ray.at( tNear, target );
- } else {
- ray.at( tFar, target );
- }
- return target;
- }
- /**
- * Returns `true` if the given ray intersects with this convex hull.
- *
- * @param {Ray} ray - The ray to test.
- * @return {boolean} Whether the given ray intersects with this convex hull or not.
- */
- intersectsRay( ray ) {
- return this.intersectRay( ray, _v1 ) !== null;
- }
- /**
- * Makes the convex hull empty.
- *
- * @return {ConvexHull} A reference to this convex hull.
- */
- makeEmpty() {
- this.faces = [];
- this.vertices = [];
- return this;
- }
- // private
- /**
- * Adds a vertex to the 'assigned' list of vertices and assigns it to the given face.
- *
- * @private
- * @param {VertexNode} vertex - The vertex to add.
- * @param {Face} face - The target face.
- * @return {ConvexHull} A reference to this convex hull.
- */
- _addVertexToFace( vertex, face ) {
- vertex.face = face;
- if ( face.outside === null ) {
- this.assigned.append( vertex );
- } else {
- this.assigned.insertBefore( face.outside, vertex );
- }
- face.outside = vertex;
- return this;
- }
- /**
- * Removes a vertex from the 'assigned' list of vertices and from the given face.
- * It also makes sure that the link from 'face' to the first vertex it sees in 'assigned'
- * is linked correctly after the removal.
- *
- * @private
- * @param {VertexNode} vertex - The vertex to remove.
- * @param {Face} face - The target face.
- * @return {ConvexHull} A reference to this convex hull.
- */
- _removeVertexFromFace( vertex, face ) {
- if ( vertex === face.outside ) {
- // fix face.outside link
- if ( vertex.next !== null && vertex.next.face === face ) {
- // face has at least 2 outside vertices, move the 'outside' reference
- face.outside = vertex.next;
- } else {
- // vertex was the only outside vertex that face had
- face.outside = null;
- }
- }
- this.assigned.remove( vertex );
- return this;
- }
- /**
- * Removes all the visible vertices that a given face is able to see which are stored in
- * the 'assigned' vertex list.
- *
- * @private
- * @param {Face} face - The target face.
- * @return {VertexNode|undefined} A reference to this convex hull.
- */
- _removeAllVerticesFromFace( face ) {
- if ( face.outside !== null ) {
- // reference to the first and last vertex of this face
- const start = face.outside;
- let end = face.outside;
- while ( end.next !== null && end.next.face === face ) {
- end = end.next;
- }
- this.assigned.removeSubList( start, end );
- // fix references
- start.prev = end.next = null;
- face.outside = null;
- return start;
- }
- }
- /**
- * Removes all the visible vertices that `face` is able to see.
- *
- * - If `absorbingFace` doesn't exist, then all the removed vertices will be added to the 'unassigned' vertex list.
- * - If `absorbingFace` exists, then this method will assign all the vertices of 'face' that can see 'absorbingFace'.
- * - If a vertex cannot see `absorbingFace`, it's added to the 'unassigned' vertex list.
- *
- * @private
- * @param {Face} face - The given face.
- * @param {Face} [absorbingFace] - An optional face that tries to absorb the vertices of the first face.
- * @return {ConvexHull} A reference to this convex hull.
- */
- _deleteFaceVertices( face, absorbingFace ) {
- const faceVertices = this._removeAllVerticesFromFace( face );
- if ( faceVertices !== undefined ) {
- if ( absorbingFace === undefined ) {
- // mark the vertices to be reassigned to some other face
- this.unassigned.appendChain( faceVertices );
- } else {
- // if there's an absorbing face try to assign as many vertices as possible to it
- let vertex = faceVertices;
- do {
- // we need to buffer the subsequent vertex at this point because the 'vertex.next' reference
- // will be changed by upcoming method calls
- const nextVertex = vertex.next;
- const distance = absorbingFace.distanceToPoint( vertex.point );
- // check if 'vertex' is able to see 'absorbingFace'
- if ( distance > this.tolerance ) {
- this._addVertexToFace( vertex, absorbingFace );
- } else {
- this.unassigned.append( vertex );
- }
- // now assign next vertex
- vertex = nextVertex;
- } while ( vertex !== null );
- }
- }
- return this;
- }
- /**
- * Reassigns as many vertices as possible from the unassigned list to the new faces.
- *
- * @private
- * @param {Array<Face>} newFaces - The new faces.
- * @return {ConvexHull} A reference to this convex hull.
- */
- _resolveUnassignedPoints( newFaces ) {
- if ( this.unassigned.isEmpty() === false ) {
- let vertex = this.unassigned.first();
- do {
- // buffer 'next' reference, see ._deleteFaceVertices()
- const nextVertex = vertex.next;
- let maxDistance = this.tolerance;
- let maxFace = null;
- for ( let i = 0; i < newFaces.length; i ++ ) {
- const face = newFaces[ i ];
- if ( face.mark === Visible ) {
- const distance = face.distanceToPoint( vertex.point );
- if ( distance > maxDistance ) {
- maxDistance = distance;
- maxFace = face;
- }
- if ( maxDistance > 1000 * this.tolerance ) break;
- }
- }
- // 'maxFace' can be null e.g. if there are identical vertices
- if ( maxFace !== null ) {
- this._addVertexToFace( vertex, maxFace );
- }
- vertex = nextVertex;
- } while ( vertex !== null );
- }
- return this;
- }
- /**
- * Computes the extremes values (min/max vectors) which will be used to
- * compute the initial hull.
- *
- * @private
- * @return {Object} The extremes.
- */
- _computeExtremes() {
- const min = new Vector3();
- const max = new Vector3();
- const minVertices = [];
- const maxVertices = [];
- // initially assume that the first vertex is the min/max
- for ( let i = 0; i < 3; i ++ ) {
- minVertices[ i ] = maxVertices[ i ] = this.vertices[ 0 ];
- }
- min.copy( this.vertices[ 0 ].point );
- max.copy( this.vertices[ 0 ].point );
- // compute the min/max vertex on all six directions
- for ( let i = 0, l = this.vertices.length; i < l; i ++ ) {
- const vertex = this.vertices[ i ];
- const point = vertex.point;
- // update the min coordinates
- for ( let j = 0; j < 3; j ++ ) {
- if ( point.getComponent( j ) < min.getComponent( j ) ) {
- min.setComponent( j, point.getComponent( j ) );
- minVertices[ j ] = vertex;
- }
- }
- // update the max coordinates
- for ( let j = 0; j < 3; j ++ ) {
- if ( point.getComponent( j ) > max.getComponent( j ) ) {
- max.setComponent( j, point.getComponent( j ) );
- maxVertices[ j ] = vertex;
- }
- }
- }
- // use min/max vectors to compute an optimal epsilon
- this.tolerance = 3 * Number.EPSILON * (
- Math.max( Math.abs( min.x ), Math.abs( max.x ) ) +
- Math.max( Math.abs( min.y ), Math.abs( max.y ) ) +
- Math.max( Math.abs( min.z ), Math.abs( max.z ) )
- );
- return { min: minVertices, max: maxVertices };
- }
- /**
- * Computes the initial simplex assigning to its faces all the points that are
- * candidates to form part of the hull.
- *
- * @private
- * @return {ConvexHull} A reference to this convex hull.
- */
- _computeInitialHull() {
- const vertices = this.vertices;
- const extremes = this._computeExtremes();
- const min = extremes.min;
- const max = extremes.max;
- // 1. Find the two vertices 'v0' and 'v1' with the greatest 1d separation
- // (max.x - min.x)
- // (max.y - min.y)
- // (max.z - min.z)
- let maxDistance = 0;
- let index = 0;
- for ( let i = 0; i < 3; i ++ ) {
- const distance = max[ i ].point.getComponent( i ) - min[ i ].point.getComponent( i );
- if ( distance > maxDistance ) {
- maxDistance = distance;
- index = i;
- }
- }
- const v0 = min[ index ];
- const v1 = max[ index ];
- let v2;
- let v3;
- // 2. The next vertex 'v2' is the one farthest to the line formed by 'v0' and 'v1'
- maxDistance = 0;
- _line3.set( v0.point, v1.point );
- for ( let i = 0, l = this.vertices.length; i < l; i ++ ) {
- const vertex = vertices[ i ];
- if ( vertex !== v0 && vertex !== v1 ) {
- _line3.closestPointToPoint( vertex.point, true, _closestPoint );
- const distance = _closestPoint.distanceToSquared( vertex.point );
- if ( distance > maxDistance ) {
- maxDistance = distance;
- v2 = vertex;
- }
- }
- }
- // 3. The next vertex 'v3' is the one farthest to the plane 'v0', 'v1', 'v2'
- maxDistance = - 1;
- _plane.setFromCoplanarPoints( v0.point, v1.point, v2.point );
- for ( let i = 0, l = this.vertices.length; i < l; i ++ ) {
- const vertex = vertices[ i ];
- if ( vertex !== v0 && vertex !== v1 && vertex !== v2 ) {
- const distance = Math.abs( _plane.distanceToPoint( vertex.point ) );
- if ( distance > maxDistance ) {
- maxDistance = distance;
- v3 = vertex;
- }
- }
- }
- const faces = [];
- if ( _plane.distanceToPoint( v3.point ) < 0 ) {
- // the face is not able to see the point so 'plane.normal' is pointing outside the tetrahedron
- faces.push(
- Face.create( v0, v1, v2 ),
- Face.create( v3, v1, v0 ),
- Face.create( v3, v2, v1 ),
- Face.create( v3, v0, v2 )
- );
- // set the twin edge
- for ( let i = 0; i < 3; i ++ ) {
- const j = ( i + 1 ) % 3;
- // join face[ i ] i > 0, with the first face
- faces[ i + 1 ].getEdge( 2 ).setTwin( faces[ 0 ].getEdge( j ) );
- // join face[ i ] with face[ i + 1 ], 1 <= i <= 3
- faces[ i + 1 ].getEdge( 1 ).setTwin( faces[ j + 1 ].getEdge( 0 ) );
- }
- } else {
- // the face is able to see the point so 'plane.normal' is pointing inside the tetrahedron
- faces.push(
- Face.create( v0, v2, v1 ),
- Face.create( v3, v0, v1 ),
- Face.create( v3, v1, v2 ),
- Face.create( v3, v2, v0 )
- );
- // set the twin edge
- for ( let i = 0; i < 3; i ++ ) {
- const j = ( i + 1 ) % 3;
- // join face[ i ] i > 0, with the first face
- faces[ i + 1 ].getEdge( 2 ).setTwin( faces[ 0 ].getEdge( ( 3 - i ) % 3 ) );
- // join face[ i ] with face[ i + 1 ]
- faces[ i + 1 ].getEdge( 0 ).setTwin( faces[ j + 1 ].getEdge( 1 ) );
- }
- }
- // the initial hull is the tetrahedron
- for ( let i = 0; i < 4; i ++ ) {
- this.faces.push( faces[ i ] );
- }
- // initial assignment of vertices to the faces of the tetrahedron
- for ( let i = 0, l = vertices.length; i < l; i ++ ) {
- const vertex = vertices[ i ];
- if ( vertex !== v0 && vertex !== v1 && vertex !== v2 && vertex !== v3 ) {
- maxDistance = this.tolerance;
- let maxFace = null;
- for ( let j = 0; j < 4; j ++ ) {
- const distance = this.faces[ j ].distanceToPoint( vertex.point );
- if ( distance > maxDistance ) {
- maxDistance = distance;
- maxFace = this.faces[ j ];
- }
- }
- if ( maxFace !== null ) {
- this._addVertexToFace( vertex, maxFace );
- }
- }
- }
- return this;
- }
- /**
- * Removes inactive (e.g. deleted) faces from the internal face list.
- *
- * @private
- * @return {ConvexHull} A reference to this convex hull.
- */
- _reindexFaces() {
- const activeFaces = [];
- for ( let i = 0; i < this.faces.length; i ++ ) {
- const face = this.faces[ i ];
- if ( face.mark === Visible ) {
- activeFaces.push( face );
- }
- }
- this.faces = activeFaces;
- return this;
- }
- /**
- * Finds the next vertex to create faces with the current hull.
- *
- * - Let the initial face be the first face existing in the 'assigned' vertex list.
- * - If a face doesn't exist then return since there're no vertices left.
- * - Otherwise for each vertex that face sees find the one furthest away from it.
- *
- * @private
- * @return {?VertexNode} The next vertex to add.
- */
- _nextVertexToAdd() {
- // if the 'assigned' list of vertices is empty, no vertices are left. return with 'undefined'
- if ( this.assigned.isEmpty() === false ) {
- let eyeVertex, maxDistance = 0;
- // grab the first available face and start with the first visible vertex of that face
- const eyeFace = this.assigned.first().face;
- let vertex = eyeFace.outside;
- // now calculate the farthest vertex that face can see
- do {
- const distance = eyeFace.distanceToPoint( vertex.point );
- if ( distance > maxDistance ) {
- maxDistance = distance;
- eyeVertex = vertex;
- }
- vertex = vertex.next;
- } while ( vertex !== null && vertex.face === eyeFace );
- return eyeVertex;
- }
- }
- /**
- * Computes a chain of half edges in CCW order called the 'horizon'. For an edge
- * to be part of the horizon it must join a face that can see 'eyePoint' and a face
- * that cannot see 'eyePoint'.
- *
- * @private
- * @param {Vector3} eyePoint - The 3D-coordinates of a point.
- * @param {HalfEdge} crossEdge - The edge used to jump to the current face.
- * @param {Face} face - The current face being tested.
- * @param {Array<HalfEdge>} horizon - The edges that form part of the horizon in CCW order.
- * @return {ConvexHull} A reference to this convex hull.
- */
- _computeHorizon( eyePoint, crossEdge, face, horizon ) {
- // moves face's vertices to the 'unassigned' vertex list
- this._deleteFaceVertices( face );
- face.mark = Deleted;
- let edge;
- if ( crossEdge === null ) {
- edge = crossEdge = face.getEdge( 0 );
- } else {
- // start from the next edge since 'crossEdge' was already analyzed
- // (actually 'crossEdge.twin' was the edge who called this method recursively)
- edge = crossEdge.next;
- }
- do {
- const twinEdge = edge.twin;
- const oppositeFace = twinEdge.face;
- if ( oppositeFace.mark === Visible ) {
- if ( oppositeFace.distanceToPoint( eyePoint ) > this.tolerance ) {
- // the opposite face can see the vertex, so proceed with next edge
- this._computeHorizon( eyePoint, twinEdge, oppositeFace, horizon );
- } else {
- // the opposite face can't see the vertex, so this edge is part of the horizon
- horizon.push( edge );
- }
- }
- edge = edge.next;
- } while ( edge !== crossEdge );
- return this;
- }
- /**
- * Creates a face with the vertices 'eyeVertex.point', 'horizonEdge.tail' and 'horizonEdge.head'
- * in CCW order. All the half edges are created in CCW order thus the face is always pointing
- * outside the hull.
- *
- * @private
- * @param {VertexNode} eyeVertex - The vertex that is added to the hull.
- * @param {HalfEdge} horizonEdge - A single edge of the horizon.
- * @return {HalfEdge} The half edge whose vertex is the eyeVertex.
- */
- _addAdjoiningFace( eyeVertex, horizonEdge ) {
- // all the half edges are created in ccw order thus the face is always pointing outside the hull
- const face = Face.create( eyeVertex, horizonEdge.tail(), horizonEdge.head() );
- this.faces.push( face );
- // join face.getEdge( - 1 ) with the horizon's opposite edge face.getEdge( - 1 ) = face.getEdge( 2 )
- face.getEdge( - 1 ).setTwin( horizonEdge.twin );
- return face.getEdge( 0 ); // the half edge whose vertex is the eyeVertex
- }
- /**
- * Adds 'horizon.length' faces to the hull, each face will be linked with the horizon
- * opposite face and the face on the left/right.
- *
- * @private
- * @param {VertexNode} eyeVertex - The vertex that is added to the hull.
- * @param {Array<HalfEdge>} horizon - The horizon.
- * @return {ConvexHull} A reference to this convex hull.
- */
- _addNewFaces( eyeVertex, horizon ) {
- this.newFaces = [];
- let firstSideEdge = null;
- let previousSideEdge = null;
- for ( let i = 0; i < horizon.length; i ++ ) {
- const horizonEdge = horizon[ i ];
- // returns the right side edge
- const sideEdge = this._addAdjoiningFace( eyeVertex, horizonEdge );
- if ( firstSideEdge === null ) {
- firstSideEdge = sideEdge;
- } else {
- // joins face.getEdge( 1 ) with previousFace.getEdge( 0 )
- sideEdge.next.setTwin( previousSideEdge );
- }
- this.newFaces.push( sideEdge.face );
- previousSideEdge = sideEdge;
- }
- // perform final join of new faces
- firstSideEdge.next.setTwin( previousSideEdge );
- return this;
- }
- /**
- * Adds a vertex to the hull with the following algorithm:
- *
- * - Compute the 'horizon' which is a chain of half edges. For an edge to belong to this group
- * it must be the edge connecting a face that can see 'eyeVertex' and a face which cannot see 'eyeVertex'.
- * - All the faces that can see 'eyeVertex' have its visible vertices removed from the assigned vertex list.
- * - A new set of faces is created with each edge of the 'horizon' and 'eyeVertex'. Each face is connected
- * with the opposite horizon face and the face on the left/right.
- * - The vertices removed from all the visible faces are assigned to the new faces if possible.
- *
- * @private
- * @param {VertexNode} eyeVertex - The vertex to add.
- * @return {ConvexHull} A reference to this convex hull.
- */
- _addVertexToHull( eyeVertex ) {
- const horizon = [];
- this.unassigned.clear();
- // remove 'eyeVertex' from 'eyeVertex.face' so that it can't be added to the 'unassigned' vertex list
- this._removeVertexFromFace( eyeVertex, eyeVertex.face );
- this._computeHorizon( eyeVertex.point, null, eyeVertex.face, horizon );
- this._addNewFaces( eyeVertex, horizon );
- // reassign 'unassigned' vertices to the new faces
- this._resolveUnassignedPoints( this.newFaces );
- return this;
- }
- /**
- * Cleans up internal properties after computing the convex hull.
- *
- * @private
- * @return {ConvexHull} A reference to this convex hull.
- */
- _cleanup() {
- this.assigned.clear();
- this.unassigned.clear();
- this.newFaces = [];
- return this;
- }
- /**
- * Starts the execution of the quick hull algorithm.
- *
- * @private
- * @return {ConvexHull} A reference to this convex hull.
- */
- _compute() {
- let vertex;
- this._computeInitialHull();
- // add all available vertices gradually to the hull
- while ( ( vertex = this._nextVertexToAdd() ) !== undefined ) {
- this._addVertexToHull( vertex );
- }
- this._reindexFaces();
- this._cleanup();
- return this;
- }
- }
- /**
- * Represents a section bounded by a specific amount of half-edges.
- * The current implementation assumes that a face always consist of three edges.
- *
- * @private
- */
- class Face {
- /**
- * Constructs a new face.
- */
- constructor() {
- /**
- * The normal vector of the face.
- *
- * @private
- * @type {Vector3}
- */
- this.normal = new Vector3();
- /**
- * The midpoint or centroid of the face.
- *
- * @private
- * @type {Vector3}
- */
- this.midpoint = new Vector3();
- /**
- * The area of the face.
- *
- * @private
- * @type {number}
- * @default 0
- */
- this.area = 0;
- /**
- * Signed distance from face to the origin.
- *
- * @private
- * @type {number}
- * @default 0
- */
- this.constant = 0;
- /**
- * Reference to a vertex in a vertex list this face can see.
- *
- * @private
- * @type {?VertexNode}
- * @default null
- */
- this.outside = null; // reference to a vertex in a vertex list this face can see
- this.mark = Visible;
- /**
- * Reference to the base edge of a face. To retrieve all edges, you can use the
- * `next` reference of the current edge.
- *
- * @private
- * @type {?HalfEdge}
- * @default null
- */
- this.edge = null;
- }
- /**
- * Creates a face from the given vertex nodes.
- *
- * @private
- * @param {VertexNode} a - The first vertex node.
- * @param {VertexNode} b - The second vertex node.
- * @param {VertexNode} c - The third vertex node.
- * @return {Face} The created face.
- */
- static create( a, b, c ) {
- const face = new Face();
- const e0 = new HalfEdge( a, face );
- const e1 = new HalfEdge( b, face );
- const e2 = new HalfEdge( c, face );
- // join edges
- e0.next = e2.prev = e1;
- e1.next = e0.prev = e2;
- e2.next = e1.prev = e0;
- // main half edge reference
- face.edge = e0;
- return face.compute();
- }
- /**
- * Returns an edge by the given index.
- *
- * @private
- * @param {number} i - The edge index.
- * @return {HalfEdge} The edge.
- */
- getEdge( i ) {
- let edge = this.edge;
- while ( i > 0 ) {
- edge = edge.next;
- i --;
- }
- while ( i < 0 ) {
- edge = edge.prev;
- i ++;
- }
- return edge;
- }
- /**
- * Computes all properties of the face.
- *
- * @private
- * @return {Face} A reference to this face.
- */
- compute() {
- const a = this.edge.tail();
- const b = this.edge.head();
- const c = this.edge.next.head();
- _triangle.set( a.point, b.point, c.point );
- _triangle.getNormal( this.normal );
- _triangle.getMidpoint( this.midpoint );
- this.area = _triangle.getArea();
- this.constant = this.normal.dot( this.midpoint );
- return this;
- }
- /**
- * Returns the signed distance from a given point to the plane representation of this face.
- *
- * @private
- * @param {Vector3} point - The point to compute the distance to.
- * @return {number} The distance.
- */
- distanceToPoint( point ) {
- return this.normal.dot( point ) - this.constant;
- }
- }
- /**
- * The basis for a half-edge data structure, also known as doubly
- * connected edge list (DCEL).
- *
- * @private
- */
- class HalfEdge {
- /**
- * Constructs a new half edge.
- *
- * @param {VertexNode} vertex - A reference to its destination vertex.
- * @param {Face} face - A reference to its face.
- */
- constructor( vertex, face ) {
- /**
- * A reference to its destination vertex.
- *
- * @private
- * @type {VertexNode}
- */
- this.vertex = vertex;
- /**
- * Reference to the previous half-edge of the same face.
- *
- * @private
- * @type {?HalfEdge}
- * @default null
- */
- this.prev = null;
- /**
- * Reference to the next half-edge of the same face.
- *
- * @private
- * @type {?HalfEdge}
- * @default null
- */
- this.next = null;
- /**
- * Reference to the twin half-edge to reach the opposite face.
- *
- * @private
- * @type {?HalfEdge}
- * @default null
- */
- this.twin = null;
- /**
- * A reference to its face.
- *
- * @private
- * @type {Face}
- */
- this.face = face;
- }
- /**
- * Returns the destination vertex.
- *
- * @private
- * @return {VertexNode} The destination vertex.
- */
- head() {
- return this.vertex;
- }
- /**
- * Returns the origin vertex.
- *
- * @private
- * @return {VertexNode} The destination vertex.
- */
- tail() {
- return this.prev ? this.prev.vertex : null;
- }
- /**
- * Returns the Euclidean length (straight-line length) of the edge.
- *
- * @private
- * @return {number} The edge's length.
- */
- length() {
- const head = this.head();
- const tail = this.tail();
- if ( tail !== null ) {
- return tail.point.distanceTo( head.point );
- }
- return - 1;
- }
- /**
- * Returns the square of the Euclidean length (straight-line length) of the edge.
- *
- * @private
- * @return {number} The square of the edge's length.
- */
- lengthSquared() {
- const head = this.head();
- const tail = this.tail();
- if ( tail !== null ) {
- return tail.point.distanceToSquared( head.point );
- }
- return - 1;
- }
- /**
- * Sets the twin edge of this half-edge. It also ensures that the twin reference
- * of the given half-edge is correctly set.
- *
- * @private
- * @param {HalfEdge} edge - The twin edge to set.
- * @return {HalfEdge} A reference to this edge.
- */
- setTwin( edge ) {
- this.twin = edge;
- edge.twin = this;
- return this;
- }
- }
- /**
- * A vertex as a double linked list node.
- *
- * @private
- */
- class VertexNode {
- /**
- * Constructs a new vertex node.
- *
- * @param {Vector3} point - A point in 3D space.
- */
- constructor( point ) {
- /**
- * A point in 3D space.
- *
- * @private
- * @type {Vector3}
- */
- this.point = point;
- /**
- * Reference to the previous vertex in the double linked list.
- *
- * @private
- * @type {?VertexNode}
- * @default null
- */
- this.prev = null;
- /**
- * Reference to the next vertex in the double linked list.
- *
- * @private
- * @type {?VertexNode}
- * @default null
- */
- this.next = null;
- /**
- * Reference to the face that is able to see this vertex.
- *
- * @private
- * @type {?Face}
- * @default null
- */
- this.face = null;
- }
- }
- /**
- * A doubly linked list of vertices.
- *
- * @private
- */
- class VertexList {
- /**
- * Constructs a new vertex list.
- */
- constructor() {
- /**
- * Reference to the first vertex of the linked list.
- *
- * @private
- * @type {?VertexNode}
- * @default null
- */
- this.head = null;
- /**
- * Reference to the last vertex of the linked list.
- *
- * @private
- * @type {?VertexNode}
- * @default null
- */
- this.tail = null;
- }
- /**
- * Returns the head reference.
- *
- * @private
- * @return {VertexNode} The head reference.
- */
- first() {
- return this.head;
- }
- /**
- * Returns the tail reference.
- *
- * @private
- * @return {VertexNode} The tail reference.
- */
- last() {
- return this.tail;
- }
- /**
- * Clears the linked list.
- *
- * @private
- * @return {VertexList} A reference to this vertex list.
- */
- clear() {
- this.head = this.tail = null;
- return this;
- }
- /**
- * Inserts a vertex before a target vertex.
- *
- * @private
- * @param {VertexNode} target - The target.
- * @param {VertexNode} vertex - The vertex to insert.
- * @return {VertexList} A reference to this vertex list.
- */
- insertBefore( target, vertex ) {
- vertex.prev = target.prev;
- vertex.next = target;
- if ( vertex.prev === null ) {
- this.head = vertex;
- } else {
- vertex.prev.next = vertex;
- }
- target.prev = vertex;
- return this;
- }
- /**
- * Inserts a vertex after a target vertex.
- *
- * @private
- * @param {VertexNode} target - The target.
- * @param {VertexNode} vertex - The vertex to insert.
- * @return {VertexList} A reference to this vertex list.
- */
- insertAfter( target, vertex ) {
- vertex.prev = target;
- vertex.next = target.next;
- if ( vertex.next === null ) {
- this.tail = vertex;
- } else {
- vertex.next.prev = vertex;
- }
- target.next = vertex;
- return this;
- }
- /**
- * Appends a vertex to this vertex list.
- *
- * @private
- * @param {VertexNode} vertex - The vertex to append.
- * @return {VertexList} A reference to this vertex list.
- */
- append( vertex ) {
- if ( this.head === null ) {
- this.head = vertex;
- } else {
- this.tail.next = vertex;
- }
- vertex.prev = this.tail;
- vertex.next = null; // the tail has no subsequent vertex
- this.tail = vertex;
- return this;
- }
- /**
- * Appends a chain of vertices where the given vertex is the head.
- *
- * @private
- * @param {VertexNode} vertex - The head vertex of a chain of vertices.
- * @return {VertexList} A reference to this vertex list.
- */
- appendChain( vertex ) {
- if ( this.head === null ) {
- this.head = vertex;
- } else {
- this.tail.next = vertex;
- }
- vertex.prev = this.tail;
- // ensure that the 'tail' reference points to the last vertex of the chain
- while ( vertex.next !== null ) {
- vertex = vertex.next;
- }
- this.tail = vertex;
- return this;
- }
- /**
- * Removes a vertex from the linked list.
- *
- * @private
- * @param {VertexNode} vertex - The vertex to remove.
- * @return {VertexList} A reference to this vertex list.
- */
- remove( vertex ) {
- if ( vertex.prev === null ) {
- this.head = vertex.next;
- } else {
- vertex.prev.next = vertex.next;
- }
- if ( vertex.next === null ) {
- this.tail = vertex.prev;
- } else {
- vertex.next.prev = vertex.prev;
- }
- return this;
- }
- /**
- * Removes a sublist of vertices from the linked list.
- *
- * @private
- * @param {VertexNode} a - The head of the sublist.
- * @param {VertexNode} b - The tail of the sublist.
- * @return {VertexList} A reference to this vertex list.
- */
- removeSubList( a, b ) {
- if ( a.prev === null ) {
- this.head = b.next;
- } else {
- a.prev.next = b.next;
- }
- if ( b.next === null ) {
- this.tail = a.prev;
- } else {
- b.next.prev = a.prev;
- }
- return this;
- }
- /**
- * Returns `true` if the linked list is empty.
- *
- * @private
- * @return {boolean} Whether the linked list is empty or not.
- */
- isEmpty() {
- return this.head === null;
- }
- }
- export { ConvexHull, Face, HalfEdge, VertexNode, VertexList };
|