UNPKG

three-mesh-bvh

Version:

A BVH implementation to speed up raycasting against three.js meshes.

1 lines 339 kB
{"version":3,"file":"index.umd.cjs","sources":["../src/core/Constants.js","../src/core/MeshBVHNode.js","../src/utils/ArrayBoxUtilities.js","../src/core/buildFunctions.js","../src/math/SeparatingAxisBounds.js","../src/math/MathUtilities.js","../src/math/ExtendedTriangle.js","../src/math/OrientedBox.js","../src/utils/ThreeRayIntersectUtilities.js","../src/utils/GeometryRayIntersectUtilities.js","../src/utils/TriangleUtilities.js","../src/utils/PrimitivePool.js","../src/core/nodeBufferFunctions.js","../src/core/castFunctions.js","../src/core/MeshBVH.js","../src/objects/MeshBVHVisualizer.js","../src/debug/Debug.js","../src/utils/ExtensionUtilities.js","../src/gpu/VertexAttributeTexture.js","../src/gpu/MeshBVHUniformStruct.js","../src/gpu/shaderFunctions.js","../src/utils/StaticGeometryGenerator.js"],"sourcesContent":["// Split strategy constants\r\nexport const CENTER = 0;\r\nexport const AVERAGE = 1;\r\nexport const SAH = 2;\r\n\r\n// Traversal constants\r\nexport const NOT_INTERSECTED = 0;\r\nexport const INTERSECTED = 1;\r\nexport const CONTAINED = 2;\r\n\r\n// SAH cost constants\r\n// TODO: hone these costs more. The relative difference between them should be the\r\n// difference in measured time to perform a triangle intersection vs traversing\r\n// bounds.\r\nexport const TRIANGLE_INTERSECT_COST = 1.25;\r\nexport const TRAVERSAL_COST = 1;\r\n\r\n\r\n// Build constants\r\nexport const BYTES_PER_NODE = 6 * 4 + 4 + 4;\r\nexport const IS_LEAFNODE_FLAG = 0xFFFF;\r\n\r\n// EPSILON for computing floating point error during build\r\n// https://en.wikipedia.org/wiki/Machine_epsilon#Values_for_standard_hardware_floating_point_arithmetics\r\nexport const FLOAT32_EPSILON = Math.pow( 2, - 24 );\r\n\r\n","export class MeshBVHNode {\r\n\r\n\tconstructor() {\r\n\r\n\t\t// internal nodes have boundingData, left, right, and splitAxis\r\n\t\t// leaf nodes have offset and count (referring to primitives in the mesh geometry)\r\n\r\n\t}\r\n\r\n}\r\n","export function arrayToBox( nodeIndex32, array, target ) {\r\n\r\n\ttarget.min.x = array[ nodeIndex32 ];\r\n\ttarget.min.y = array[ nodeIndex32 + 1 ];\r\n\ttarget.min.z = array[ nodeIndex32 + 2 ];\r\n\r\n\ttarget.max.x = array[ nodeIndex32 + 3 ];\r\n\ttarget.max.y = array[ nodeIndex32 + 4 ];\r\n\ttarget.max.z = array[ nodeIndex32 + 5 ];\r\n\r\n\treturn target;\r\n\r\n}\r\n\r\nexport function getLongestEdgeIndex( bounds ) {\r\n\r\n\tlet splitDimIdx = - 1;\r\n\tlet splitDist = - Infinity;\r\n\r\n\tfor ( let i = 0; i < 3; i ++ ) {\r\n\r\n\t\tconst dist = bounds[ i + 3 ] - bounds[ i ];\r\n\t\tif ( dist > splitDist ) {\r\n\r\n\t\t\tsplitDist = dist;\r\n\t\t\tsplitDimIdx = i;\r\n\r\n\t\t}\r\n\r\n\t}\r\n\r\n\treturn splitDimIdx;\r\n\r\n}\r\n\r\n// copys bounds a into bounds b\r\nexport function copyBounds( source, target ) {\r\n\r\n\ttarget.set( source );\r\n\r\n}\r\n\r\n// sets bounds target to the union of bounds a and b\r\nexport function unionBounds( a, b, target ) {\r\n\r\n\tlet aVal, bVal;\r\n\tfor ( let d = 0; d < 3; d ++ ) {\r\n\r\n\t\tconst d3 = d + 3;\r\n\r\n\t\t// set the minimum values\r\n\t\taVal = a[ d ];\r\n\t\tbVal = b[ d ];\r\n\t\ttarget[ d ] = aVal < bVal ? aVal : bVal;\r\n\r\n\t\t// set the max values\r\n\t\taVal = a[ d3 ];\r\n\t\tbVal = b[ d3 ];\r\n\t\ttarget[ d3 ] = aVal > bVal ? aVal : bVal;\r\n\r\n\t}\r\n\r\n}\r\n\r\n// expands the given bounds by the provided triangle bounds\r\nexport function expandByTriangleBounds( startIndex, triangleBounds, bounds ) {\r\n\r\n\tfor ( let d = 0; d < 3; d ++ ) {\r\n\r\n\t\tconst tCenter = triangleBounds[ startIndex + 2 * d ];\r\n\t\tconst tHalf = triangleBounds[ startIndex + 2 * d + 1 ];\r\n\r\n\t\tconst tMin = tCenter - tHalf;\r\n\t\tconst tMax = tCenter + tHalf;\r\n\r\n\t\tif ( tMin < bounds[ d ] ) {\r\n\r\n\t\t\tbounds[ d ] = tMin;\r\n\r\n\t\t}\r\n\r\n\t\tif ( tMax > bounds[ d + 3 ] ) {\r\n\r\n\t\t\tbounds[ d + 3 ] = tMax;\r\n\r\n\t\t}\r\n\r\n\t}\r\n\r\n}\r\n\r\n// compute bounds surface area\r\nexport function computeSurfaceArea( bounds ) {\r\n\r\n\tconst d0 = bounds[ 3 ] - bounds[ 0 ];\r\n\tconst d1 = bounds[ 4 ] - bounds[ 1 ];\r\n\tconst d2 = bounds[ 5 ] - bounds[ 2 ];\r\n\r\n\treturn 2 * ( d0 * d1 + d1 * d2 + d2 * d0 );\r\n\r\n}\r\n","import { BufferAttribute } from 'three';\r\nimport { MeshBVHNode } from './MeshBVHNode.js';\r\nimport { getLongestEdgeIndex, computeSurfaceArea, copyBounds, unionBounds, expandByTriangleBounds } from '../utils/ArrayBoxUtilities.js';\r\nimport {\r\n\tCENTER, AVERAGE, SAH, TRIANGLE_INTERSECT_COST, TRAVERSAL_COST,\r\n\tBYTES_PER_NODE, FLOAT32_EPSILON, IS_LEAFNODE_FLAG,\r\n} from './Constants.js';\r\n\r\nfunction ensureIndex( geo, options ) {\r\n\r\n\tif ( ! geo.index ) {\r\n\r\n\t\tconst vertexCount = geo.attributes.position.count;\r\n\t\tconst BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer;\r\n\t\tlet index;\r\n\t\tif ( vertexCount > 65535 ) {\r\n\r\n\t\t\tindex = new Uint32Array( new BufferConstructor( 4 * vertexCount ) );\r\n\r\n\t\t} else {\r\n\r\n\t\t\tindex = new Uint16Array( new BufferConstructor( 2 * vertexCount ) );\r\n\r\n\t\t}\r\n\r\n\t\tgeo.setIndex( new BufferAttribute( index, 1 ) );\r\n\r\n\t\tfor ( let i = 0; i < vertexCount; i ++ ) {\r\n\r\n\t\t\tindex[ i ] = i;\r\n\r\n\t\t}\r\n\r\n\t}\r\n\r\n}\r\n\r\n// Computes the set of { offset, count } ranges which need independent BVH roots. Each\r\n// region in the geometry index that belongs to a different set of material groups requires\r\n// a separate BVH root, so that triangles indices belonging to one group never get swapped\r\n// with triangle indices belongs to another group. For example, if the groups were like this:\r\n//\r\n// [-------------------------------------------------------------]\r\n// |__________________|\r\n// g0 = [0, 20] |______________________||_____________________|\r\n// g1 = [16, 40] g2 = [41, 60]\r\n//\r\n// we would need four BVH roots: [0, 15], [16, 20], [21, 40], [41, 60].\r\nfunction getRootIndexRanges( geo ) {\r\n\r\n\tif ( ! geo.groups || ! geo.groups.length ) {\r\n\r\n\t\treturn [ { offset: 0, count: geo.index.count / 3 } ];\r\n\r\n\t}\r\n\r\n\tconst ranges = [];\r\n\tconst rangeBoundaries = new Set();\r\n\tfor ( const group of geo.groups ) {\r\n\r\n\t\trangeBoundaries.add( group.start );\r\n\t\trangeBoundaries.add( group.start + group.count );\r\n\r\n\t}\r\n\r\n\t// note that if you don't pass in a comparator, it sorts them lexicographically as strings :-(\r\n\tconst sortedBoundaries = Array.from( rangeBoundaries.values() ).sort( ( a, b ) => a - b );\r\n\tfor ( let i = 0; i < sortedBoundaries.length - 1; i ++ ) {\r\n\r\n\t\tconst start = sortedBoundaries[ i ], end = sortedBoundaries[ i + 1 ];\r\n\t\tranges.push( { offset: ( start / 3 ), count: ( end - start ) / 3 } );\r\n\r\n\t}\r\n\r\n\treturn ranges;\r\n\r\n}\r\n\r\n// computes the union of the bounds of all of the given triangles and puts the resulting box in target. If\r\n// centroidTarget is provided then a bounding box is computed for the centroids of the triangles, as well.\r\n// These are computed together to avoid redundant accesses to bounds array.\r\nfunction getBounds( triangleBounds, offset, count, target, centroidTarget = null ) {\r\n\r\n\tlet minx = Infinity;\r\n\tlet miny = Infinity;\r\n\tlet minz = Infinity;\r\n\tlet maxx = - Infinity;\r\n\tlet maxy = - Infinity;\r\n\tlet maxz = - Infinity;\r\n\r\n\tlet cminx = Infinity;\r\n\tlet cminy = Infinity;\r\n\tlet cminz = Infinity;\r\n\tlet cmaxx = - Infinity;\r\n\tlet cmaxy = - Infinity;\r\n\tlet cmaxz = - Infinity;\r\n\r\n\tconst includeCentroid = centroidTarget !== null;\r\n\tfor ( let i = offset * 6, end = ( offset + count ) * 6; i < end; i += 6 ) {\r\n\r\n\t\tconst cx = triangleBounds[ i + 0 ];\r\n\t\tconst hx = triangleBounds[ i + 1 ];\r\n\t\tconst lx = cx - hx;\r\n\t\tconst rx = cx + hx;\r\n\t\tif ( lx < minx ) minx = lx;\r\n\t\tif ( rx > maxx ) maxx = rx;\r\n\t\tif ( includeCentroid && cx < cminx ) cminx = cx;\r\n\t\tif ( includeCentroid && cx > cmaxx ) cmaxx = cx;\r\n\r\n\t\tconst cy = triangleBounds[ i + 2 ];\r\n\t\tconst hy = triangleBounds[ i + 3 ];\r\n\t\tconst ly = cy - hy;\r\n\t\tconst ry = cy + hy;\r\n\t\tif ( ly < miny ) miny = ly;\r\n\t\tif ( ry > maxy ) maxy = ry;\r\n\t\tif ( includeCentroid && cy < cminy ) cminy = cy;\r\n\t\tif ( includeCentroid && cy > cmaxy ) cmaxy = cy;\r\n\r\n\t\tconst cz = triangleBounds[ i + 4 ];\r\n\t\tconst hz = triangleBounds[ i + 5 ];\r\n\t\tconst lz = cz - hz;\r\n\t\tconst rz = cz + hz;\r\n\t\tif ( lz < minz ) minz = lz;\r\n\t\tif ( rz > maxz ) maxz = rz;\r\n\t\tif ( includeCentroid && cz < cminz ) cminz = cz;\r\n\t\tif ( includeCentroid && cz > cmaxz ) cmaxz = cz;\r\n\r\n\t}\r\n\r\n\ttarget[ 0 ] = minx;\r\n\ttarget[ 1 ] = miny;\r\n\ttarget[ 2 ] = minz;\r\n\r\n\ttarget[ 3 ] = maxx;\r\n\ttarget[ 4 ] = maxy;\r\n\ttarget[ 5 ] = maxz;\r\n\r\n\tif ( includeCentroid ) {\r\n\r\n\t\tcentroidTarget[ 0 ] = cminx;\r\n\t\tcentroidTarget[ 1 ] = cminy;\r\n\t\tcentroidTarget[ 2 ] = cminz;\r\n\r\n\t\tcentroidTarget[ 3 ] = cmaxx;\r\n\t\tcentroidTarget[ 4 ] = cmaxy;\r\n\t\tcentroidTarget[ 5 ] = cmaxz;\r\n\r\n\t}\r\n\r\n}\r\n\r\n// A stand alone function for retrieving the centroid bounds.\r\nfunction getCentroidBounds( triangleBounds, offset, count, centroidTarget ) {\r\n\r\n\tlet cminx = Infinity;\r\n\tlet cminy = Infinity;\r\n\tlet cminz = Infinity;\r\n\tlet cmaxx = - Infinity;\r\n\tlet cmaxy = - Infinity;\r\n\tlet cmaxz = - Infinity;\r\n\r\n\tfor ( let i = offset * 6, end = ( offset + count ) * 6; i < end; i += 6 ) {\r\n\r\n\t\tconst cx = triangleBounds[ i + 0 ];\r\n\t\tif ( cx < cminx ) cminx = cx;\r\n\t\tif ( cx > cmaxx ) cmaxx = cx;\r\n\r\n\t\tconst cy = triangleBounds[ i + 2 ];\r\n\t\tif ( cy < cminy ) cminy = cy;\r\n\t\tif ( cy > cmaxy ) cmaxy = cy;\r\n\r\n\t\tconst cz = triangleBounds[ i + 4 ];\r\n\t\tif ( cz < cminz ) cminz = cz;\r\n\t\tif ( cz > cmaxz ) cmaxz = cz;\r\n\r\n\t}\r\n\r\n\tcentroidTarget[ 0 ] = cminx;\r\n\tcentroidTarget[ 1 ] = cminy;\r\n\tcentroidTarget[ 2 ] = cminz;\r\n\r\n\tcentroidTarget[ 3 ] = cmaxx;\r\n\tcentroidTarget[ 4 ] = cmaxy;\r\n\tcentroidTarget[ 5 ] = cmaxz;\r\n\r\n}\r\n\r\n\r\n// reorders `tris` such that for `count` elements after `offset`, elements on the left side of the split\r\n// will be on the left and elements on the right side of the split will be on the right. returns the index\r\n// of the first element on the right side, or offset + count if there are no elements on the right side.\r\nfunction partition( index, triangleBounds, offset, count, split ) {\r\n\r\n\tlet left = offset;\r\n\tlet right = offset + count - 1;\r\n\tconst pos = split.pos;\r\n\tconst axisOffset = split.axis * 2;\r\n\r\n\t// hoare partitioning, see e.g. https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme\r\n\twhile ( true ) {\r\n\r\n\t\twhile ( left <= right && triangleBounds[ left * 6 + axisOffset ] < pos ) {\r\n\r\n\t\t\tleft ++;\r\n\r\n\t\t}\r\n\r\n\r\n\t\t// if a triangle center lies on the partition plane it is considered to be on the right side\r\n\t\twhile ( left <= right && triangleBounds[ right * 6 + axisOffset ] >= pos ) {\r\n\r\n\t\t\tright --;\r\n\r\n\t\t}\r\n\r\n\t\tif ( left < right ) {\r\n\r\n\t\t\t// we need to swap all of the information associated with the triangles at index\r\n\t\t\t// left and right; that's the verts in the geometry index, the bounds,\r\n\t\t\t// and perhaps the SAH planes\r\n\r\n\t\t\tfor ( let i = 0; i < 3; i ++ ) {\r\n\r\n\t\t\t\tlet t0 = index[ left * 3 + i ];\r\n\t\t\t\tindex[ left * 3 + i ] = index[ right * 3 + i ];\r\n\t\t\t\tindex[ right * 3 + i ] = t0;\r\n\r\n\t\t\t\tlet t1 = triangleBounds[ left * 6 + i * 2 + 0 ];\r\n\t\t\t\ttriangleBounds[ left * 6 + i * 2 + 0 ] = triangleBounds[ right * 6 + i * 2 + 0 ];\r\n\t\t\t\ttriangleBounds[ right * 6 + i * 2 + 0 ] = t1;\r\n\r\n\t\t\t\tlet t2 = triangleBounds[ left * 6 + i * 2 + 1 ];\r\n\t\t\t\ttriangleBounds[ left * 6 + i * 2 + 1 ] = triangleBounds[ right * 6 + i * 2 + 1 ];\r\n\t\t\t\ttriangleBounds[ right * 6 + i * 2 + 1 ] = t2;\r\n\r\n\t\t\t}\r\n\r\n\t\t\tleft ++;\r\n\t\t\tright --;\r\n\r\n\t\t} else {\r\n\r\n\t\t\treturn left;\r\n\r\n\t\t}\r\n\r\n\t}\r\n\r\n}\r\n\r\nconst BIN_COUNT = 32;\r\nconst binsSort = ( a, b ) => a.candidate - b.candidate;\r\nconst sahBins = new Array( BIN_COUNT ).fill().map( () => {\r\n\r\n\treturn {\r\n\r\n\t\tcount: 0,\r\n\t\tbounds: new Float32Array( 6 ),\r\n\t\trightCacheBounds: new Float32Array( 6 ),\r\n\t\tleftCacheBounds: new Float32Array( 6 ),\r\n\t\tcandidate: 0,\r\n\r\n\t};\r\n\r\n} );\r\nconst leftBounds = new Float32Array( 6 );\r\n\r\nfunction getOptimalSplit( nodeBoundingData, centroidBoundingData, triangleBounds, offset, count, strategy ) {\r\n\r\n\tlet axis = - 1;\r\n\tlet pos = 0;\r\n\r\n\t// Center\r\n\tif ( strategy === CENTER ) {\r\n\r\n\t\taxis = getLongestEdgeIndex( centroidBoundingData );\r\n\t\tif ( axis !== - 1 ) {\r\n\r\n\t\t\tpos = ( centroidBoundingData[ axis ] + centroidBoundingData[ axis + 3 ] ) / 2;\r\n\r\n\t\t}\r\n\r\n\t} else if ( strategy === AVERAGE ) {\r\n\r\n\t\taxis = getLongestEdgeIndex( nodeBoundingData );\r\n\t\tif ( axis !== - 1 ) {\r\n\r\n\t\t\tpos = getAverage( triangleBounds, offset, count, axis );\r\n\r\n\t\t}\r\n\r\n\t} else if ( strategy === SAH ) {\r\n\r\n\t\tconst rootSurfaceArea = computeSurfaceArea( nodeBoundingData );\r\n\t\tlet bestCost = TRIANGLE_INTERSECT_COST * count;\r\n\r\n\t\t// iterate over all axes\r\n\t\tconst cStart = offset * 6;\r\n\t\tconst cEnd = ( offset + count ) * 6;\r\n\t\tfor ( let a = 0; a < 3; a ++ ) {\r\n\r\n\t\t\tconst axisLeft = centroidBoundingData[ a ];\r\n\t\t\tconst axisRight = centroidBoundingData[ a + 3 ];\r\n\t\t\tconst axisLength = axisRight - axisLeft;\r\n\t\t\tconst binWidth = axisLength / BIN_COUNT;\r\n\r\n\t\t\t// If we have fewer triangles than we're planning to split then just check all\r\n\t\t\t// the triangle positions because it will be faster.\r\n\t\t\tif ( count < BIN_COUNT / 4 ) {\r\n\r\n\t\t\t\t// initialize the bin candidates\r\n\t\t\t\tconst truncatedBins = [ ...sahBins ];\r\n\t\t\t\ttruncatedBins.length = count;\r\n\r\n\t\t\t\t// set the candidates\r\n\t\t\t\tlet b = 0;\r\n\t\t\t\tfor ( let c = cStart; c < cEnd; c += 6, b ++ ) {\r\n\r\n\t\t\t\t\tconst bin = truncatedBins[ b ];\r\n\t\t\t\t\tbin.candidate = triangleBounds[ c + 2 * a ];\r\n\t\t\t\t\tbin.count = 0;\r\n\r\n\t\t\t\t\tconst {\r\n\t\t\t\t\t\tbounds,\r\n\t\t\t\t\t\tleftCacheBounds,\r\n\t\t\t\t\t\trightCacheBounds,\r\n\t\t\t\t\t} = bin;\r\n\t\t\t\t\tfor ( let d = 0; d < 3; d ++ ) {\r\n\r\n\t\t\t\t\t\trightCacheBounds[ d ] = Infinity;\r\n\t\t\t\t\t\trightCacheBounds[ d + 3 ] = - Infinity;\r\n\r\n\t\t\t\t\t\tleftCacheBounds[ d ] = Infinity;\r\n\t\t\t\t\t\tleftCacheBounds[ d + 3 ] = - Infinity;\r\n\r\n\t\t\t\t\t\tbounds[ d ] = Infinity;\r\n\t\t\t\t\t\tbounds[ d + 3 ] = - Infinity;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\texpandByTriangleBounds( c, triangleBounds, bounds );\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\ttruncatedBins.sort( binsSort );\r\n\r\n\t\t\t\t// remove redundant splits\r\n\t\t\t\tlet splitCount = count;\r\n\t\t\t\tfor ( let bi = 0; bi < splitCount; bi ++ ) {\r\n\r\n\t\t\t\t\tconst bin = truncatedBins[ bi ];\r\n\t\t\t\t\twhile ( bi + 1 < splitCount && truncatedBins[ bi + 1 ].candidate === bin.candidate ) {\r\n\r\n\t\t\t\t\t\ttruncatedBins.splice( bi + 1, 1 );\r\n\t\t\t\t\t\tsplitCount --;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// find the appropriate bin for each triangle and expand the bounds.\r\n\t\t\t\tfor ( let c = cStart; c < cEnd; c += 6 ) {\r\n\r\n\t\t\t\t\tconst center = triangleBounds[ c + 2 * a ];\r\n\t\t\t\t\tfor ( let bi = 0; bi < splitCount; bi ++ ) {\r\n\r\n\t\t\t\t\t\tconst bin = truncatedBins[ bi ];\r\n\t\t\t\t\t\tif ( center >= bin.candidate ) {\r\n\r\n\t\t\t\t\t\t\texpandByTriangleBounds( c, triangleBounds, bin.rightCacheBounds );\r\n\r\n\t\t\t\t\t\t} else {\r\n\r\n\t\t\t\t\t\t\texpandByTriangleBounds( c, triangleBounds, bin.leftCacheBounds );\r\n\t\t\t\t\t\t\tbin.count ++;\r\n\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// expand all the bounds\r\n\t\t\t\tfor ( let bi = 0; bi < splitCount; bi ++ ) {\r\n\r\n\t\t\t\t\tconst bin = truncatedBins[ bi ];\r\n\t\t\t\t\tconst leftCount = bin.count;\r\n\t\t\t\t\tconst rightCount = count - bin.count;\r\n\r\n\t\t\t\t\t// check the cost of this split\r\n\t\t\t\t\tconst leftBounds = bin.leftCacheBounds;\r\n\t\t\t\t\tconst rightBounds = bin.rightCacheBounds;\r\n\r\n\t\t\t\t\tlet leftProb = 0;\r\n\t\t\t\t\tif ( leftCount !== 0 ) {\r\n\r\n\t\t\t\t\t\tleftProb = computeSurfaceArea( leftBounds ) / rootSurfaceArea;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tlet rightProb = 0;\r\n\t\t\t\t\tif ( rightCount !== 0 ) {\r\n\r\n\t\t\t\t\t\trightProb = computeSurfaceArea( rightBounds ) / rootSurfaceArea;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tconst cost = TRAVERSAL_COST + TRIANGLE_INTERSECT_COST * (\r\n\t\t\t\t\t\tleftProb * leftCount + rightProb * rightCount\r\n\t\t\t\t\t);\r\n\r\n\t\t\t\t\tif ( cost < bestCost ) {\r\n\r\n\t\t\t\t\t\taxis = a;\r\n\t\t\t\t\t\tbestCost = cost;\r\n\t\t\t\t\t\tpos = bin.candidate;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t} else {\r\n\r\n\t\t\t\t// reset the bins\r\n\t\t\t\tfor ( let i = 0; i < BIN_COUNT; i ++ ) {\r\n\r\n\t\t\t\t\tconst bin = sahBins[ i ];\r\n\t\t\t\t\tbin.count = 0;\r\n\t\t\t\t\tbin.candidate = axisLeft + binWidth + i * binWidth;\r\n\r\n\t\t\t\t\tconst bounds = bin.bounds;\r\n\t\t\t\t\tfor ( let d = 0; d < 3; d ++ ) {\r\n\r\n\t\t\t\t\t\tbounds[ d ] = Infinity;\r\n\t\t\t\t\t\tbounds[ d + 3 ] = - Infinity;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// iterate over all center positions\r\n\t\t\t\tfor ( let c = cStart; c < cEnd; c += 6 ) {\r\n\r\n\t\t\t\t\tconst triCenter = triangleBounds[ c + 2 * a ];\r\n\t\t\t\t\tconst relativeCenter = triCenter - axisLeft;\r\n\r\n\t\t\t\t\t// in the partition function if the centroid lies on the split plane then it is\r\n\t\t\t\t\t// considered to be on the right side of the split\r\n\t\t\t\t\tlet binIndex = ~ ~ ( relativeCenter / binWidth );\r\n\t\t\t\t\tif ( binIndex >= BIN_COUNT ) binIndex = BIN_COUNT - 1;\r\n\r\n\t\t\t\t\tconst bin = sahBins[ binIndex ];\r\n\t\t\t\t\tbin.count ++;\r\n\r\n\t\t\t\t\texpandByTriangleBounds( c, triangleBounds, bin.bounds );\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// cache the unioned bounds from right to left so we don't have to regenerate them each time\r\n\t\t\t\tconst lastBin = sahBins[ BIN_COUNT - 1 ];\r\n\t\t\t\tcopyBounds( lastBin.bounds, lastBin.rightCacheBounds );\r\n\t\t\t\tfor ( let i = BIN_COUNT - 2; i >= 0; i -- ) {\r\n\r\n\t\t\t\t\tconst bin = sahBins[ i ];\r\n\t\t\t\t\tconst nextBin = sahBins[ i + 1 ];\r\n\t\t\t\t\tunionBounds( bin.bounds, nextBin.rightCacheBounds, bin.rightCacheBounds );\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\tlet leftCount = 0;\r\n\t\t\t\tfor ( let i = 0; i < BIN_COUNT - 1; i ++ ) {\r\n\r\n\t\t\t\t\tconst bin = sahBins[ i ];\r\n\t\t\t\t\tconst binCount = bin.count;\r\n\t\t\t\t\tconst bounds = bin.bounds;\r\n\r\n\t\t\t\t\tconst nextBin = sahBins[ i + 1 ];\r\n\t\t\t\t\tconst rightBounds = nextBin.rightCacheBounds;\r\n\r\n\t\t\t\t\t// dont do anything with the bounds if the new bounds have no triangles\r\n\t\t\t\t\tif ( binCount !== 0 ) {\r\n\r\n\t\t\t\t\t\tif ( leftCount === 0 ) {\r\n\r\n\t\t\t\t\t\t\tcopyBounds( bounds, leftBounds );\r\n\r\n\t\t\t\t\t\t} else {\r\n\r\n\t\t\t\t\t\t\tunionBounds( bounds, leftBounds, leftBounds );\r\n\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tleftCount += binCount;\r\n\r\n\t\t\t\t\t// check the cost of this split\r\n\t\t\t\t\tlet leftProb = 0;\r\n\t\t\t\t\tlet rightProb = 0;\r\n\r\n\t\t\t\t\tif ( leftCount !== 0 ) {\r\n\r\n\t\t\t\t\t\tleftProb = computeSurfaceArea( leftBounds ) / rootSurfaceArea;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tconst rightCount = count - leftCount;\r\n\t\t\t\t\tif ( rightCount !== 0 ) {\r\n\r\n\t\t\t\t\t\trightProb = computeSurfaceArea( rightBounds ) / rootSurfaceArea;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tconst cost = TRAVERSAL_COST + TRIANGLE_INTERSECT_COST * (\r\n\t\t\t\t\t\tleftProb * leftCount + rightProb * rightCount\r\n\t\t\t\t\t);\r\n\r\n\t\t\t\t\tif ( cost < bestCost ) {\r\n\r\n\t\t\t\t\t\taxis = a;\r\n\t\t\t\t\t\tbestCost = cost;\r\n\t\t\t\t\t\tpos = bin.candidate;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t} else {\r\n\r\n\t\tconsole.warn( `MeshBVH: Invalid build strategy value ${ strategy } used.` );\r\n\r\n\t}\r\n\r\n\treturn { axis, pos };\r\n\r\n}\r\n\r\n// returns the average coordinate on the specified axis of the all the provided triangles\r\nfunction getAverage( triangleBounds, offset, count, axis ) {\r\n\r\n\tlet avg = 0;\r\n\tfor ( let i = offset, end = offset + count; i < end; i ++ ) {\r\n\r\n\t\tavg += triangleBounds[ i * 6 + axis * 2 ];\r\n\r\n\t}\r\n\r\n\treturn avg / count;\r\n\r\n}\r\n\r\n// precomputes the bounding box for each triangle; required for quickly calculating tree splits.\r\n// result is an array of size tris.length * 6 where triangle i maps to a\r\n// [x_center, x_delta, y_center, y_delta, z_center, z_delta] tuple starting at index i * 6,\r\n// representing the center and half-extent in each dimension of triangle i\r\nfunction computeTriangleBounds( geo, fullBounds ) {\r\n\r\n\tconst posAttr = geo.attributes.position;\r\n\tconst posArr = posAttr.array;\r\n\tconst index = geo.index.array;\r\n\tconst triCount = index.length / 3;\r\n\tconst triangleBounds = new Float32Array( triCount * 6 );\r\n\r\n\t// support for an interleaved position buffer\r\n\tconst bufferOffset = posAttr.offset || 0;\r\n\tlet stride = 3;\r\n\tif ( posAttr.isInterleavedBufferAttribute ) {\r\n\r\n\t\tstride = posAttr.data.stride;\r\n\r\n\t}\r\n\r\n\tfor ( let tri = 0; tri < triCount; tri ++ ) {\r\n\r\n\t\tconst tri3 = tri * 3;\r\n\t\tconst tri6 = tri * 6;\r\n\t\tconst ai = index[ tri3 + 0 ] * stride + bufferOffset;\r\n\t\tconst bi = index[ tri3 + 1 ] * stride + bufferOffset;\r\n\t\tconst ci = index[ tri3 + 2 ] * stride + bufferOffset;\r\n\r\n\t\tfor ( let el = 0; el < 3; el ++ ) {\r\n\r\n\t\t\tconst a = posArr[ ai + el ];\r\n\t\t\tconst b = posArr[ bi + el ];\r\n\t\t\tconst c = posArr[ ci + el ];\r\n\r\n\t\t\tlet min = a;\r\n\t\t\tif ( b < min ) min = b;\r\n\t\t\tif ( c < min ) min = c;\r\n\r\n\t\t\tlet max = a;\r\n\t\t\tif ( b > max ) max = b;\r\n\t\t\tif ( c > max ) max = c;\r\n\r\n\t\t\t// Increase the bounds size by float32 epsilon to avoid precision errors when\r\n\t\t\t// converting to 32 bit float. Scale the epsilon by the size of the numbers being\r\n\t\t\t// worked with.\r\n\t\t\tconst halfExtents = ( max - min ) / 2;\r\n\t\t\tconst el2 = el * 2;\r\n\t\t\ttriangleBounds[ tri6 + el2 + 0 ] = min + halfExtents;\r\n\t\t\ttriangleBounds[ tri6 + el2 + 1 ] = halfExtents + ( Math.abs( min ) + halfExtents ) * FLOAT32_EPSILON;\r\n\r\n\t\t\tif ( min < fullBounds[ el ] ) fullBounds[ el ] = min;\r\n\t\t\tif ( max > fullBounds[ el + 3 ] ) fullBounds[ el + 3 ] = max;\r\n\r\n\t\t}\r\n\r\n\t}\r\n\r\n\treturn triangleBounds;\r\n\r\n}\r\n\r\nexport function buildTree( geo, options ) {\r\n\r\n\tfunction triggerProgress( trianglesProcessed ) {\r\n\r\n\t\tif ( onProgress ) {\r\n\r\n\t\t\tonProgress( trianglesProcessed / totalTriangles );\r\n\r\n\t\t}\r\n\r\n\t}\r\n\r\n\t// either recursively splits the given node, creating left and right subtrees for it, or makes it a leaf node,\r\n\t// recording the offset and count of its triangles and writing them into the reordered geometry index.\r\n\tfunction splitNode( node, offset, count, centroidBoundingData = null, depth = 0 ) {\r\n\r\n\t\tif ( ! reachedMaxDepth && depth >= maxDepth ) {\r\n\r\n\t\t\treachedMaxDepth = true;\r\n\t\t\tif ( verbose ) {\r\n\r\n\t\t\t\tconsole.warn( `MeshBVH: Max depth of ${ maxDepth } reached when generating BVH. Consider increasing maxDepth.` );\r\n\t\t\t\tconsole.warn( geo );\r\n\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t\t// early out if we've met our capacity\r\n\t\tif ( count <= maxLeafTris || depth >= maxDepth ) {\r\n\r\n\t\t\ttriggerProgress( offset + count );\r\n\t\t\tnode.offset = offset;\r\n\t\t\tnode.count = count;\r\n\t\t\treturn node;\r\n\r\n\t\t}\r\n\r\n\t\t// Find where to split the volume\r\n\t\tconst split = getOptimalSplit( node.boundingData, centroidBoundingData, triangleBounds, offset, count, strategy );\r\n\t\tif ( split.axis === - 1 ) {\r\n\r\n\t\t\ttriggerProgress( offset + count );\r\n\t\t\tnode.offset = offset;\r\n\t\t\tnode.count = count;\r\n\t\t\treturn node;\r\n\r\n\t\t}\r\n\r\n\t\tconst splitOffset = partition( indexArray, triangleBounds, offset, count, split );\r\n\r\n\t\t// create the two new child nodes\r\n\t\tif ( splitOffset === offset || splitOffset === offset + count ) {\r\n\r\n\t\t\ttriggerProgress( offset + count );\r\n\t\t\tnode.offset = offset;\r\n\t\t\tnode.count = count;\r\n\r\n\t\t} else {\r\n\r\n\t\t\tnode.splitAxis = split.axis;\r\n\r\n\t\t\t// create the left child and compute its bounding box\r\n\t\t\tconst left = new MeshBVHNode();\r\n\t\t\tconst lstart = offset;\r\n\t\t\tconst lcount = splitOffset - offset;\r\n\t\t\tnode.left = left;\r\n\t\t\tleft.boundingData = new Float32Array( 6 );\r\n\r\n\t\t\tgetBounds( triangleBounds, lstart, lcount, left.boundingData, cacheCentroidBoundingData );\r\n\t\t\tsplitNode( left, lstart, lcount, cacheCentroidBoundingData, depth + 1 );\r\n\r\n\t\t\t// repeat for right\r\n\t\t\tconst right = new MeshBVHNode();\r\n\t\t\tconst rstart = splitOffset;\r\n\t\t\tconst rcount = count - lcount;\r\n\t\t\tnode.right = right;\r\n\t\t\tright.boundingData = new Float32Array( 6 );\r\n\r\n\t\t\tgetBounds( triangleBounds, rstart, rcount, right.boundingData, cacheCentroidBoundingData );\r\n\t\t\tsplitNode( right, rstart, rcount, cacheCentroidBoundingData, depth + 1 );\r\n\r\n\t\t}\r\n\r\n\t\treturn node;\r\n\r\n\t}\r\n\r\n\tensureIndex( geo, options );\r\n\r\n\t// Compute the full bounds of the geometry at the same time as triangle bounds because\r\n\t// we'll need it for the root bounds in the case with no groups and it should be fast here.\r\n\t// We can't use the geometrying bounding box if it's available because it may be out of date.\r\n\tconst fullBounds = new Float32Array( 6 );\r\n\tconst cacheCentroidBoundingData = new Float32Array( 6 );\r\n\tconst triangleBounds = computeTriangleBounds( geo, fullBounds );\r\n\tconst indexArray = geo.index.array;\r\n\tconst maxDepth = options.maxDepth;\r\n\tconst verbose = options.verbose;\r\n\tconst maxLeafTris = options.maxLeafTris;\r\n\tconst strategy = options.strategy;\r\n\tconst onProgress = options.onProgress;\r\n\tconst totalTriangles = geo.index.count / 3;\r\n\tlet reachedMaxDepth = false;\r\n\r\n\tconst roots = [];\r\n\tconst ranges = getRootIndexRanges( geo );\r\n\r\n\tif ( ranges.length === 1 ) {\r\n\r\n\t\tconst range = ranges[ 0 ];\r\n\t\tconst root = new MeshBVHNode();\r\n\t\troot.boundingData = fullBounds;\r\n\t\tgetCentroidBounds( triangleBounds, range.offset, range.count, cacheCentroidBoundingData );\r\n\r\n\t\tsplitNode( root, range.offset, range.count, cacheCentroidBoundingData );\r\n\t\troots.push( root );\r\n\r\n\t} else {\r\n\r\n\t\tfor ( let range of ranges ) {\r\n\r\n\t\t\tconst root = new MeshBVHNode();\r\n\t\t\troot.boundingData = new Float32Array( 6 );\r\n\t\t\tgetBounds( triangleBounds, range.offset, range.count, root.boundingData, cacheCentroidBoundingData );\r\n\r\n\t\t\tsplitNode( root, range.offset, range.count, cacheCentroidBoundingData );\r\n\t\t\troots.push( root );\r\n\r\n\t\t}\r\n\r\n\t}\r\n\r\n\treturn roots;\r\n\r\n}\r\n\r\nexport function buildPackedTree( geo, options ) {\r\n\r\n\t// boundingData \t\t\t\t: 6 float32\r\n\t// right / offset \t\t\t\t: 1 uint32\r\n\t// splitAxis / isLeaf + count \t: 1 uint32 / 2 uint16\r\n\tconst roots = buildTree( geo, options );\r\n\r\n\tlet float32Array;\r\n\tlet uint32Array;\r\n\tlet uint16Array;\r\n\tconst packedRoots = [];\r\n\tconst BufferConstructor = options.useSharedArrayBuffer ? SharedArrayBuffer : ArrayBuffer;\r\n\tfor ( let i = 0; i < roots.length; i ++ ) {\r\n\r\n\t\tconst root = roots[ i ];\r\n\t\tlet nodeCount = countNodes( root );\r\n\r\n\t\tconst buffer = new BufferConstructor( BYTES_PER_NODE * nodeCount );\r\n\t\tfloat32Array = new Float32Array( buffer );\r\n\t\tuint32Array = new Uint32Array( buffer );\r\n\t\tuint16Array = new Uint16Array( buffer );\r\n\t\tpopulateBuffer( 0, root );\r\n\t\tpackedRoots.push( buffer );\r\n\r\n\t}\r\n\r\n\treturn packedRoots;\r\n\r\n\tfunction countNodes( node ) {\r\n\r\n\t\tif ( node.count ) {\r\n\r\n\t\t\treturn 1;\r\n\r\n\t\t} else {\r\n\r\n\t\t\treturn 1 + countNodes( node.left ) + countNodes( node.right );\r\n\r\n\t\t}\r\n\r\n\t}\r\n\r\n\tfunction populateBuffer( byteOffset, node ) {\r\n\r\n\t\tconst stride4Offset = byteOffset / 4;\r\n\t\tconst stride2Offset = byteOffset / 2;\r\n\t\tconst isLeaf = ! ! node.count;\r\n\t\tconst boundingData = node.boundingData;\r\n\t\tfor ( let i = 0; i < 6; i ++ ) {\r\n\r\n\t\t\tfloat32Array[ stride4Offset + i ] = boundingData[ i ];\r\n\r\n\t\t}\r\n\r\n\t\tif ( isLeaf ) {\r\n\r\n\t\t\tconst offset = node.offset;\r\n\t\t\tconst count = node.count;\r\n\t\t\tuint32Array[ stride4Offset + 6 ] = offset;\r\n\t\t\tuint16Array[ stride2Offset + 14 ] = count;\r\n\t\t\tuint16Array[ stride2Offset + 15 ] = IS_LEAFNODE_FLAG;\r\n\t\t\treturn byteOffset + BYTES_PER_NODE;\r\n\r\n\t\t} else {\r\n\r\n\t\t\tconst left = node.left;\r\n\t\t\tconst right = node.right;\r\n\t\t\tconst splitAxis = node.splitAxis;\r\n\r\n\t\t\tlet nextUnusedPointer;\r\n\t\t\tnextUnusedPointer = populateBuffer( byteOffset + BYTES_PER_NODE, left );\r\n\r\n\t\t\tif ( ( nextUnusedPointer / 4 ) > Math.pow( 2, 32 ) ) {\r\n\r\n\t\t\t\tthrow new Error( 'MeshBVH: Cannot store child pointer greater than 32 bits.' );\r\n\r\n\t\t\t}\r\n\r\n\t\t\tuint32Array[ stride4Offset + 6 ] = nextUnusedPointer / 4;\r\n\t\t\tnextUnusedPointer = populateBuffer( nextUnusedPointer, right );\r\n\r\n\t\t\tuint32Array[ stride4Offset + 7 ] = splitAxis;\r\n\t\t\treturn nextUnusedPointer;\r\n\r\n\t\t}\r\n\r\n\t}\r\n\r\n}\r\n","import { Vector3 } from 'three';\r\n\r\nexport class SeparatingAxisBounds {\r\n\r\n\tconstructor() {\r\n\r\n\t\tthis.min = Infinity;\r\n\t\tthis.max = - Infinity;\r\n\r\n\t}\r\n\r\n\tsetFromPointsField( points, field ) {\r\n\r\n\t\tlet min = Infinity;\r\n\t\tlet max = - Infinity;\r\n\t\tfor ( let i = 0, l = points.length; i < l; i ++ ) {\r\n\r\n\t\t\tconst p = points[ i ];\r\n\t\t\tconst val = p[ field ];\r\n\t\t\tmin = val < min ? val : min;\r\n\t\t\tmax = val > max ? val : max;\r\n\r\n\t\t}\r\n\r\n\t\tthis.min = min;\r\n\t\tthis.max = max;\r\n\r\n\t}\r\n\r\n\tsetFromPoints( axis, points ) {\r\n\r\n\t\tlet min = Infinity;\r\n\t\tlet max = - Infinity;\r\n\t\tfor ( let i = 0, l = points.length; i < l; i ++ ) {\r\n\r\n\t\t\tconst p = points[ i ];\r\n\t\t\tconst val = axis.dot( p );\r\n\t\t\tmin = val < min ? val : min;\r\n\t\t\tmax = val > max ? val : max;\r\n\r\n\t\t}\r\n\r\n\t\tthis.min = min;\r\n\t\tthis.max = max;\r\n\r\n\t}\r\n\r\n\tisSeparated( other ) {\r\n\r\n\t\treturn this.min > other.max || other.min > this.max;\r\n\r\n\t}\r\n\r\n}\r\n\r\nSeparatingAxisBounds.prototype.setFromBox = ( function () {\r\n\r\n\tconst p = new Vector3();\r\n\treturn function setFromBox( axis, box ) {\r\n\r\n\t\tconst boxMin = box.min;\r\n\t\tconst boxMax = box.max;\r\n\t\tlet min = Infinity;\r\n\t\tlet max = - Infinity;\r\n\t\tfor ( let x = 0; x <= 1; x ++ ) {\r\n\r\n\t\t\tfor ( let y = 0; y <= 1; y ++ ) {\r\n\r\n\t\t\t\tfor ( let z = 0; z <= 1; z ++ ) {\r\n\r\n\t\t\t\t\tp.x = boxMin.x * x + boxMax.x * ( 1 - x );\r\n\t\t\t\t\tp.y = boxMin.y * y + boxMax.y * ( 1 - y );\r\n\t\t\t\t\tp.z = boxMin.z * z + boxMax.z * ( 1 - z );\r\n\r\n\t\t\t\t\tconst val = axis.dot( p );\r\n\t\t\t\t\tmin = Math.min( val, min );\r\n\t\t\t\t\tmax = Math.max( val, max );\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t\tthis.min = min;\r\n\t\tthis.max = max;\r\n\r\n\t};\r\n\r\n} )();\r\n\r\nexport const areIntersecting = ( function () {\r\n\r\n\tconst cacheSatBounds = new SeparatingAxisBounds();\r\n\treturn function areIntersecting( shape1, shape2 ) {\r\n\r\n\t\tconst points1 = shape1.points;\r\n\t\tconst satAxes1 = shape1.satAxes;\r\n\t\tconst satBounds1 = shape1.satBounds;\r\n\r\n\t\tconst points2 = shape2.points;\r\n\t\tconst satAxes2 = shape2.satAxes;\r\n\t\tconst satBounds2 = shape2.satBounds;\r\n\r\n\t\t// check axes of the first shape\r\n\t\tfor ( let i = 0; i < 3; i ++ ) {\r\n\r\n\t\t\tconst sb = satBounds1[ i ];\r\n\t\t\tconst sa = satAxes1[ i ];\r\n\t\t\tcacheSatBounds.setFromPoints( sa, points2 );\r\n\t\t\tif ( sb.isSeparated( cacheSatBounds ) ) return false;\r\n\r\n\t\t}\r\n\r\n\t\t// check axes of the second shape\r\n\t\tfor ( let i = 0; i < 3; i ++ ) {\r\n\r\n\t\t\tconst sb = satBounds2[ i ];\r\n\t\t\tconst sa = satAxes2[ i ];\r\n\t\t\tcacheSatBounds.setFromPoints( sa, points1 );\r\n\t\t\tif ( sb.isSeparated( cacheSatBounds ) ) return false;\r\n\r\n\t\t}\r\n\r\n\t};\r\n\r\n} )();\r\n","import { Vector3, Vector2, Plane, Line3 } from 'three';\r\n\r\nexport const closestPointLineToLine = ( function () {\r\n\r\n\t// https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Line.cpp#L56\r\n\tconst dir1 = new Vector3();\r\n\tconst dir2 = new Vector3();\r\n\tconst v02 = new Vector3();\r\n\treturn function closestPointLineToLine( l1, l2, result ) {\r\n\r\n\t\tconst v0 = l1.start;\r\n\t\tconst v10 = dir1;\r\n\t\tconst v2 = l2.start;\r\n\t\tconst v32 = dir2;\r\n\r\n\t\tv02.subVectors( v0, v2 );\r\n\t\tdir1.subVectors( l1.end, l2.start );\r\n\t\tdir2.subVectors( l2.end, l2.start );\r\n\r\n\t\t// float d0232 = v02.Dot(v32);\r\n\t\tconst d0232 = v02.dot( v32 );\r\n\r\n\t\t// float d3210 = v32.Dot(v10);\r\n\t\tconst d3210 = v32.dot( v10 );\r\n\r\n\t\t// float d3232 = v32.Dot(v32);\r\n\t\tconst d3232 = v32.dot( v32 );\r\n\r\n\t\t// float d0210 = v02.Dot(v10);\r\n\t\tconst d0210 = v02.dot( v10 );\r\n\r\n\t\t// float d1010 = v10.Dot(v10);\r\n\t\tconst d1010 = v10.dot( v10 );\r\n\r\n\t\t// float denom = d1010*d3232 - d3210*d3210;\r\n\t\tconst denom = d1010 * d3232 - d3210 * d3210;\r\n\r\n\t\tlet d, d2;\r\n\t\tif ( denom !== 0 ) {\r\n\r\n\t\t\td = ( d0232 * d3210 - d0210 * d3232 ) / denom;\r\n\r\n\t\t} else {\r\n\r\n\t\t\td = 0;\r\n\r\n\t\t}\r\n\r\n\t\td2 = ( d0232 + d * d3210 ) / d3232;\r\n\r\n\t\tresult.x = d;\r\n\t\tresult.y = d2;\r\n\r\n\t};\r\n\r\n} )();\r\n\r\nexport const closestPointsSegmentToSegment = ( function () {\r\n\r\n\t// https://github.com/juj/MathGeoLib/blob/master/src/Geometry/LineSegment.cpp#L187\r\n\tconst paramResult = new Vector2();\r\n\tconst temp1 = new Vector3();\r\n\tconst temp2 = new Vector3();\r\n\treturn function closestPointsSegmentToSegment( l1, l2, target1, target2 ) {\r\n\r\n\t\tclosestPointLineToLine( l1, l2, paramResult );\r\n\r\n\t\tlet d = paramResult.x;\r\n\t\tlet d2 = paramResult.y;\r\n\t\tif ( d >= 0 && d <= 1 && d2 >= 0 && d2 <= 1 ) {\r\n\r\n\t\t\tl1.at( d, target1 );\r\n\t\t\tl2.at( d2, target2 );\r\n\r\n\t\t\treturn;\r\n\r\n\t\t} else if ( d >= 0 && d <= 1 ) {\r\n\r\n\t\t\t// Only d2 is out of bounds.\r\n\t\t\tif ( d2 < 0 ) {\r\n\r\n\t\t\t\tl2.at( 0, target2 );\r\n\r\n\t\t\t} else {\r\n\r\n\t\t\t\tl2.at( 1, target2 );\r\n\r\n\t\t\t}\r\n\r\n\t\t\tl1.closestPointToPoint( target2, true, target1 );\r\n\t\t\treturn;\r\n\r\n\t\t} else if ( d2 >= 0 && d2 <= 1 ) {\r\n\r\n\t\t\t// Only d is out of bounds.\r\n\t\t\tif ( d < 0 ) {\r\n\r\n\t\t\t\tl1.at( 0, target1 );\r\n\r\n\t\t\t} else {\r\n\r\n\t\t\t\tl1.at( 1, target1 );\r\n\r\n\t\t\t}\r\n\r\n\t\t\tl2.closestPointToPoint( target1, true, target2 );\r\n\t\t\treturn;\r\n\r\n\t\t} else {\r\n\r\n\t\t\t// Both u and u2 are out of bounds.\r\n\t\t\tlet p;\r\n\t\t\tif ( d < 0 ) {\r\n\r\n\t\t\t\tp = l1.start;\r\n\r\n\t\t\t} else {\r\n\r\n\t\t\t\tp = l1.end;\r\n\r\n\t\t\t}\r\n\r\n\t\t\tlet p2;\r\n\t\t\tif ( d2 < 0 ) {\r\n\r\n\t\t\t\tp2 = l2.start;\r\n\r\n\t\t\t} else {\r\n\r\n\t\t\t\tp2 = l2.end;\r\n\r\n\t\t\t}\r\n\r\n\t\t\tconst closestPoint = temp1;\r\n\t\t\tconst closestPoint2 = temp2;\r\n\t\t\tl1.closestPointToPoint( p2, true, temp1 );\r\n\t\t\tl2.closestPointToPoint( p, true, temp2 );\r\n\r\n\t\t\tif ( closestPoint.distanceToSquared( p2 ) <= closestPoint2.distanceToSquared( p ) ) {\r\n\r\n\t\t\t\ttarget1.copy( closestPoint );\r\n\t\t\t\ttarget2.copy( p2 );\r\n\t\t\t\treturn;\r\n\r\n\t\t\t} else {\r\n\r\n\t\t\t\ttarget1.copy( p );\r\n\t\t\t\ttarget2.copy( closestPoint2 );\r\n\t\t\t\treturn;\r\n\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t};\r\n\r\n} )();\r\n\r\n\r\nexport const sphereIntersectTriangle = ( function () {\r\n\r\n\t// https://stackoverflow.com/questions/34043955/detect-collision-between-sphere-and-triangle-in-three-js\r\n\tconst closestPointTemp = new Vector3();\r\n\tconst projectedPointTemp = new Vector3();\r\n\tconst planeTemp = new Plane();\r\n\tconst lineTemp = new Line3();\r\n\treturn function sphereIntersectTriangle( sphere, triangle ) {\r\n\r\n\t\tconst { radius, center } = sphere;\r\n\t\tconst { a, b, c } = triangle;\r\n\r\n\t\t// phase 1\r\n\t\tlineTemp.start = a;\r\n\t\tlineTemp.end = b;\r\n\t\tconst closestPoint1 = lineTemp.closestPointToPoint( center, true, closestPointTemp );\r\n\t\tif ( closestPoint1.distanceTo( center ) <= radius ) return true;\r\n\r\n\t\tlineTemp.start = a;\r\n\t\tlineTemp.end = c;\r\n\t\tconst closestPoint2 = lineTemp.closestPointToPoint( center, true, closestPointTemp );\r\n\t\tif ( closestPoint2.distanceTo( center ) <= radius ) return true;\r\n\r\n\t\tlineTemp.start = b;\r\n\t\tlineTemp.end = c;\r\n\t\tconst closestPoint3 = lineTemp.closestPointToPoint( center, true, closestPointTemp );\r\n\t\tif ( closestPoint3.distanceTo( center ) <= radius ) return true;\r\n\r\n\t\t// phase 2\r\n\t\tconst plane = triangle.getPlane( planeTemp );\r\n\t\tconst dp = Math.abs( plane.distanceToPoint( center ) );\r\n\t\tif ( dp <= radius ) {\r\n\r\n\t\t\tconst pp = plane.projectPoint( center, projectedPointTemp );\r\n\t\t\tconst cp = triangle.containsPoint( pp );\r\n\t\t\tif ( cp ) return true;\r\n\r\n\t\t}\r\n\r\n\t\treturn false;\r\n\r\n\t};\r\n\r\n} )();\r\n","import { Triangle, Vector3, Line3, Sphere, Plane } from 'three';\r\nimport { SeparatingAxisBounds } from './SeparatingAxisBounds.js';\r\nimport { closestPointsSegmentToSegment, sphereIntersectTriangle } from './MathUtilities.js';\r\n\r\nconst DIST_EPSILON = 1e-15;\r\nfunction isNearZero( value ) {\r\n\r\n\treturn Math.abs( value ) < DIST_EPSILON;\r\n\r\n}\r\n\r\nexport class ExtendedTriangle extends Triangle {\r\n\r\n\tconstructor( ...args ) {\r\n\r\n\t\tsuper( ...args );\r\n\r\n\t\tthis.isExtendedTriangle = true;\r\n\t\tthis.satAxes = new Array( 4 ).fill().map( () => new Vector3() );\r\n\t\tthis.satBounds = new Array( 4 ).fill().map( () => new SeparatingAxisBounds() );\r\n\t\tthis.points = [ this.a, this.b, this.c ];\r\n\t\tthis.sphere = new Sphere();\r\n\t\tthis.plane = new Plane();\r\n\t\tthis.needsUpdate = true;\r\n\r\n\t}\r\n\r\n\tintersectsSphere( sphere ) {\r\n\r\n\t\treturn sphereIntersectTriangle( sphere, this );\r\n\r\n\t}\r\n\r\n\tupdate() {\r\n\r\n\t\tconst a = this.a;\r\n\t\tconst b = this.b;\r\n\t\tconst c = this.c;\r\n\t\tconst points = this.points;\r\n\r\n\t\tconst satAxes = this.satAxes;\r\n\t\tconst satBounds = this.satBounds;\r\n\r\n\t\tconst axis0 = satAxes[ 0 ];\r\n\t\tconst sab0 = satBounds[ 0 ];\r\n\t\tthis.getNormal( axis0 );\r\n\t\tsab0.setFromPoints( axis0, points );\r\n\r\n\t\tconst axis1 = satAxes[ 1 ];\r\n\t\tconst sab1 = satBounds[ 1 ];\r\n\t\taxis1.subVectors( a, b );\r\n\t\tsab1.setFromPoints( axis1, points );\r\n\r\n\t\tconst axis2 = satAxes[ 2 ];\r\n\t\tconst sab2 = satBounds[ 2 ];\r\n\t\taxis2.subVectors( b, c );\r\n\t\tsab2.setFromPoints( axis2, points );\r\n\r\n\t\tconst axis3 = satAxes[ 3 ];\r\n\t\tconst sab3 = satBounds[ 3 ];\r\n\t\taxis3.subVectors( c, a );\r\n\t\tsab3.setFromPoints( axis3, points );\r\n\r\n\t\tthis.sphere.setFromPoints( this.points );\r\n\t\tthis.plane.setFromNormalAndCoplanarPoint( axis0, a );\r\n\t\tthis.needsUpdate = false;\r\n\r\n\t}\r\n\r\n}\r\n\r\nExtendedTriangle.prototype.closestPointToSegment = ( function () {\r\n\r\n\tconst point1 = new Vector3();\r\n\tconst point2 = new Vector3();\r\n\tconst edge = new Line3();\r\n\r\n\treturn function distanceToSegment( segment, target1 = null, target2 = null ) {\r\n\r\n\t\tconst { start, end } = segment;\r\n\t\tconst points = this.points;\r\n\t\tlet distSq;\r\n\t\tlet closestDistanceSq = Infinity;\r\n\r\n\t\t// check the triangle edges\r\n\t\tfor ( let i = 0; i < 3; i ++ ) {\r\n\r\n\t\t\tconst nexti = ( i + 1 ) % 3;\r\n\t\t\tedge.start.copy( points[ i ] );\r\n\t\t\tedge.end.copy( points[ nexti ] );\r\n\r\n\t\t\tclosestPointsSegmentToSegment( edge, segment, point1, point2 );\r\n\r\n\t\t\tdistSq = point1.distanceToSquared( point2 );\r\n\t\t\tif ( distSq < closestDistanceSq ) {\r\n\r\n\t\t\t\tclosestDistanceSq = distSq;\r\n\t\t\t\tif ( target1 ) target1.copy( point1 );\r\n\t\t\t\tif ( target2 ) target2.copy( point2 );\r\n\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t\t// check end points\r\n\t\tthis.closestPointToPoint( start, point1 );\r\n\t\tdistSq = start.distanceToSquared( point1 );\r\n\t\tif ( distSq < closestDistanceSq ) {\r\n\r\n\t\t\tclosestDistanceSq = distSq;\r\n\t\t\tif ( target1 ) target1.copy( point1 );\r\n\t\t\tif ( target2 ) target2.copy( start );\r\n\r\n\t\t}\r\n\r\n\t\tthis.closestPointToPoint( end, point1 );\r\n\t\tdistSq = end.distanceToSquared( point1 );\r\n\t\tif ( distSq < closestDistanceSq ) {\r\n\r\n\t\t\tclosestDistanceSq = distSq;\r\n\t\t\tif ( target1 ) target1.copy( point1 );\r\n\t\t\tif ( target2 ) target2.copy( end );\r\n\r\n\t\t}\r\n\r\n\t\treturn Math.sqrt( closestDistanceSq );\r\n\r\n\t};\r\n\r\n} )();\r\n\r\nExtendedTriangle.prototype.intersectsTriangle = ( function () {\r\n\r\n\tconst saTri2 = new ExtendedTriangle();\r\n\tconst arr1 = new Array( 3 );\r\n\tconst arr2 = new Array( 3 );\r\n\tconst cachedSatBounds = new SeparatingAxisBounds();\r\n\tconst cachedSatBounds2 = new SeparatingAxisBounds();\r\n\tconst cachedAxis = new Vector3();\r\n\tconst dir1 = new Vector3();\r\n\tconst dir2 = new Vector3();\r\n\tconst tempDir = new Vector3();\r\n\tconst edge = new Line3();\r\n\tconst edge1 = new Line3();\r\n\tconst edge2 = new Line3();\r\n\r\n\t// TODO: If the triangles are coplanar and intersecting the target is nonsensical. It should at least\r\n\t// be a line contained by both triangles if not a different special case somehow represented in the return result.\r\n\treturn function intersectsTriangle( other, target = null ) {\r\n\r\n\t\tif ( this.needsUpdate ) {\r\n\r\n\t\t\tthis.update();\r\n\r\n\t\t}\r\n\r\n\t\tif ( ! other.isExtendedTriangle ) {\r\n\r\n\t\t\tsaTri2.copy( other );\r\n\t\t\tsaTri2.update();\r\n\t\t\tother = saTri2;\r\n\r\n\t\t} else if ( other.needsUpdate ) {\r\n\r\n\t\t\tother.update();\r\n\r\n\t\t}\r\n\r\n\t\tconst plane1 = this.plane;\r\n\t\tconst plane2 = other.plane;\r\n\r\n\t\tif ( Math.abs( plane1.normal.dot( plane2.normal ) ) > 1.0 - 1e-10 ) {\r\n\r\n\t\t\t// perform separating axis intersection test only for coplanar triangles\r\n\t\t\tconst satBounds1 = this.satBounds;\r\n\t\t\tconst satAxes1 = this.satAxes;\r\n\t\t\tarr2[ 0 ] = other.a;\r\n\t\t\tarr2[ 1 ] = other.b;\r\n\t\t\tarr2[ 2 ] = other.c;\r\n\t\t\tfor ( let i = 0; i < 4; i ++ ) {\r\n\r\n\t\t\t\tconst sb = satBounds1[ i ];\r\n\t\t\t\tconst sa = satAxes1[ i ];\r\n\t\t\t\tcachedSatBounds.setFromPoints( sa, arr2 );\r\n\t\t\t\tif ( sb.isSeparated( cachedSatBounds ) ) return false;\r\n\r\n\t\t\t}\r\n\r\n\t\t\tconst satBounds2 = other.satBounds;\r\n\t\t\tconst satAxes2 = other.satAxes;\r\n\t\t\tarr1[ 0 ] = this.a;\r\n\t\t\tarr1[ 1 ] = this.b;\r\n\t\t\tarr1[ 2 ] = this.c;\r\n\t\t\tfor ( let i = 0; i < 4; i ++ ) {\r\n\r\n\t\t\t\tconst sb = satBounds2[ i ];\r\n\t\t\t\tconst sa = satAxes2[ i ];\r\n\t\t\t\tcachedSatBounds.setFromPoints( sa, arr1 );\r\n\t\t\t\tif ( sb.isSeparated( cachedSatBounds ) ) return false;\r\n\r\n\t\t\t}\r\n\r\n\t\t\t// check crossed axes\r\n\t\t\tfor ( let i = 0; i < 4; i ++ ) {\r\n\r\n\t\t\t\tconst sa1 = satAxes1[ i ];\r\n\t\t\t\tfor ( let i2 = 0; i2 < 4; i2 ++ ) {\r\n\r\n\t\t\t\t\tconst sa2 = satAxes2[ i2 ];\r\n\t\t\t\t\tcachedAxis.crossVectors( sa1, sa2 );\r\n\t\t\t\t\tcachedSatBounds.setFromPoints( cachedAxis, arr1 );\r\n\t\t\t\t\tcachedSatBounds2.setFromPoints( cachedAxis, arr2 );\r\n\t\t\t\t\tif ( cachedSatBounds.isSeparated( cachedSatBounds2 ) ) return false;\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\r\n\t\t\tif ( target ) {\r\n\r\n\t\t\t\t// TODO find two points that intersect on the edges and make that the result\r\n\t\t\t\tconsole.warn( 'ExtendedTriangle.intersectsTriangle: Triangles are coplanar which does not support an output edge. Setting edge to 0, 0, 0.' );\r\n\r\n\t\t\t\ttarget.start.set( 0, 0, 0 );\r\n\t\t\t\ttarget.end.set( 0, 0, 0 );\r\n\r\n\t\t\t}\r\n\r\n\t\t\treturn true;\r\n\r\n\t\t} else {\r\n\r\n\t\t\t// find the edge that intersects the other triangle plane\r\n\t\t\tconst points1 = this.points;\r\n\t\t\tlet found1 = false;\r\n\t\t\tlet count1 = 0;\r\n\t\t\tfor ( let i = 0; i < 3; i ++ ) {\r\n\r\n\t\t\t\tconst p = points1[ i ];\r\n\t\t\t\tconst pNext = points1[ ( i + 1 ) % 3 ];\r\n\r\n\t\t\t\tedge.start.copy( p );\r\n\t\t\t\tedge.end.copy( pNext );\r\n\t\t\t\tedge.delta( dir1 );\r\n\r\n\t\t\t\tconst targetPoint = found1 ? edge1.start : edge1.end;\r\n\t\t\t\tconst startIntersects = isNearZero( plane2.distanceToPoint( p ) );\r\n\t\t\t\tif ( isNearZero( plane2.normal.dot( dir1 ) ) && startIntersects ) {\r\n\r\n\t\t\t\t\t// if the edge lies on the plane then take the line\r\n\t\t\t\t\tedge1.copy( edge );\r\n\t\t\t\t\tcount1 = 2;\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// check if the start point is near the plane because \"intersectLine\" is not robust to that case\r\n\t\t\t\tconst doesIntersect = plane2.intersectLine( edge, targetPoint ) || startIntersects;\r\n\t\t\t\tif ( doesIntersect && ! isNearZero( targetPoint.distanceTo( pNext ) ) ) {\r\n\r\n\t\t\t\t\tcount1 ++;\r\n\t\t\t\t\tif ( found1 ) {\r\n\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tfound1 = true;\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\r\n\t\t\tif ( count1 === 1 && this.containsPoint( edge1.start ) ) {\r\n\r\n\t\t\t\tif ( target ) {\r\n\r\n\t\t\t\t\ttarget.start.copy( edge1.start );\r\n\t\t\t\t\ttarget.end.copy( edge1.start );\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn true;\r\n\r\n\t\t\t} else if ( count1 !== 2 ) {\r\n\r\n\t\t\t\treturn false;\r\n\r\n\t\t\t}\r\n\r\n\t\t\t// find the other triangles edge that intersects this plane\r\n\t\t\tconst points2 = other.points;\r\n\t\t\tlet found2 = false;\r\n\t\t\tlet count2 = 0;\r\n\t\t\tfor ( let i = 0; i < 3; i ++ ) {\r\n\r\n\t\t\t\tconst p = points2[ i ];\r\n\t\t\t\tconst pNext = points2[ ( i + 1 ) % 3 ];\r\n\r\n\t\t\t\tedge.start.copy( p );\r\n\t\t\t\tedge.end.copy( pNext );\r\n\t\t\t\tedge.delta( dir2 );\r\n\r\n\t\t\t\tconst targetPoint = found2 ? edge2.start : edge2.end;\r\n\t\t\t\tconst startIntersects = isNearZero( plane1.distanceToPoint( p ) );\r\n\t\t\t\tif ( isNearZero( plane1.normal.dot( dir2 ) ) && startIntersects ) {\r\n\r\n\t\t\t\t\t// if the edge lies on the plane then take the line\r\n\t\t\t\t\tedge2.copy( edge );\r\n\t\t\t\t\tcount2 = 2;\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// check if the start point is near the plane because \"intersectLine\" is not robust to that case\r\n\t\t\t\tconst doesIntersect = plane1.intersectLine( edge, targetPoint ) || startIntersects;\r\n\t\t\t\tif ( doesIntersect && ! isNearZero( targetPoint.distanceTo( pNext ) ) ) {\r\n\r\n\t\t\t\t\tcount2 ++;\r\n\t\t\t\t\tif ( found2 ) {\r\n\r\n\t\t\t\t\t\tbreak;\r\n\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tfound2 = true;\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\r\n\t\t\tif ( count2 === 1 && this.containsPoint( edge2.start ) ) {\r\n\r\n\t\t\t\tif ( target ) {\r\n\r\n\t\t\t\t\ttarget.start.copy( edge2.start );\r\n\t\t\t\t\ttarget.end.copy( edge2.start );\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn true;\r\n\r\n\t\t\t} else if ( count2 !== 2 ) {\r\n\r\n\t\t\t\treturn false;\r\n\r\n\t\t\t}\r\n\r\n\t\t\t// find swap the second edge so both lines are running the same direction\r\n\t\t\tedge1.delta( dir1 );\r\n\t\t\tedge2.delta( dir2 );\r\n\r\n\t\t\tif ( dir1.dot( dir2 ) < 0 ) {\r\n\r\n\t\t\t\tlet tmp = edge2.start;\r\n\t\t\t\tedge2.start = edge2.end;\r\n\t\t\t\tedge2.end = tmp;\r\n\r\n\t\t\t}\r\n\r\n\t\t\t// check if the edges are overlapping\r\n\t\t\tconst s1 = edge1.start.dot( dir1 );\r\n\t\t\tconst e1 = edge1.end.dot( dir1 );\r\n\t\t\tconst s2 = edge2.start.dot( dir1 );\r\n\t\t\tconst e2 = edge2.end.dot( dir1 );\r\n\t\t\tconst separated1 = e1 < s2;\r\n\t\t\tconst separated2 = s1 < e2;\r\n\r\n\t\t\tif ( s1 !== e2 && s2 !== e1 && separated1 === separated2 ) {\r\n\r\n\t\t\t\treturn false;\r\n\r\n\t\t\t}\r\n\r\n\t\t\t// assign the target output\r\n\t\t\tif ( target ) {\r\n\r\n\t\t\t\ttempDir.subVectors( edge1.start, edge2.start );\r\n\t\t\t\tif ( tempDir.dot( dir1 ) > 0 ) {\r\n\r\n\t\t\t\t\ttarget.start.copy( edge1.start );\r\n\r\n\t\t\t\t} else {\r\n\r\n\t\t\t\t\ttarget.start.copy( edge2.start );\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\ttempDir.subVectors( edge1.end, edge2.end );\r\n\t\t\t\tif ( tempDir.dot( dir1 ) < 0 ) {\r\n\r\n\t\t\t\t\ttarget.end.copy( edge1.end );\r\n\r\n\t\t\t\t} else {\r\n\r\n\t\t\t\t\ttarget.end.copy( edge2.end );\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\r\n\t\t\treturn true;\r\n\r\n\t\t}\r\n\r\n\t};\r\n\r\n} )();\r\n\r\n\r\nExtendedTriangle.prototype.distanceToPoint = ( function () {\r\n\r\n\tconst target = new Vector3();\r\n\treturn function distanceToPoint( point ) {\r\n\r\n\t\tthis.closestPointToPoint( point, target );\r\n\t\treturn point.distanceTo( target );\r\n\r\n\t};\r\n\r\n} )();\r\n\r\n\r\nExtendedTriangle.prototype.distanceToTriangle = ( function () {\r\n\r\n\tconst point = new Vector3();\r\n\tconst point2 = new Vector3();\r\n\tconst cornerFields = [ 'a', 'b', 'c' ];\r\n\tconst line1 = new Line3();\r\n\tconst line2 = new Line3();\r\n\r\n\treturn function distanceToTriangle( other, target1 = null, target2 = null ) {\r\n\r\n\t\tconst lineTarget = target1 || target2 ? line1 : null;\r\n\t\tif ( this.intersectsTriangle( other, lineTarget ) ) {\r\n\r\n\t\t\tif ( target1 || target2 ) {\r\n\r\n\t\t\t\tif ( target1 ) lineTarget.getCenter( target1 );\r\n\t\t\t\tif ( target2 ) lineTarget.getCenter( t