@@ -188,9 +188,23 @@ class RenderWebGL extends EventEmitter {
188
188
/** @type {function } */
189
189
this . _exitRegion = null ;
190
190
191
+ /** @type {object } */
192
+ this . _backgroundDrawRegionId = {
193
+ enter : ( ) => this . _enterDrawBackground ( ) ,
194
+ exit : ( ) => this . _exitDrawBackground ( )
195
+ } ;
196
+
191
197
/** @type {Array.<snapshotCallback> } */
192
198
this . _snapshotCallbacks = [ ] ;
193
199
200
+ /** @type {Array<number> } */
201
+ // Don't set this directly-- use setBackgroundColor so it stays in sync with _backgroundColor3b
202
+ this . _backgroundColor4f = [ 0 , 0 , 0 , 1 ] ;
203
+
204
+ /** @type {Uint8ClampedArray } */
205
+ // Don't set this directly-- use setBackgroundColor so it stays in sync with _backgroundColor4f
206
+ this . _backgroundColor3b = new Uint8ClampedArray ( 3 ) ;
207
+
194
208
this . _createGeometry ( ) ;
195
209
196
210
this . on ( RenderConstants . Events . NativeSizeChanged , this . onNativeSizeChanged ) ;
@@ -250,7 +264,14 @@ class RenderWebGL extends EventEmitter {
250
264
* @param {number } blue The blue component for the background.
251
265
*/
252
266
setBackgroundColor ( red , green , blue ) {
253
- this . _backgroundColor = [ red , green , blue , 1 ] ;
267
+ this . _backgroundColor4f [ 0 ] = red ;
268
+ this . _backgroundColor4f [ 1 ] = green ;
269
+ this . _backgroundColor4f [ 2 ] = blue ;
270
+
271
+ this . _backgroundColor3b [ 0 ] = red * 255 ;
272
+ this . _backgroundColor3b [ 1 ] = green * 255 ;
273
+ this . _backgroundColor3b [ 2 ] = blue * 255 ;
274
+
254
275
}
255
276
256
277
/**
@@ -629,7 +650,7 @@ class RenderWebGL extends EventEmitter {
629
650
630
651
twgl . bindFramebufferInfo ( gl , null ) ;
631
652
gl . viewport ( 0 , 0 , gl . canvas . width , gl . canvas . height ) ;
632
- gl . clearColor . apply ( gl , this . _backgroundColor ) ;
653
+ gl . clearColor . apply ( gl , this . _backgroundColor4f ) ;
633
654
gl . clear ( gl . COLOR_BUFFER_BIT ) ;
634
655
635
656
this . _drawThese ( this . _drawList , ShaderManager . DRAW_MODE . default , this . _projection ) ;
@@ -745,12 +766,22 @@ class RenderWebGL extends EventEmitter {
745
766
*/
746
767
isTouchingColor ( drawableID , color3b , mask3b ) {
747
768
const candidates = this . _candidatesTouching ( drawableID , this . _visibleDrawList ) ;
748
- if ( candidates . length === 0 ) {
769
+
770
+ let bounds ;
771
+ if ( colorMatches ( color3b , this . _backgroundColor3b , 0 ) ) {
772
+ // If the color we're checking for is the background color, don't confine the check to
773
+ // candidate drawables' bounds--since the background spans the entire stage, we must check
774
+ // everything that lies inside the drawable.
775
+ bounds = this . _touchingBounds ( drawableID ) ;
776
+ // e.g. empty costume, or off the stage
777
+ if ( bounds === null ) return false ;
778
+ } else if ( candidates . length === 0 ) {
779
+ // If not checking for the background color, we can return early if there are no candidate drawables.
749
780
return false ;
781
+ } else {
782
+ bounds = this . _candidatesBounds ( candidates ) ;
750
783
}
751
784
752
- const bounds = this . _candidatesBounds ( candidates ) ;
753
-
754
785
const maxPixelsForCPU = this . _getMaxPixelsForCPU ( ) ;
755
786
756
787
const debugCanvasContext = this . _debugCanvas && this . _debugCanvas . getContext ( '2d' ) ;
@@ -811,6 +842,19 @@ class RenderWebGL extends EventEmitter {
811
842
}
812
843
}
813
844
845
+ _enterDrawBackground ( ) {
846
+ const gl = this . gl ;
847
+ const currentShader = this . _shaderManager . getShader ( ShaderManager . DRAW_MODE . background , 0 ) ;
848
+ gl . disable ( gl . BLEND ) ;
849
+ gl . useProgram ( currentShader . program ) ;
850
+ twgl . setBuffersAndAttributes ( gl , currentShader , this . _bufferInfo ) ;
851
+ }
852
+
853
+ _exitDrawBackground ( ) {
854
+ const gl = this . gl ;
855
+ gl . enable ( gl . BLEND ) ;
856
+ }
857
+
814
858
_isTouchingColorGpuStart ( drawableID , candidateIDs , bounds , color3b , mask3b ) {
815
859
this . _doExitDrawRegion ( ) ;
816
860
@@ -822,15 +866,8 @@ class RenderWebGL extends EventEmitter {
822
866
gl . viewport ( 0 , 0 , bounds . width , bounds . height ) ;
823
867
const projection = twgl . m4 . ortho ( bounds . left , bounds . right , bounds . top , bounds . bottom , - 1 , 1 ) ;
824
868
825
- let fillBackgroundColor = this . _backgroundColor ;
826
-
827
- // When using masking such that the background fill color will showing through, ensure we don't
828
- // fill using the same color that we are trying to detect!
829
- if ( color3b [ 0 ] > 196 && color3b [ 1 ] > 196 && color3b [ 2 ] > 196 ) {
830
- fillBackgroundColor = [ 0 , 0 , 0 , 255 ] ;
831
- }
832
-
833
- gl . clearColor . apply ( gl , fillBackgroundColor ) ;
869
+ // Clear the query buffer to fully transparent. This will be the color of pixels that fail the stencil test.
870
+ gl . clearColor ( 0 , 0 , 0 , 0 ) ;
834
871
gl . clear ( gl . COLOR_BUFFER_BIT | gl . STENCIL_BUFFER_BIT ) ;
835
872
836
873
let extraUniforms ;
@@ -842,6 +879,9 @@ class RenderWebGL extends EventEmitter {
842
879
}
843
880
844
881
try {
882
+ // Using the stencil buffer, mask out the drawing to either the drawable's alpha channel
883
+ // or pixels of the drawable which match the mask color, depending on whether a mask color is given.
884
+ // Masked-out pixels will not be checked.
845
885
gl . enable ( gl . STENCIL_TEST ) ;
846
886
gl . stencilFunc ( gl . ALWAYS , 1 , 1 ) ;
847
887
gl . stencilOp ( gl . KEEP , gl . KEEP , gl . REPLACE ) ;
@@ -862,12 +902,25 @@ class RenderWebGL extends EventEmitter {
862
902
gl . stencilOp ( gl . KEEP , gl . KEEP , gl . KEEP ) ;
863
903
gl . colorMask ( true , true , true , true ) ;
864
904
905
+ // Draw the background as a quad. Drawing a background with gl.clear will not mask to the stenciled area.
906
+ this . enterDrawRegion ( this . _backgroundDrawRegionId ) ;
907
+
908
+ const uniforms = {
909
+ u_backgroundColor : this . _backgroundColor4f
910
+ } ;
911
+
912
+ const currentShader = this . _shaderManager . getShader ( ShaderManager . DRAW_MODE . background , 0 ) ;
913
+ twgl . setUniforms ( currentShader , uniforms ) ;
914
+ twgl . drawBufferInfo ( gl , this . _bufferInfo , gl . TRIANGLES ) ;
915
+
916
+ // Draw the candidate drawables on top of the background.
865
917
this . _drawThese ( candidateIDs , ShaderManager . DRAW_MODE . default , projection ,
866
918
{ idFilterFunc : testID => testID !== drawableID }
867
919
) ;
868
920
} finally {
869
921
gl . colorMask ( true , true , true , true ) ;
870
922
gl . disable ( gl . STENCIL_TEST ) ;
923
+ this . _doExitDrawRegion ( ) ;
871
924
}
872
925
}
873
926
@@ -886,7 +939,8 @@ class RenderWebGL extends EventEmitter {
886
939
}
887
940
888
941
for ( let pixelBase = 0 ; pixelBase < pixels . length ; pixelBase += 4 ) {
889
- if ( colorMatches ( color3b , pixels , pixelBase ) ) {
942
+ // Transparent pixels are masked (either by the drawable's alpha channel or color mask).
943
+ if ( pixels [ pixelBase + 3 ] !== 0 && colorMatches ( color3b , pixels , pixelBase ) ) {
890
944
return true ;
891
945
}
892
946
}
@@ -1321,7 +1375,7 @@ class RenderWebGL extends EventEmitter {
1321
1375
gl . viewport ( 0 , 0 , bounds . width , bounds . height ) ;
1322
1376
const projection = twgl . m4 . ortho ( bounds . left , bounds . right , bounds . top , bounds . bottom , - 1 , 1 ) ;
1323
1377
1324
- gl . clearColor . apply ( gl , this . _backgroundColor ) ;
1378
+ gl . clearColor . apply ( gl , this . _backgroundColor4f ) ;
1325
1379
gl . clear ( gl . COLOR_BUFFER_BIT ) ;
1326
1380
this . _drawThese ( this . _drawList , ShaderManager . DRAW_MODE . default , projection ) ;
1327
1381
@@ -1411,6 +1465,13 @@ class RenderWebGL extends EventEmitter {
1411
1465
// Update the CPU position data
1412
1466
drawable . updateCPURenderAttributes ( ) ;
1413
1467
const candidateBounds = drawable . getFastBounds ( ) ;
1468
+
1469
+ // Push bounds out to integers. If a drawable extends out into half a pixel, that half-pixel still
1470
+ // needs to be tested. Plus, in some areas we construct another rectangle from the union of these,
1471
+ // and iterate over its pixels (width * height). Turns out that doesn't work so well when the
1472
+ // width/height aren't integers.
1473
+ candidateBounds . snapToInt ( ) ;
1474
+
1414
1475
if ( bounds . intersects ( candidateBounds ) ) {
1415
1476
result . push ( {
1416
1477
id,
0 commit comments