import Delaunator from 'delaunator' ; const hashPoint = ( p ) : string => `${ p . x },${ p . y }` ; const equalPoints = < T extends IPoint > ( a ) => ( b ) : boolean => a . x === b . x && a . y === b . y ; /** Returns unique points from points array */ const uniqPoints = < T extends IPoint > ( list ) : T [ ] => list . sort ( ( a , b ) => ( a . x - b . x ? a . x - b . x : a . y - b . y ) ) . filter ( ( v , i , arr ) => i === 0 || ! ( v . x === arr [ i - 1 ] . x && v . y === arr [ i - 1 ] . y ) ) ; const arrayToPoint = ( array ) : IPoint => ( { x : array [ 0 ] , y : array [ 1 ] } ) ; const pointToArray = ( point ) : number [ ] => [ point . x , point . y ] ; const manhattenDistance = ( p0 , p1 ) : number => Math . abs ( p0 . x - p1 . x ) + Math . abs ( p0 . y - p1 . y ) ; const euclideanDistance = ( p0 , p1 ) : number => Math . sqrt ( Math . pow ( p0 . x - p1 . x , 2 ) + Math . pow ( p0 . y - p1 . y , 2 ) ) ; const pointInCircle = ( point , origin , r ) : boolean => Math . pow ( origin . x - point . x , 2 ) + Math . pow ( origin . y - point . y , 2 ) <= r * r ; const range = ( from , to ) : number [ ] => [ ... Array ( to - from ) . keys ( ) ] . map ( i => from + i ) ; /** Returns the angle (0-360) anticlockwise from the horizontal for a point on a circle */ const getAngle = ( cX , cY , pX , pY ) : number => { const x = pX - cX ; const y = pY - cY ; if ( x === 0 && y === 0 ) return 0 ; const angle = ( Math . acos ( x / Math . sqrt ( x * x + y * y ) ) * 180 ) / Math . PI ; if ( y < 0 ) return 360 - angle ; return angle ; } ; const getReflectedPoint = ( p , lp0 , lp1 ) : IPoint => { const m = ( lp0 . y - lp1 . y ) / ( lp0 . x - lp1 . x ) ; const b = lp0 . y - lp0 . x * m ; const d = ( p . x + ( p . y - b ) * m ) / ( 1 + m ** 2 ) ; return { x : 2 * d - p . x , y : 2 * d * m - p . y + 2 * b , } ; } ; /** Returns 0 if p is on the line, +1 on one side and -1 on the other side. */ const findSide = ( p , lp0 , lp1 ) : number => Math . sign ( ( lp1 . x - lp0 . x ) * ( p . y - lp0 . y ) - ( lp1 . y - lp0 . y ) * ( p . x - lp0 . x ) ) ; /** Creates lines between points based on delaunay triangulation */ const pointsToLines = < T extends IPoint > ( nodes ) : T [ ] [ ] => { const filteredNodes = uniqPoints ( nodes ) ; if ( filteredNodes . length === 1 ) return [ [ filteredNodes [ 0 ] , filteredNodes [ 0 ] ] ] ; if ( filteredNodes . length === 2 ) return [ filteredNodes ] ; let lastSlope = 0 ; for ( let i = 0 ; i < filteredNodes . length ; i ++ ) { if ( i === filteredNodes . length - 1 ) { return filteredNodes . flatMap ( ( _ , i , arr ) => ( i === 0 ? [ ] : [ [ arr [ i - 1 ] , arr [ i ] ] ] ) ) ; } const node = filteredNodes [ i ] ; const next = filteredNodes [ i + 1 ] ; const dX = Math . abs ( node . x - next . x ) ; const dY = Math . abs ( node . y - next . y ) ; if ( i === 0 ) { lastSlope = dY / dX ; } else if ( lastSlope !== dY / dX ) { break ; } } const delaunay = Delaunator . from ( filteredNodes . map ( pointToArray ) ) ; return delaunay . triangles . reduce < T [ ] [ ] > ( ( pV , _ , i , arr ) => { if ( i <= delaunay . halfedges [ i ] ) return pV ; const p = filteredNodes [ arr [ i ] ] ; const q = filteredNodes [ arr [ i % 3 === 2 ? i - 2 : i + 1 ] ] ; return pV . concat ( [ [ p , q ] ] ) ; } , [ ] ) ; } ; /**
 * Creates triangles between points based on delaunay triangulation
 *
 * If triangles can not be formed, it's going to return lines
 */ const pointsToTriangles = < T extends IPoint > ( nodes ) : T [ ] [ ] => { const filteredNodes = uniqPoints ( nodes ) ; if ( filteredNodes . length === 1 ) return [ [ filteredNodes [ 0 ] , filteredNodes [ 0 ] ] ] ; if ( filteredNodes . length === 2 ) return [ filteredNodes ] ; let lastSlope = 0 ; for ( let i = 0 ; i < filteredNodes . length ; i ++ ) { if ( i === filteredNodes . length - 1 ) { return filteredNodes . flatMap ( ( _ , i , arr ) => ( i === 0 ? [ ] : [ [ arr [ i - 1 ] , arr [ i ] ] ] ) ) ; } const node = filteredNodes [ i ] ; const next = filteredNodes [ i + 1 ] ; const dX = Math . abs ( node . x - next . x ) ; const dY = Math . abs ( node . y - next . y ) ; if ( i === 0 ) { lastSlope = dY / dX ; } else if ( lastSlope !== dY / dX ) { break ; } } const delaunay = Delaunator . from ( filteredNodes . map ( pointToArray ) ) ; return range ( 0 , delaunay . triangles . length / 3 ) . map ( t => [ 3 * t , 3 * t + 1 , 3 * t + 2 ] . map ( e => filteredNodes [ delaunay . triangles [ e ] ] ) ) ; } ; export default { hashPoint , equalPoints , uniqPoints , arrayToPoint , pointToArray , manhattenDistance , euclideanDistance , pointInCircle , pointsToLines , pointsToTriangles , range , getAngle , getReflectedPoint , findSide , } ;