import { SmoothGraphicsData } from './core/SmoothGraphicsData' ; import { FILL_COMMANDS } from './shapes' ; import { Buffer , Geometry , } from '@pixi/core' ; import { WRAP_MODES , TYPES } from '@pixi/constants' ; import { SHAPES , Point , Matrix } from '@pixi/math' ; import { premultiplyTint } from '@pixi/utils' ; import { Bounds } from '@pixi/display' ; import type { Circle , Ellipse , Polygon , Rectangle , RoundedRectangle , IPointData } from '@pixi/math' ; import { BuildData } from './core/BuildData' ; import { SegmentPacker } from './core/SegmentPacker' ; import { LineStyle } from './core/LineStyle' ; import { FillStyle } from './core/FillStyle' ; import { BatchPart } from './core/BatchPart' ; import { BatchDrawCall , IGraphicsBatchSettings , matrixEquals } from './core/BatchDrawCall' ; export type IShape = Circle | Ellipse | Polygon | Rectangle | RoundedRectangle ; export const BATCH_POOL = [ ] ; export const DRAW_CALL_POOL = [ ] ; const tmpPoint = new Point ( ) ; const tmpBounds = new Bounds ( ) ; export class SmoothGraphicsGeometry extends Geometry { public static BATCHABLE_SIZE = 100 ; public boundsPadding : number ; indicesUint16 : Uint16Array | Uint32Array = null ; batchable : boolean ; buildData : BuildData ; get points ( ) { return this . buildData . verts ; } get closePointEps ( ) { return this . buildData . closePointEps ; } graphicsData : Array < SmoothGraphicsData > ; drawCalls : Array < BatchDrawCall > ; batchDirty : number ; batches : Array < BatchPart > ; packer : SegmentPacker ; packSize : number ; pack32index : boolean ; strideFloats : number ; protected dirty : number ; protected cacheDirty : number ; protected clearDirty : number ; protected shapeBuildIndex : number ; protected shapeBatchIndex : number ; protected _bounds : Bounds ; protected boundsDirty : number ; _buffer : Buffer ; _indexBuffer : Buffer ; _bufferFloats : Float32Array ; _bufferUint : Uint32Array ; initAttributes ( _static ) { this . _buffer = new Buffer ( null , _static , false ) ; this . _bufferFloats = new Float32Array ( ) ; this . _bufferUint = new Uint32Array ( ) ; this . _indexBuffer = new Buffer ( null , _static , true ) ; this . addAttribute ( 'aPrev' , this . _buffer , 2 , false , TYPES . FLOAT ) . addAttribute ( 'aPoint1' , this . _buffer , 2 , false , TYPES . FLOAT ) . addAttribute ( 'aPoint2' , this . _buffer , 2 , false , TYPES . FLOAT ) . addAttribute ( 'aNext' , this . _buffer , 2 , false , TYPES . FLOAT ) . addAttribute ( 'aTravel' , this . _buffer , 1 , false , TYPES . FLOAT ) . addAttribute ( 'aVertexJoint' , this . _buffer , 1 , false , TYPES . FLOAT ) . addAttribute ( 'aStyleId' , this . _buffer , 1 , false , TYPES . FLOAT ) . addAttribute ( 'aColor' , this . _buffer , 4 , true , TYPES . UNSIGNED_BYTE ) . addIndex ( this . _indexBuffer ) ; this . strideFloats = 12 ; } constructor ( ) { super ( ) ; this . initAttributes ( false ) ; this . buildData = new BuildData ( ) ; this . graphicsData = [ ] ; this . dirty = 0 ; this . batchDirty = - 1 ; this . cacheDirty = - 1 ; this . clearDirty = 0 ; this . drawCalls = [ ] ; this . batches = [ ] ; this . shapeBuildIndex = 0 ; this . shapeBatchIndex = 0 ; this . _bounds = new Bounds ( ) ; this . boundsDirty = - 1 ; this . boundsPadding = 0 ; this . batchable = false ; this . indicesUint16 = null ; this . packer = null ; this . packSize = 0 ; this . pack32index = null ; } public checkInstancing ( instanced , allow32Indices ) { if ( this . packer ) { return ; } this . packer = new SegmentPacker ( ) ; this . pack32index = allow32Indices ; } member readonly public get bounds ( ) : Bounds { if ( this . boundsDirty !== this . dirty ) { this . boundsDirty = this . dirty ; this . calculateBounds ( ) ; } return this . _bounds ; } /**
     * Call if you changed graphicsData manually.
     * Empties all batch buffers.
     */ protected invalidate ( ) { this . boundsDirty = - 1 ; this . dirty ++ ; this . batchDirty ++ ; this . shapeBuildIndex = 0 ; this . shapeBatchIndex = 0 ; this . packSize = 0 ; this . buildData . clear ( ) ; for ( let i = 0 ; i < this . drawCalls . length ; i ++ ) { this . drawCalls [ i ] . clear ( ) ; DRAW_CALL_POOL . push ( this . drawCalls [ i ] ) ; } this . drawCalls . length = 0 ; for ( let i = 0 ; i < this . batches . length ; i ++ ) { const batchPart = this . batches [ i ] ; batchPart . reset ( ) ; BATCH_POOL . push ( batchPart ) ; } this . batches . length = 0 ; } public clear ( ) { if ( this . graphicsData . length > 0 ) { this . invalidate ( ) ; this . clearDirty ++ ; this . graphicsData . length = 0 ; } return this ; } public drawShape ( shape , fillStyle = null , lineStyle = null , matrix = null ) { const data = new SmoothGraphicsData ( shape , fillStyle , lineStyle , matrix ) ; this . graphicsData . push ( data ) ; this . dirty ++ ; return this ; } public drawHole ( shape , matrix = null ) { if ( ! this . graphicsData . length ) { return null ; } const data = new SmoothGraphicsData ( shape , null , null , matrix ) ; const lastShape = this . graphicsData [ this . graphicsData . length - 1 ] ; data . lineStyle = lastShape . lineStyle ; lastShape . holes . push ( data ) ; this . dirty ++ ; return this ; } public destroy ( ) { super . destroy ( ) ; for ( let i = 0 ; i < this . graphicsData . length ; ++ i ) { this . graphicsData [ i ] . destroy ( ) ; } this . buildData . destroy ( ) ; this . buildData = null ; this . indexBuffer . destroy ( ) ; this . indexBuffer = null ; this . graphicsData . length = 0 ; this . graphicsData = null ; this . drawCalls . length = 0 ; this . drawCalls = null ; this . batches . length = 0 ; this . batches = null ; this . _bounds = null ; } param { PIXI . IPointData } point return { Boolean } public containsPoint ( point ) { const graphicsData = this . graphicsData ; for ( let i = 0 ; i < graphicsData . length ; ++ i ) { const data = graphicsData [ i ] ; if ( ! data . fillStyle . visible ) { continue ; } if ( data . shape ) { if ( data . matrix ) { data . matrix . applyInverse ( point , tmpPoint ) ; } else { tmpPoint . copyFrom ( point ) ; } if ( data . shape . contains ( tmpPoint . x , tmpPoint . y ) ) { let hitHole = false ; if ( data . holes ) { for ( let i = 0 ; i < data . holes . length ; i ++ ) { const hole = data . holes [ i ] ; if ( hole . shape . contains ( tmpPoint . x , tmpPoint . y ) ) { hitHole = true ; break ; } } } if ( ! hitHole ) { return true ; } } } } return false ; } updatePoints ( ) { } updateBufferSize ( ) { this . _buffer . update ( new Float32Array ( ) ) ; } updateBuild ( ) { const { graphicsData , buildData } = this ; const len = graphicsData . length ; for ( let i = this . shapeBuildIndex ; i < len ; i ++ ) { const data = graphicsData [ i ] ; data . strokeStart = 0 ; data . strokeLen = 0 ; data . fillStart = 0 ; data . fillLen = 0 ; const { fillStyle , lineStyle , holes } = data ; if ( ! fillStyle . visible && ! lineStyle . visible ) { continue ; } const command = FILL_COMMANDS [ data . type ] ; data . clearPath ( ) ; command . path ( data , buildData ) ; if ( data . matrix ) { this . transformPoints ( data . points , data . matrix ) ; } data . clearBuild ( ) ; if ( data . points . length <= 2 ) { continue ; } if ( fillStyle . visible ) { data . fillAA = ( data . fillStyle as any ) . smooth && ! ( data . lineStyle . visible && data . lineStyle . alpha >= 0.99 && data . lineStyle . width >= 0.99 ) ; data . fillStart = buildData . joints . length ; if ( holes . length ) { this . processHoles ( holes ) ; FILL_COMMANDS [ SHAPES . POLY ] . fill ( data , buildData ) ; } else { if ( data . fillAA && ( data . type === SHAPES . RECT || data . type === SHAPES . RREC ) ) { FILL_COMMANDS [ SHAPES . POLY ] . fill ( data , buildData ) ; } else { command . fill ( data , buildData ) ; } } data . fillLen = buildData . joints . length - data . fillStart ; } if ( lineStyle . visible ) { data . strokeStart = buildData . joints . length ; command . line ( data , buildData ) ; for ( let i = 0 ; i < holes . length ; i ++ ) { command . line ( holes [ i ] , buildData ) ; } data . strokeLen = buildData . joints . length - data . strokeStart ; } } this . shapeBuildIndex = len ; } updateBatches ( shaderSettings ? ) { if ( ! this . graphicsData . length ) { this . batchable = true ; return ; } this . updateBuild ( ) ; if ( ! this . validateBatching ( ) ) { return ; } const { buildData , graphicsData } = this ; const len = graphicsData . length ; this . cacheDirty = this . dirty ; let batchPart = null ; let currentStyle = null ; if ( this . batches . length > 0 ) { batchPart = this . batches [ this . batches . length - 1 ] ; currentStyle = batchPart . style ; } for ( let i = this . shapeBatchIndex ; i < len ; i ++ ) { const data = graphicsData [ i ] ; const fillStyle = data . fillStyle ; const lineStyle = data . lineStyle ; if ( data . matrix ) { this . transformPoints ( data . points , data . matrix ) ; } if ( ! fillStyle . visible && ! lineStyle . visible ) { continue ; } for ( let j = 0 ; j < 2 ; j ++ ) { const style = ( j === 0 ) ? fillStyle : lineStyle ; if ( ! style . visible ) continue ; const nextTexture = style . texture . baseTexture ; const attribOld = buildData . vertexSize ; const indexOld = buildData . indexSize ; nextTexture . wrapMode = WRAP_MODES . REPEAT ; if ( j === 0 ) { this . packer . updateBufferSize ( data . fillStart , data . fillLen , data . triangles . length , buildData ) ; } else { this . packer . updateBufferSize ( data . strokeStart , data . strokeLen , data . triangles . length , buildData ) ; } const attribSize = buildData . vertexSize ; if ( attribSize === attribOld ) continue ; if ( batchPart && ! this . _compareStyles ( currentStyle , style ) ) { batchPart . end ( indexOld , attribOld ) ; batchPart = null ; } if ( ! batchPart ) { batchPart = BATCH_POOL . pop ( ) || new BatchPart ( ) ; batchPart . begin ( style , indexOld , attribOld ) ; this . batches . push ( batchPart ) ; currentStyle = style ; } if ( j === 0 ) { batchPart . jointEnd = data . fillStart + data . fillLen ; } else { batchPart . jointEnd = data . strokeStart + data . strokeLen ; } } } this . shapeBatchIndex = len ; if ( batchPart ) { batchPart . end ( buildData . indexSize , buildData . vertexSize ) ; } if ( this . batches . length === 0 ) { this . batchable = true ; return ; } this . batchable = this . isBatchable ( ) ; if ( this . batchable ) { this . packBatches ( ) ; } else { this . buildDrawCalls ( shaderSettings ) ; this . updatePack ( ) ; } } updatePack ( ) { const { vertexSize , indexSize } = this . buildData ; if ( this . packSize === vertexSize ) { return ; } const { strideFloats , packer , buildData , batches } = this ; const buffer = this . _buffer ; const index = this . _indexBuffer ; const floatsSize = vertexSize * strideFloats ; if ( buffer . data . length !== floatsSize ) { const arrBuf = new ArrayBuffer ( floatsSize * 4 ) ; this . _bufferFloats = new Float32Array ( arrBuf ) ; this . _bufferUint = new Uint32Array ( arrBuf ) ; buffer . data = this . _bufferFloats ; } if ( index . data . length !== indexSize ) { if ( vertexSize > 0xffff && this . pack32index ) { index . data = new Uint32Array ( indexSize ) ; } else { index . data = new Uint16Array ( indexSize ) ; } } packer . beginPack ( buildData , this . _bufferFloats , this . _bufferUint , index . data as Uint16Array ) ; let j = 0 ; for ( let i = 0 ; i < this . graphicsData . length ; i ++ ) { const data = this . graphicsData [ i ] ; if ( data . fillLen ) { while ( batches [ j ] . jointEnd <= data . fillStart ) { j ++ ; } packer . packInterleavedGeometry ( data . fillStart , data . fillLen , data . triangles , batches [ j ] . styleId , batches [ j ] . rgba ) ; } if ( data . strokeLen ) { while ( batches [ j ] . jointEnd <= data . strokeStart ) { j ++ ; } packer . packInterleavedGeometry ( data . strokeStart , data . strokeLen , data . triangles , batches [ j ] . styleId , batches [ j ] . rgba ) ; } } buffer . update ( ) ; index . update ( ) ; this . packSize = vertexSize ; } param { PIXI . FillStyle | PIXI . LineStyle } styleA param { PIXI . FillStyle | PIXI . LineStyle } styleB protected _compareStyles ( styleA , styleB ) { if ( ! styleA || ! styleB ) { return false ; } if ( styleA . texture . baseTexture !== styleB . texture . baseTexture ) { return false ; } if ( styleA . color + styleA . alpha !== styleB . color + styleB . alpha ) { return false ; } if ( ( styleA as LineStyle ) . width !== ( styleB as LineStyle ) . width ) { return false ; } if ( ( styleA as LineStyle ) . scaleMode !== ( styleB as LineStyle ) . scaleMode ) { return false ; } if ( ( styleA as LineStyle ) . alignment !== ( styleB as LineStyle ) . alignment ) { return false ; } const mat1 = styleA . matrix || Matrix . IDENTITY ; const mat2 = styleB . matrix || Matrix . IDENTITY ; return matrixEquals ( mat1 , mat2 ) ; } protected protected validateBatching ( ) { if ( this . dirty === this . cacheDirty || ! this . graphicsData . length ) { return false ; } for ( let i = 0 , l = this . graphicsData . length ; i < l ; i ++ ) { const data = this . graphicsData [ i ] ; const fill = data . fillStyle ; const line = data . lineStyle ; if ( fill && ! fill . texture . baseTexture . valid ) return false ; if ( line && ! line . texture . baseTexture . valid ) return false ; } return true ; } protected protected packBatches ( ) { this . batchDirty ++ ; const batches = this . batches ; for ( let i = 0 , l = batches . length ; i < l ; i ++ ) { const batch = batches [ i ] ; for ( let j = 0 ; j < batch . size ; j ++ ) { const index = batch . start + j ; this . indicesUint16 [ index ] = this . indicesUint16 [ index ] - batch . attribStart ; } } } protected isBatchable ( ) { return false ; } protected protected buildDrawCalls ( shaderSettings ? ) { for ( let i = 0 ; i < this . drawCalls . length ; i ++ ) { this . drawCalls [ i ] . clear ( ) ; DRAW_CALL_POOL . push ( this . drawCalls [ i ] ) ; } this . drawCalls . length = 0 ; let currentGroup = DRAW_CALL_POOL . pop ( ) || new BatchDrawCall ( ) ; currentGroup . begin ( shaderSettings , null ) ; let index = 0 ; this . drawCalls . push ( currentGroup ) ; for ( let i = 0 ; i < this . batches . length ; i ++ ) { const batchData = this . batches [ i ] ; const style = batchData . style as LineStyle ; if ( batchData . attribSize === 0 ) { continue ; } let styleId = - 1 ; const mat = style . getTextureMatrix ( ) ; if ( currentGroup . check ( style . shader ) ) { styleId = currentGroup . add ( style . texture , mat , style . width , style . alignment || 0 , style . packLineScale ( ) ) ; } if ( styleId < 0 ) { currentGroup = DRAW_CALL_POOL . pop ( ) || new BatchDrawCall ( ) ; this . drawCalls . push ( currentGroup ) ; currentGroup . begin ( shaderSettings , style . shader ) ; currentGroup . start = index ; styleId = currentGroup . add ( style . texture , mat , style . width , style . alignment || 0 , style . packLineScale ( ) ) ; } currentGroup . size += batchData . size ; index += batchData . size ; const { color , alpha } = style ; const rgb = ( color >> 16 ) + ( color & 0xff00 ) + ( ( color & 0xff ) << 16 ) ; batchData . rgba = premultiplyTint ( rgb , alpha ) ; batchData . styleId = styleId ; } } protected processHoles ( holes ) { for ( let i = 0 ; i < holes . length ; i ++ ) { const hole = holes [ i ] ; const command = FILL_COMMANDS [ hole . type ] ; command . path ( hole , this . buildData ) ; if ( hole . matrix ) { this . transformPoints ( hole . points , hole . matrix ) ; } } } protected protected calculateBounds ( ) { const bounds = this . _bounds ; const sequenceBounds = tmpBounds ; let curMatrix = Matrix . IDENTITY ; this . _bounds . clear ( ) ; sequenceBounds . clear ( ) ; for ( let i = 0 ; i < this . graphicsData . length ; i ++ ) { const data = this . graphicsData [ i ] ; const shape = data . shape ; const type = data . type ; const lineStyle = data . lineStyle ; const nextMatrix = data . matrix || Matrix . IDENTITY ; let lineWidth = 0.0 ; if ( lineStyle && lineStyle . visible ) { const alignment = lineStyle . alignment ; lineWidth = lineStyle . width ; if ( type === SHAPES . POLY ) { lineWidth = lineWidth * ( 0.5 + Math . abs ( 0.5 - alignment ) ) ; } else { lineWidth = lineWidth * Math . max ( 0 , alignment ) ; } } if ( curMatrix !== nextMatrix ) { if ( ! sequenceBounds . isEmpty ( ) ) { bounds . addBoundsMatrix ( sequenceBounds , curMatrix ) ; sequenceBounds . clear ( ) ; } curMatrix = nextMatrix ; } if ( type === SHAPES . RECT || type === SHAPES . RREC ) { const rect = shape as Rectangle | RoundedRectangle ; sequenceBounds . addFramePad ( rect . x , rect . y , rect . x + rect . width , rect . y + rect . height , lineWidth , lineWidth ) ; } else if ( type === SHAPES . CIRC ) { const circle = shape as Circle ; sequenceBounds . addFramePad ( circle . x , circle . y , circle . x , circle . y , circle . radius + lineWidth , circle . radius + lineWidth ) ; } else if ( type === SHAPES . ELIP ) { const ellipse = shape as Ellipse ; sequenceBounds . addFramePad ( ellipse . x , ellipse . y , ellipse . x , ellipse . y , ellipse . width + lineWidth , ellipse . height + lineWidth ) ; } else { const poly = shape as Polygon ; bounds . addVerticesMatrix ( curMatrix , ( poly . points as any ) , 0 , poly . points . length , lineWidth , lineWidth ) ; } } if ( ! sequenceBounds . isEmpty ( ) ) { bounds . addBoundsMatrix ( sequenceBounds , curMatrix ) ; } bounds . pad ( this . boundsPadding , this . boundsPadding ) ; } protected param { number [ ] } points param { PIXI . Matrix } matrix protected transformPoints ( points , matrix ) { for ( let i = 0 ; i < points . length / 2 ; i ++ ) { const x = points [ ( i * 2 ) ] ; const y = points [ ( i * 2 ) + 1 ] ; points [ ( i * 2 ) ] = ( matrix . a * x ) + ( matrix . c * y ) + matrix . tx ; points [ ( i * 2 ) + 1 ] = ( matrix . b * x ) + ( matrix . d * y ) + matrix . ty ; } } }