author Yannick Deubel (https://github.com/yandeu)
 *  copyright license https import { Camera , LinearFilter , Raycaster , Texture , Vector2 } from 'three' ; import type { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' ; import { Tap } from '@yandeu/tap' ; import { SimpleSprite } from './simpleSprite' ; export const roundRect = ( ctx , x , y , w , h , r ) => { if ( w < 2 * r ) r = w / 2 ; if ( h < 2 * r ) r = h / 2 ; ctx . beginPath ( ) ; ctx . moveTo ( x + r , y ) ; ctx . arcTo ( x + w , y , x + w , y + h , r ) ; ctx . arcTo ( x + w , y + h , x , y + h , r ) ; ctx . arcTo ( x , y + h , x , y , r ) ; ctx . arcTo ( x , y , x + w , y , r ) ; ctx . closePath ( ) ; } ; export const fontHeightCache = new Map ( ) ; export const calcHeight = ( text , fontSize , fontFamily , lineHeight = 1 ) => { const key = fontSize + fontFamily ; let height = fontHeightCache . get ( key ) ; if ( ! height ) { const span = document . createElement ( 'p' ) ; span . style . fontFamily = fontFamily ; span . style . fontSize = `${ fontSize }px` ; span . style . whiteSpace = 'nowrap' ; span . style . lineHeight = lineHeight . toString ( ) ; span . textContent = text ; document . body . appendChild ( span ) ; height = Math . ceil ( span . offsetHeight ) ; document . body . removeChild ( span ) ; fontHeightCache . set ( key , height ) ; } return height ; } ; export const calcWidth = ( ctx , lines ) => { return Math . max ( ... lines . map ( line => Math . ceil ( ctx . measureText ( line ) . width ) ) ) ; } ; export const createNewTexture = ( image ) => { const texture = new Texture ( image ) ; texture . minFilter = LinearFilter ; texture . generateMipmaps = false ; texture . needsUpdate = true ; return texture ; } ; export const canvas = document . createElement ( 'canvas' ) ; let parent ; let orbitCtl ; let tap ; let down = false ; const objects = [ ] ; const raycaster = new Raycaster ( ) ; const mouse = new Vector2 ( ) ; const pos = new Vector2 ( ) ; const size = new Vector2 ( ) ; const messages = [ ] ; const warn = ( msg ) => { if ( messages . includes ( msg ) ) return ; console . warn ( msg ) ; messages . push ( msg ) ; } ; export const setSize = ( width , height ) => { size . x = width ; size . y = height ; } ; export const setParent = ( canvas ) => { parent = canvas ; } ; export const getParent = ( ) => parent ; export const setOrbitControls = ( orbitControls ? ) => { if ( orbitControls ) orbitCtl = orbitControls ; } ; const addEventListeners = ( ) => { const canvas = getParent ( ) ; if ( ! canvas ) { warn ( 'Please call "FLAT.initEvents()" first.' ) ; return ; } tap = new Tap ( canvas ) ; } ; export const destroy = ( ) => { clearObjects ( ) ; if ( tap ) tap . destroy ( ) ; } ; export const addObject = ( object ) => { if ( objects . length === 0 ) addEventListeners ( ) ; objects . push ( object ) ; } ; export const clearObjects = ( ) => { while ( objects . length > 0 ) { objects . pop ( ) ; } } ; export const updateEvents = async ( camera ) => { if ( ! tap ) return ; const { currentPosition : { x , y } , isDown } = tap ; const hasMouseMoved = mouse . x !== x || mouse . y !== y ; const hasClicked = down !== isDown ; if ( ! hasMouseMoved && ! hasClicked ) return ; down = isDown ; mouse . x = x ; mouse . y = y ; if ( size . x === 0 || typeof size . x === 'undefined' ) { warn ( '[FLAT] Please call FLAT.setSize() first!' ) ; return ; } pos . x = ( x / size . x ) * 2 - 1 ; pos . y = - ( y / size . y ) * 2 + 1 ; raycaster . setFromCamera ( pos , camera ) ; let _objects = [ ... objects ] ; const intersects = raycaster . intersectObjects ( _objects ) ; if ( intersects . length === 0 ) document . body . style . cursor = 'default' ; else document . body . style . cursor = 'pointer' ; if ( orbitCtl && orbitCtl . enabled && intersects . length >= 0 ) orbitCtl . enabled = false ; if ( orbitCtl && ! orbitCtl . enabled && intersects . length === 0 ) orbitCtl . enabled = true ; for ( let i = 0 ; i < intersects . length ; i ++ ) { const object = intersects [ i ] . object as SimpleSprite ; let isTransparent = false ; if ( object . pixelPerfect ) { const getImageData = ( image ) => { canvas . width = image . width ; canvas . height = image . height ; const context = canvas . getContext ( '2d' ) as CanvasRenderingContext2D ; context . drawImage ( image , 0 , 0 ) ; return context . getImageData ( 0 , 0 , image . width , image . height ) ; } ; const getPixel = ( imagedata , x , y ) => { const position = ( x + imagedata . width * y ) * 4 ; const data = imagedata . data ; return { r : data [ position ] , g : data [ position + 1 ] , b : data [ position + 2 ] , a : data [ position + 3 ] } ; } ; const uv = intersects [ 0 ] . uv ; const { x , y } = object . texture . transformUv ( uv as Vector2 ) ; const bitmap = await createImageBitmap ( object . texture . image ) ; const imageData = getImageData ( bitmap ) as ImageData ; const { r , g , b , a } = getPixel ( imageData , Math . round ( x * imageData . width ) , Math . round ( y * imageData . height ) ) ; isTransparent = r + g + b + a === 0 ; } if ( object . pixelPerfect && isTransparent ) continue ; object . event = tap . isDown ? 'down' : 'over' ; const removeIndex = _objects . findIndex ( o => o . uuid === object . uuid ) ; _objects = [ ... _objects . slice ( 0 , removeIndex ) , ... _objects . slice ( removeIndex + 1 ) ] ; break ; } _objects . forEach ( o => { o . event = 'out' ; } ) ; return intersects ; } ;