UNPKG

three

Version:

JavaScript 3D library

346 lines (233 loc) 9.71 kB
/*! * Portions adapted from SculptGL by Stéphane Ginier. * Copyright (c) 2019 Stéphane GINIER * Licensed under the MIT License; see ./SculptGL.LICENSE.txt. */ const TRI_INDEX = 4294967295; const MAX_FLAG = 0x7fffffff; let _memoryBuffer = new ArrayBuffer( 100000 ); const _sortByIndex = ( a, b ) => a - b; function getMemory( byteLength ) { if ( _memoryBuffer.byteLength < byteLength ) { _memoryBuffer = new ArrayBuffer( Math.max( byteLength, _memoryBuffer.byteLength * 2 ) ); } return _memoryBuffer; } function replaceElement( array, oldValue, newValue ) { for ( let i = 0, l = array.length; i < l; ++ i ) { if ( array[ i ] === oldValue ) { array[ i ] = newValue; return; } } } function removeElement( array, value ) { for ( let i = 0, l = array.length; i < l; ++ i ) { if ( array[ i ] === value ) { array[ i ] = array[ l - 1 ]; array.pop(); return; } } } function tidy( array ) { if ( array.length < 2 ) return; array.sort( _sortByIndex ); let writeIndex = 1; for ( let i = 1; i < array.length; i ++ ) { if ( array[ writeIndex - 1 ] !== array[ i ] ) array[ writeIndex ++ ] = array[ i ]; } array.length = writeIndex; } // Geometry helpers const _edge1 = [ 0, 0, 0 ]; const _edge2 = [ 0, 0, 0 ]; const _pvec = [ 0, 0, 0 ]; const _tvec = [ 0, 0, 0 ]; const _qvec = [ 0, 0, 0 ]; const _scaledDir = [ 0, 0, 0 ]; const RAY_EPSILON = 1e-15; function cross( out, a, b ) { out[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ]; out[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ]; out[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ]; return out; } function dot( a, b ) { return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ]; } function sub( out, a, b ) { out[ 0 ] = a[ 0 ] - b[ 0 ]; out[ 1 ] = a[ 1 ] - b[ 1 ]; out[ 2 ] = a[ 2 ] - b[ 2 ]; return out; } function sqrDist( a, b ) { const dx = a[ 0 ] - b[ 0 ], dy = a[ 1 ] - b[ 1 ], dz = a[ 2 ] - b[ 2 ]; return dx * dx + dy * dy + dz * dz; } function setRayIntersection( target, origin, direction, distance ) { if ( ! target ) return; target[ 0 ] = origin[ 0 ] + direction[ 0 ] * distance; target[ 1 ] = origin[ 1 ] + direction[ 1 ] * distance; target[ 2 ] = origin[ 2 ] + direction[ 2 ] * distance; } function intersectionRayTriangleScaled( orig, dir, v1, v2, v3, vertInter ) { sub( _edge1, v2, v1 ); sub( _edge2, v3, v1 ); const edge1Scale = Math.max( Math.abs( _edge1[ 0 ] ), Math.abs( _edge1[ 1 ] ), Math.abs( _edge1[ 2 ] ) ); const edge2Scale = Math.max( Math.abs( _edge2[ 0 ] ), Math.abs( _edge2[ 1 ] ), Math.abs( _edge2[ 2 ] ) ); const dirScale = Math.max( Math.abs( dir[ 0 ] ), Math.abs( dir[ 1 ] ), Math.abs( dir[ 2 ] ) ); if ( edge1Scale === 0 || edge2Scale === 0 || dirScale === 0 || Number.isFinite( edge1Scale ) === false || Number.isFinite( edge2Scale ) === false || Number.isFinite( dirScale ) === false ) return - 1.0; _edge1[ 0 ] /= edge1Scale; _edge1[ 1 ] /= edge1Scale; _edge1[ 2 ] /= edge1Scale; _edge2[ 0 ] /= edge2Scale; _edge2[ 1 ] /= edge2Scale; _edge2[ 2 ] /= edge2Scale; _scaledDir[ 0 ] = dir[ 0 ] / dirScale; _scaledDir[ 1 ] = dir[ 1 ] / dirScale; _scaledDir[ 2 ] = dir[ 2 ] / dirScale; cross( _pvec, _scaledDir, _edge2 ); const det = dot( _edge1, _pvec ); const determinantScale = Math.hypot( _edge1[ 0 ], _edge1[ 1 ], _edge1[ 2 ] ) * Math.hypot( _edge2[ 0 ], _edge2[ 1 ], _edge2[ 2 ] ) * Math.hypot( _scaledDir[ 0 ], _scaledDir[ 1 ], _scaledDir[ 2 ] ); if ( Math.abs( det ) <= RAY_EPSILON * determinantScale ) return - 1.0; const invDet = 1.0 / det; sub( _tvec, orig, v1 ); const translationScale = Math.max( Math.abs( _tvec[ 0 ] ), Math.abs( _tvec[ 1 ] ), Math.abs( _tvec[ 2 ] ) ); if ( Number.isFinite( translationScale ) === false ) return - 1.0; if ( translationScale !== 0 ) { _tvec[ 0 ] /= translationScale; _tvec[ 1 ] /= translationScale; _tvec[ 2 ] /= translationScale; } const u = dot( _tvec, _pvec ) * invDet * translationScale / edge1Scale; if ( u < - RAY_EPSILON || u > 1.0 + RAY_EPSILON ) return - 1.0; cross( _qvec, _tvec, _edge1 ); const v = dot( _scaledDir, _qvec ) * invDet * translationScale / edge2Scale; if ( v < - RAY_EPSILON || u + v > 1.0 + RAY_EPSILON ) return - 1.0; const t = dot( _edge2, _qvec ) * invDet * translationScale / dirScale; if ( t < - RAY_EPSILON ) return - 1.0; setRayIntersection( vertInter, orig, dir, t ); return t; } function intersectionRayTriangle( orig, dir, v1, v2, v3, vertInter ) { sub( _edge1, v2, v1 ); sub( _edge2, v3, v1 ); cross( _pvec, dir, _edge2 ); const det = dot( _edge1, _pvec ); const determinantScale = Math.hypot( _edge1[ 0 ], _edge1[ 1 ], _edge1[ 2 ] ) * Math.hypot( _edge2[ 0 ], _edge2[ 1 ], _edge2[ 2 ] ) * Math.hypot( dir[ 0 ], dir[ 1 ], dir[ 2 ] ); const determinantTolerance = RAY_EPSILON * determinantScale; if ( Number.isFinite( det ) === false || Number.isFinite( determinantScale ) === false || determinantTolerance === 0 ) { return intersectionRayTriangleScaled( orig, dir, v1, v2, v3, vertInter ); } if ( Math.abs( det ) <= determinantTolerance ) return - 1.0; const invDet = 1.0 / det; sub( _tvec, orig, v1 ); const u = dot( _tvec, _pvec ) * invDet; if ( Number.isFinite( u ) === false ) return intersectionRayTriangleScaled( orig, dir, v1, v2, v3, vertInter ); if ( u < - RAY_EPSILON || u > 1.0 + RAY_EPSILON ) return - 1.0; cross( _qvec, _tvec, _edge1 ); const v = dot( dir, _qvec ) * invDet; if ( Number.isFinite( v ) === false ) return intersectionRayTriangleScaled( orig, dir, v1, v2, v3, vertInter ); if ( v < - RAY_EPSILON || u + v > 1.0 + RAY_EPSILON ) return - 1.0; const t = dot( _edge2, _qvec ) * invDet; if ( Number.isFinite( t ) === false ) return intersectionRayTriangleScaled( orig, dir, v1, v2, v3, vertInter ); if ( t < - RAY_EPSILON ) return - 1.0; setRayIntersection( vertInter, orig, dir, t ); return t; } function distanceSqToSegment( point, v1, v2 ) { const ptx = point[ 0 ] - v1[ 0 ], pty = point[ 1 ] - v1[ 1 ], ptz = point[ 2 ] - v1[ 2 ]; const vx = v2[ 0 ] - v1[ 0 ], vy = v2[ 1 ] - v1[ 1 ], vz = v2[ 2 ] - v1[ 2 ]; const lengthSquared = vx * vx + vy * vy + vz * vz; if ( lengthSquared === 0 ) return ptx * ptx + pty * pty + ptz * ptz; const t = ( ptx * vx + pty * vy + ptz * vz ) / lengthSquared; if ( t < 0 ) return ptx * ptx + pty * pty + ptz * ptz; if ( t > 1 ) { const dx = point[ 0 ] - v2[ 0 ], dy = point[ 1 ] - v2[ 1 ], dz = point[ 2 ] - v2[ 2 ]; return dx * dx + dy * dy + dz * dz; } const rx = point[ 0 ] - v1[ 0 ] - t * vx; const ry = point[ 1 ] - v1[ 1 ] - t * vy; const rz = point[ 2 ] - v1[ 2 ] - t * vz; return rx * rx + ry * ry + rz * rz; } function distanceSqToTriangle( point, v1, v2, v3 ) { const abx = v2[ 0 ] - v1[ 0 ], aby = v2[ 1 ] - v1[ 1 ], abz = v2[ 2 ] - v1[ 2 ]; const acx = v3[ 0 ] - v1[ 0 ], acy = v3[ 1 ] - v1[ 1 ], acz = v3[ 2 ] - v1[ 2 ]; const bcx = v3[ 0 ] - v2[ 0 ], bcy = v3[ 1 ] - v2[ 1 ], bcz = v3[ 2 ] - v2[ 2 ]; const crossX = aby * acz - abz * acy; const crossY = abz * acx - abx * acz; const crossZ = abx * acy - aby * acx; const areaSquared = crossX * crossX + crossY * crossY + crossZ * crossZ; const maxEdgeSquared = Math.max( abx * abx + aby * aby + abz * abz, acx * acx + acy * acy + acz * acz, bcx * bcx + bcy * bcy + bcz * bcz ); if ( areaSquared <= Number.EPSILON * maxEdgeSquared * maxEdgeSquared ) { return Math.min( distanceSqToSegment( point, v1, v2 ), distanceSqToSegment( point, v2, v3 ), distanceSqToSegment( point, v3, v1 ) ); } const apx = point[ 0 ] - v1[ 0 ], apy = point[ 1 ] - v1[ 1 ], apz = point[ 2 ] - v1[ 2 ]; const d1 = abx * apx + aby * apy + abz * apz; const d2 = acx * apx + acy * apy + acz * apz; if ( d1 <= 0 && d2 <= 0 ) return apx * apx + apy * apy + apz * apz; const bpx = point[ 0 ] - v2[ 0 ], bpy = point[ 1 ] - v2[ 1 ], bpz = point[ 2 ] - v2[ 2 ]; const d3 = abx * bpx + aby * bpy + abz * bpz; const d4 = acx * bpx + acy * bpy + acz * bpz; if ( d3 >= 0 && d4 <= d3 ) return bpx * bpx + bpy * bpy + bpz * bpz; const vc = d1 * d4 - d3 * d2; if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) { const v = d1 / ( d1 - d3 ); const dx = apx - abx * v, dy = apy - aby * v, dz = apz - abz * v; return dx * dx + dy * dy + dz * dz; } const cpx = point[ 0 ] - v3[ 0 ], cpy = point[ 1 ] - v3[ 1 ], cpz = point[ 2 ] - v3[ 2 ]; const d5 = abx * cpx + aby * cpy + abz * cpz; const d6 = acx * cpx + acy * cpy + acz * cpz; if ( d6 >= 0 && d5 <= d6 ) return cpx * cpx + cpy * cpy + cpz * cpz; const vb = d5 * d2 - d1 * d6; if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) { const w = d2 / ( d2 - d6 ); const dx = apx - acx * w, dy = apy - acy * w, dz = apz - acz * w; return dx * dx + dy * dy + dz * dz; } const va = d3 * d6 - d5 * d4; if ( va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0 ) { const edgeX = v3[ 0 ] - v2[ 0 ], edgeY = v3[ 1 ] - v2[ 1 ], edgeZ = v3[ 2 ] - v2[ 2 ]; const w = ( d4 - d3 ) / ( d4 - d3 + d5 - d6 ); const dx = bpx - edgeX * w, dy = bpy - edgeY * w, dz = bpz - edgeZ * w; return dx * dx + dy * dy + dz * dz; } const denominator = va + vb + vc; const inverse = 1.0 / denominator; const v = vb * inverse; const w = vc * inverse; const dx = apx - abx * v - acx * w; const dy = apy - aby * v - acy * w; const dz = apz - abz * v - acz * w; return dx * dx + dy * dy + dz * dz; } function triangleInsideSphere( point, radiusSquared, v1, v2, v3 ) { return distanceSqToTriangle( point, v1, v2, v3 ) < radiusSquared; } function falloff( dist ) { const d2 = dist * dist; return 3.0 * d2 * d2 - 4.0 * d2 * dist + 1.0; } export { TRI_INDEX, MAX_FLAG, getMemory, replaceElement, removeElement, tidy, sqrDist, intersectionRayTriangle, triangleInsideSphere, falloff };