import { OrientedBox3 } from "@here/harp-geoutils" ; import { SolidLineMaterial } from "@here/harp-materials" ; import { assert } from "@here/harp-utils" ; import * as THREE from "three" ; import { displaceBox , DisplacedBufferGeometry , DisplacementRange } from "./DisplacedBufferGeometry" ; const tmpSphere = new THREE . Sphere ( ) ; const tmpInverseMatrix = new THREE . Matrix4 ( ) ; const tmpRay = new THREE . Ray ( ) ; const tmpLine1 = new THREE . Line3 ( ) ; const tmpBox = new THREE . Box3 ( ) ; const tmpOBB = new OrientedBox3 ( ) ; const tmpPlane = new THREE . Plane ( ) ; const tmpV1 = new THREE . Vector3 ( ) ; const tmpV2 = new THREE . Vector3 ( ) ; const tmpV3 = new THREE . Vector3 ( ) ; const tmpV4 = new THREE . Vector3 ( ) ; const SEGMENT_STRIDE = 6 ; const VERTEX_STRIDE = 2 ; function isSolidLineMaterial ( material ) { return Array . isArray ( material ) ? material . every ( mat => mat instanceof SolidLineMaterial ) : material instanceof SolidLineMaterial ; } /**
 * Identify the position attribute that has been used to create the bounding volumes.
 * Also store version info to be able to detect changes to the data.
 */ interface AttributeInfo { /** Attribute used for bounding volume creation. */ data : THREE . BufferAttribute | THREE . InterleavedBuffer ; /** Version of attribute at time of bounding volume creation. */ version : number | undefined ; } param attribute returns function getAttributeInfo ( attribute ) { const isBufferAttribute = ( attribute as THREE . BufferAttribute ) . isBufferAttribute === true ; const data = isBufferAttribute ? ( attribute as THREE . BufferAttribute ) : ( attribute as THREE . InterleavedBufferAttribute ) . data ; return { data , version : data . version } ; } param attribute param attrInfo returns function attributeChanged ( attribute , attrInfo ) { const isBufferAttribute = ( attribute as THREE . BufferAttribute ) . isBufferAttribute === true ; const data = isBufferAttribute ? ( attribute as THREE . BufferAttribute ) : ( attribute as THREE . InterleavedBufferAttribute ) . data ; return ( attrInfo === undefined || attrInfo . data !== data || ( ( attribute as THREE . BufferAttribute ) . isBufferAttribute && attrInfo . version !== data . version ) ) ; } param geometry param featureBeginIndex param featureEndIndex returns function computeFeatureBoundingSphere ( geometry , featureBeginIndex , featureEndIndex ) { let displacementRange ; if ( geometry instanceof DisplacedBufferGeometry ) { displacementRange = geometry . displacementRange ; geometry = geometry . originalGeometry ; } const attributes = geometry . attributes ; const pos = attributes . position as THREE . BufferAttribute ; const indices = geometry . index ! . array ; const sphere = new THREE . Sphere ( ) ; const bbox = tmpBox . makeEmpty ( ) ; const vertex = tmpV1 ; for ( let i = featureBeginIndex ; i < featureEndIndex ; i += SEGMENT_STRIDE ) { bbox . expandByPoint ( vertex . fromBufferAttribute ( pos , indices [ i ] ) ) ; bbox . expandByPoint ( vertex . fromBufferAttribute ( pos , indices [ i + VERTEX_STRIDE ] ) ) ; } if ( displacementRange ) { const normal = tmpV2 ; normal . fromBufferAttribute ( geometry . attributes . normal as THREE . BufferAttribute , 0 ) ; return displaceBox ( bbox , displacementRange , normal ) . getBoundingSphere ( sphere ) ; } return bbox . getBoundingSphere ( sphere ) ; } param ray param line param vExtrusion param normal param hWidth returns function intersectExtrudedLine ( ray , line , vExtrusion , normal , hWidth ) { const obb = tmpOBB ; line . getCenter ( obb . position ) ; line . delta ( obb . xAxis ) . normalize ( ) ; obb . yAxis . copy ( vExtrusion ) ; obb . zAxis . copy ( normal ) ; obb . extents . set ( line . distance ( ) / 2 , hWidth , hWidth ) ; if ( obb . contains ( ray . origin ) ) { return 0 ; } return obb . intersectsRay ( ray ) ?? Infinity ; } param ray param line param hWidth returns function intersectClosestEndCap ( ray , line , hWidth ) { const sphere = new THREE . Sphere ( line . start , hWidth ) ; const startCapT = sphere . containsPoint ( ray . origin ) ? 0 : ray . intersectSphere ( sphere , tmpV4 ) ? tmpV4 . sub ( ray . origin ) . length ( ) : Infinity ; sphere . center . copy ( line . end ) ; const endCapT = sphere . containsPoint ( ray . origin ) ? 0 : ray . intersectSphere ( sphere , tmpV4 ) ? tmpV4 . sub ( ray . origin ) . length ( ) : Infinity ; return Math . min ( startCapT , endCapT ) ; } param ray param line param vExtrusion param hWidth param hWidthSq param plane param interPlane param outInterLine returns function intersectLine ( ray , line , vExtrusion , hWidth , hWidthSq , plane , interPlane , outInterLine ) { if ( interPlane . equals ( ray . origin ) && ray . direction . dot ( plane . normal ) === 0 ) { const extrLineT = intersectExtrudedLine ( ray , line , vExtrusion , plane . normal , hWidth ) ; const endCapT = intersectClosestEndCap ( ray , line , hWidth ) ; const minT = Math . min ( extrLineT , endCapT ) ; if ( minT === Infinity ) { return false ; } ray . at ( minT , outInterLine ) ; return true ; } const distSq = interPlane . distanceToSquared ( line . closestPointToPoint ( interPlane , true , tmpV4 ) ) ; if ( distSq > hWidthSq ) { return false ; } outInterLine . copy ( interPlane ) ; return true ; } param mesh param raycaster param localRay param halfWidth param lHalfWidth param lHalfWidthSq param beginIdx param endIdx param bSphere param intersections function intersectFeature ( mesh , raycaster , localRay , halfWidth , lHalfWidth , lHalfWidthSq , beginIdx , endIdx , bSphere , intersections ) { const vExt = tmpV1 ; const plane = tmpPlane ; const interPlane = tmpV2 ; const line = tmpLine1 ; const geometry = mesh . geometry as THREE . BufferGeometry ; const attributes = geometry . attributes ; const position = attributes . position as THREE . BufferAttribute ; const bitangent = attributes . biTangent ; const indices = geometry . index ! . array ; tmpSphere . copy ( bSphere ) ; tmpSphere . applyMatrix4 ( mesh . matrixWorld ) ; tmpSphere . radius += halfWidth ; if ( ! raycaster . ray . intersectsSphere ( tmpSphere ) ) { return ; } for ( let i = beginIdx ; i < endIdx ; i += SEGMENT_STRIDE ) { const a = indices [ i ] ; const b = indices [ i + VERTEX_STRIDE ] ; line . start . fromBufferAttribute ( position , a ) ; line . end . fromBufferAttribute ( position , b ) ; vExt . set ( bitangent . getX ( a ) , bitangent . getY ( a ) , bitangent . getZ ( a ) ) . normalize ( ) ; plane . setFromCoplanarPoints ( line . start , tmpV3 . copy ( line . start ) . add ( vExt ) , line . end ) ; if ( plane . normal . manhattanLength ( ) === 0 ) { continue ; } if ( ! localRay . intersectPlane ( plane , interPlane ) ) { continue ; } const interLine = tmpV3 ; if ( ! intersectLine ( localRay , line , vExt , lHalfWidth , lHalfWidthSq , plane , interPlane , interLine ) ) { continue ; } const interLineWorld = interLine . applyMatrix4 ( mesh . matrixWorld ) ; const distance = raycaster . ray . origin . distanceTo ( interLineWorld ) ; if ( distance < raycaster . near || distance > raycaster . far ) { continue ; } intersections . push ( { distance , point : interLineWorld . clone ( ) , index : i , object : mesh } ) ; } } const singleFeatureStart = [ 0 ] ; const MAX_SCALE_RATIO_DIFF = 1e-2 ; param mesh param material param raycaster param localRay param firstFeatureIdx param groupEndIdx param intersections returns function intersectGroup ( mesh , material , raycaster , localRay , firstFeatureIdx , groupEndIdx , intersections ) { const bVolumes = mesh . userData . feature . boundingVolumes ; assert ( mesh . geometry instanceof THREE . BufferGeometry , "Unsupported geometry type." ) ; const geometry = mesh . geometry as THREE . BufferGeometry ; assert ( isSolidLineMaterial ( material ) , "Unsupported material type" ) ; const solidLineMaterial = material as SolidLineMaterial ; const halfWidth = ( solidLineMaterial . lineWidth + solidLineMaterial . outlineWidth ) / 2 ; assert ( Math . abs ( 1 - mesh . scale . x / mesh . scale . y ) < MAX_SCALE_RATIO_DIFF ) ; assert ( Math . abs ( 1 - mesh . scale . x / mesh . scale . z ) < MAX_SCALE_RATIO_DIFF ) ; assert ( Math . abs ( 1 - mesh . scale . y / mesh . scale . z ) < MAX_SCALE_RATIO_DIFF ) ; const localHalfWidth = halfWidth / ( ( mesh . scale . x + mesh . scale . y + mesh . scale . z ) / 3 ) ; const localHalfWidthSq = localHalfWidth * localHalfWidth ; const featureStarts = mesh . userData . feature . starts ?? singleFeatureStart ; let featureIdx = firstFeatureIdx ; let beginIdx = featureStarts [ featureIdx ] ; const lastFeatureIdx = featureStarts . length - 1 ; while ( beginIdx < groupEndIdx ) { const bVolumeIdx = featureIdx ; const endIdx = featureIdx < lastFeatureIdx ? featureStarts [ ++ featureIdx ] : groupEndIdx ; if ( bVolumeIdx >= bVolumes . length ) { bVolumes . push ( computeFeatureBoundingSphere ( geometry , beginIdx , endIdx ) ) ; } intersectFeature ( mesh , raycaster , localRay , halfWidth , localHalfWidth , localHalfWidthSq , beginIdx , endIdx , bVolumes [ bVolumeIdx ] , intersections ) ; beginIdx = endIdx ; } return featureIdx ; } internal export class SolidLineMesh extends THREE . Mesh { param mesh param raycaster param intersections static raycast ( mesh , raycaster , intersections ) { assert ( mesh . geometry instanceof THREE . BufferGeometry , "Unsupported geometry type" ) ; const geometry = mesh . geometry as THREE . BufferGeometry ; assert ( geometry . index !== null , "Geometry does not have indices" ) ; const matrixWorld = mesh . matrixWorld ; tmpInverseMatrix . copy ( matrixWorld ) . invert ( ) ; const localRay = tmpRay . copy ( raycaster . ray ) . applyMatrix4 ( tmpInverseMatrix ) ; if ( mesh . userData . feature === undefined ) { mesh . userData . feature = { } ; } const positionAttribute = geometry . attributes [ "position" ] ; const attributeInfo = mesh . userData . feature . attributeInfo as AttributeInfo ; if ( attributeInfo === undefined || mesh . userData . feature . boundingVolumes === undefined || attributeChanged ( positionAttribute , attributeInfo ) ) { mesh . userData . feature . boundingVolumes = [ ] ; mesh . userData . feature . attributeInfo = getAttributeInfo ( positionAttribute ) ; } const indices = geometry . index ! . array ; if ( Array . isArray ( mesh . material ) ) { let nextFeatureIdx = 0 ; for ( const group of geometry . groups ) { const material = mesh . material [ group . materialIndex ! ] ; const groupEndIdx = group . start + group . count ; nextFeatureIdx = intersectGroup ( mesh , material , raycaster , localRay , nextFeatureIdx , groupEndIdx , intersections ) ; } } else { intersectGroup ( mesh , mesh . material , raycaster , localRay , 0 , indices . length , intersections ) ; } } param geometry param material constructor ( geometry , material ) { super ( geometry , material ) ; } raycast ( raycaster , intersects ) { SolidLineMesh . raycast ( this , raycaster , intersects ) ; } }