import { LoggerManager , Math2D } from "@here/harp-utils" ; import * as THREE from "three" ; import { debugContext } from "./DebugContext" ; declare const require ; const RBush = require ( "rbush" ) ; const logger = LoggerManager . instance . create ( "ScreenCollissions" ) ; export interface IBox { minX : number ; minY : number ; maxX : number ; maxY : number ; } export class CollisionBox extends Math2D . Box implements IBox { constructor ( box ? ) { super ( ) ; if ( box !== undefined ) { this . copy ( box ) ; } } copy ( box ) { if ( box instanceof Math2D . Box ) { this . set ( box . x , box . y , box . w , box . h ) ; } else if ( box instanceof THREE . Box2 ) { this . set ( box . min . x , box . min . y , box . max . x - box . min . x , box . max . y - box . min . y ) ; } else { this . set ( box . minX , box . minY , box . maxX - box . minX , box . maxY - box . minY ) ; } return this ; } get minX ( ) : number { return this . x ; } set minX ( minX ) { this . x = minX ; } get maxX ( ) : number { return this . x + this . w ; } set maxX ( maxX ) { this . w = maxX - this . x ; } get minY ( ) : number { return this . y ; } set minY ( minY ) { this . y = minY ; } get maxY ( ) : number { return this . y + this . h ; } set maxY ( maxY ) { this . h = maxY - this . y ; } } /**
 * Collision box with additional boxes defining tighter bounds for the enclosed feature
 * (e.g.glyph bounds for text).
 */ export class DetailedCollisionBox extends CollisionBox { constructor ( box , readonly detailBoxes ) { super ( box ) ; } } export interface LineWithBound extends IBox { line : THREE . Line3 ; } export function isLineWithBound ( box ) { return ( box as LineWithBound ) . line !== undefined ; } const tmpCollisionBox = new CollisionBox ( ) ; export class ScreenCollisions { /** The screen bounding box. */ readonly screenBounds = new Math2D . Box ( ) ; /** Tree of allocated bounds. */ private readonly rtree = new RBush ( ) ; /**
     * Constructs a new ScreenCollisions object.
     */ constructor ( ) { } /**
     * Resets the list of allocated screen bounds.
     */ reset ( ) { this . rtree . clear ( ) ; } param width param height update ( width , height ) { this . screenBounds . set ( width / - 2 , height / - 2 , width , height ) ; this . reset ( ) ; } param bounds allocate ( bounds ) { const bbox = ! ( bounds instanceof CollisionBox ) ? new CollisionBox ( bounds ) : bounds ; this . rtree . insert ( bbox ) ; } param bounds allocateIBoxes ( bounds ) { this . rtree . load ( bounds ) ; } param box returns search ( box ) { return this . rtree . search ( box ) ; } param bounds isAllocated ( bounds ) { const collisionBox = bounds instanceof CollisionBox ? bounds : tmpCollisionBox . copy ( bounds ) ; const results = this . search ( collisionBox ) ; return this . intersectsDetails ( collisionBox , results ) ; } param bounds isVisible ( bounds ) { return this . screenBounds . intersects ( bounds ) ; } param bounds isFullyVisible ( bounds ) { return this . screenBounds . containsBox ( bounds ) ; } param testBox param boxes returns intersectsDetails ( testBox , boxes ) { for ( const box of boxes ) { if ( box instanceof DetailedCollisionBox ) { for ( const detailBox of box . detailBoxes ) { if ( detailBox . intersects ( testBox ) ) { return true ; } } } else if ( isLineWithBound ( box ) ) { const boundedLine = box as LineWithBound ; if ( this . intersectsLine ( testBox , boundedLine ) ) { return true ; } } else { return true ; } } return false ; } note private intersectsLine ( bbox , boundedLine ) { const line = boundedLine . line ; const lineXDiffTransformed = line . end . x - line . start . x ; let signBL ; let signBR ; let signTL ; let signTR ; if ( lineXDiffTransformed !== 0 ) { const lineYDiffTransformed = line . end . y - line . start . y ; const normalX = lineYDiffTransformed ; const normalY = - lineXDiffTransformed ; const D = line . start . y - ( lineYDiffTransformed / lineXDiffTransformed ) * line . start . x ; signBL = Math . sign ( bbox . minX * normalX + ( bbox . minY - D ) * normalY ) ; signBR = Math . sign ( bbox . maxX * normalX + ( bbox . minY - D ) * normalY ) ; signTL = Math . sign ( bbox . minX * normalX + ( bbox . maxY - D ) * normalY ) ; signTR = Math . sign ( bbox . maxX * normalX + ( bbox . maxY - D ) * normalY ) ; } else { signBL = Math . sign ( bbox . minX - line . start . x ) ; signBR = Math . sign ( bbox . maxX - line . start . x ) ; signTL = Math . sign ( bbox . minX - line . start . x ) ; signTR = Math . sign ( bbox . maxX - line . start . x ) ; } return signBL !== signBR || signBL !== signTL || signBL !== signTR ; } } hidden export class ScreenCollisionsDebug extends ScreenCollisions { /** 2D rendering context. */ private m_renderContext : CanvasRenderingContext2D | null = null ; private m_renderingEnabled = false ; private m_numAllocations = 0 ; private m_numSuccessfulTests = 0 ; private m_numFailedTests = 0 ; private m_numSuccessfulVisibilityTests = 0 ; private m_numFailedVisibilityTests = 0 ; /**
     * Constructs a new ScreenCollisions object which renders its state to a 2D canvas.
     */ constructor ( debugCanvas ) { super ( ) ; if ( debugCanvas !== undefined && debugCanvas !== null ) { this . m_renderContext = debugCanvas . getContext ( "2d" ) ; } } @override
      reset ( ) { super . reset ( ) ; this . m_numAllocations = 0 ; this . m_numSuccessfulTests = 0 ; this . m_numFailedTests = 0 ; this . m_numSuccessfulVisibilityTests = 0 ; this . m_numFailedVisibilityTests = 0 ; } param width param height @override
      update ( width , height ) { if ( this . m_renderingEnabled ) { logger . log ( `Allocations: ${ this . m_numAllocations } Successful Tests: ${ this . m_numSuccessfulTests } Failed Tests: ${ this . m_numFailedTests }  Successful Visibility Tests: ${ this . m_numSuccessfulVisibilityTests }  Failed Visibility Tests: ${ this . m_numFailedVisibilityTests } ` ) ; } super . update ( width , height ) ; if ( this . m_renderContext !== null ) { this . m_renderContext . canvas . width = width ; this . m_renderContext . canvas . height = height ; } this . m_renderingEnabled = debugContext . getValue ( "DEBUG_SCREEN_COLLISIONS" ) ; } param bounds @override
      allocate ( bounds ) { super . allocate ( bounds ) ; this . m_numAllocations ++ ; if ( this . m_renderingEnabled && this . m_renderContext !== null ) { this . m_renderContext . strokeStyle = "#6666ff" ; this . m_renderContext . strokeRect ( bounds . x - this . screenBounds . x , this . screenBounds . y + this . screenBounds . h - bounds . y , bounds . w , - bounds . h ) ; } } @override  allocateIBoxes ( boundsArray ) { for ( const bounds of boundsArray ) { this . m_numAllocations ++ ; if ( this . m_renderingEnabled && this . m_renderContext !== null ) { this . m_renderContext . strokeStyle = "#aa2222" ; this . m_renderContext . strokeRect ( bounds . minX - this . screenBounds . x , this . screenBounds . y + this . screenBounds . h - bounds . minY , bounds . maxX - bounds . minX , - ( bounds . maxY - bounds . minY ) ) ; } } super . allocateIBoxes ( boundsArray ) ; } @override  intersectsDetails ( testBox , boxes ) { const collisionFound = super . intersectsDetails ( testBox , boxes ) ; if ( this . m_renderingEnabled && this . m_renderContext !== null ) { const padding = collisionFound ? 2 : 1 ; this . m_renderContext . strokeStyle = collisionFound ? "#FF0000" : "#00ff00" ; this . m_renderContext . strokeRect ( testBox . x - this . screenBounds . x - padding , this . screenBounds . y + this . screenBounds . h - testBox . y + padding , testBox . w + 2 * padding , - testBox . h - 2 * padding ) ; } if ( collisionFound ) { this . m_numFailedTests ++ ; } else { this . m_numSuccessfulTests ++ ; } return collisionFound ; } param bounds @override
      isVisible ( bounds ) { const visible = super . isVisible ( bounds ) ; if ( visible ) { this . m_numSuccessfulVisibilityTests ++ ; } else { this . m_numFailedVisibilityTests ++ ; } return visible ; } }