1- const twgl = require ( 'twgl.js' ) ;
2-
31const Skin = require ( './Skin' ) ;
2+ const SVGMIP = require ( './SVGMIP' ) ;
43const SvgRenderer = require ( 'scratch-svg-renderer' ) . SVGRenderer ;
54
65const MAX_TEXTURE_DIMENSION = 2048 ;
6+ const MIN_TEXTURE_SCALE = 1 / 256 ;
7+ const INDEX_OFFSET = 8 ;
78
89class SVGSkin extends Skin {
910 /**
@@ -25,20 +26,29 @@ class SVGSkin extends Skin {
2526 /** @type {WebGLTexture } */
2627 this . _texture = null ;
2728
28- /** @type {number } */
29- this . _textureScale = 1 ;
29+ /** @type {Array.<SVGMIPs> } */
30+ this . _scaledMIPs = [ ] ;
31+
32+ /** @type {Number } */
33+ this . _maxTextureScale = 1 ;
3034
3135 /** @type {Number } */
32- this . _maxTextureScale = 0 ;
36+ this . _largestTextureScale = 1 ;
3337 }
3438
3539 /**
3640 * Dispose of this object. Do not use it after calling this method.
3741 */
3842 dispose ( ) {
3943 if ( this . _texture ) {
40- this . _renderer . gl . deleteTexture ( this . _texture ) ;
44+ for ( const mip of this . _scaledMIPs ) {
45+ if ( mip ) {
46+ this . _renderer . gl . deleteTexture ( mip . getTexture ( ) ) ;
47+ }
48+ }
4149 this . _texture = null ;
50+ this . _scaledMIPs . length = 0 ;
51+ this . _largestTextureScale = 1 ;
4252 }
4353 super . dispose ( ) ;
4454 }
@@ -60,6 +70,34 @@ class SVGSkin extends Skin {
6070 super . setRotationCenter ( x - viewOffset [ 0 ] , y - viewOffset [ 1 ] ) ;
6171 }
6272
73+ /**
74+ * Create a MIP for a given scale and pass it a callback for updating
75+ * state when switching between scales and MIPs.
76+ * @param {number } scale - The relative size of the MIP
77+ * @return {SVGMIP } An object that handles creating and updating SVG textures.
78+ */
79+ createMIP ( scale ) {
80+ const callback = textureData => {
81+ if ( scale > this . _largestTextureScale ) {
82+ this . _largestTextureScale = scale ;
83+ // Currently silhouette only gets scaled up
84+ this . _silhouette . update ( textureData ) ;
85+ }
86+
87+ if ( scale === 1 ) {
88+ const maxDimension = Math . max ( textureData . width , textureData . height ) ;
89+ let testScale = 2 ;
90+ for ( testScale ; maxDimension * testScale <= MAX_TEXTURE_DIMENSION ; testScale *= 2 ) {
91+ this . _maxTextureScale = testScale ;
92+ }
93+ this . _silhouette . update ( textureData ) ;
94+ }
95+ } ;
96+ const mip = new SVGMIP ( this . _renderer , this . _svgRenderer , scale , callback ) ;
97+
98+ return mip ;
99+ }
100+
63101 /**
64102 * @param {Array<number> } scale - The scaling factors to be used, each in the [0,100] range.
65103 * @return {WebGLTexture } The GL texture representation of this skin when drawing at the given scale.
@@ -69,27 +107,26 @@ class SVGSkin extends Skin {
69107 // The texture only ever gets uniform scale. Take the larger of the two axes.
70108 const scaleMax = scale ? Math . max ( Math . abs ( scale [ 0 ] ) , Math . abs ( scale [ 1 ] ) ) : 100 ;
71109 const requestedScale = Math . min ( scaleMax / 100 , this . _maxTextureScale ) ;
72- let newScale = this . _textureScale ;
73- while ( ( newScale < this . _maxTextureScale ) && ( requestedScale >= 1.5 * newScale ) ) {
74- newScale *= 2 ;
110+ let newScale = 1 ;
111+ let textureIndex = 0 ;
112+
113+ if ( requestedScale < 1 ) {
114+ while ( ( newScale > MIN_TEXTURE_SCALE ) && ( requestedScale <= newScale * .75 ) ) {
115+ newScale /= 2 ;
116+ textureIndex -= 1 ;
117+ }
118+ } else {
119+ while ( ( newScale < this . _maxTextureScale ) && ( requestedScale >= 1.5 * newScale ) ) {
120+ newScale *= 2 ;
121+ textureIndex += 1 ;
122+ }
75123 }
76- if ( this . _textureScale !== newScale ) {
77- this . _textureScale = newScale ;
78- this . _svgRenderer . _draw ( this . _textureScale , ( ) => {
79- if ( this . _textureScale === newScale ) {
80- const canvas = this . _svgRenderer . canvas ;
81- const context = canvas . getContext ( '2d' ) ;
82- const textureData = context . getImageData ( 0 , 0 , canvas . width , canvas . height ) ;
83-
84- const gl = this . _renderer . gl ;
85- gl . bindTexture ( gl . TEXTURE_2D , this . _texture ) ;
86- gl . texImage2D ( gl . TEXTURE_2D , 0 , gl . RGBA , gl . RGBA , gl . UNSIGNED_BYTE , textureData ) ;
87- this . _silhouette . update ( textureData ) ;
88- }
89- } ) ;
124+
125+ if ( ! this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] ) {
126+ this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] = this . createMIP ( newScale ) ;
90127 }
91128
92- return this . _texture ;
129+ return this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] . getTexture ( ) ;
93130 }
94131
95132 /**
@@ -100,43 +137,25 @@ class SVGSkin extends Skin {
100137 * @fires Skin.event:WasAltered
101138 */
102139 setSVG ( svgData , rotationCenter ) {
103- this . _svgRenderer . fromString ( svgData , 1 , ( ) => {
104- const gl = this . _renderer . gl ;
105- this . _textureScale = this . _maxTextureScale = 1 ;
106-
107- // Pull out the ImageData from the canvas. ImageData speeds up
108- // updating Silhouette and is better handled by more browsers in
109- // regards to memory.
110- const canvas = this . _svgRenderer . canvas ;
111- const context = canvas . getContext ( '2d' ) ;
112- const textureData = context . getImageData ( 0 , 0 , canvas . width , canvas . height ) ;
113-
114- if ( this . _texture ) {
115- gl . bindTexture ( gl . TEXTURE_2D , this . _texture ) ;
116- gl . texImage2D ( gl . TEXTURE_2D , 0 , gl . RGBA , gl . RGBA , gl . UNSIGNED_BYTE , textureData ) ;
117- this . _silhouette . update ( textureData ) ;
118- } else {
119- // TODO: mipmaps?
120- const textureOptions = {
121- auto : true ,
122- wrap : gl . CLAMP_TO_EDGE ,
123- src : textureData
124- } ;
125-
126- this . _texture = twgl . createTexture ( gl , textureOptions ) ;
127- this . _silhouette . update ( textureData ) ;
128- }
140+ this . _svgRenderer . loadString ( svgData ) ;
129141
130- const maxDimension = Math . max ( this . _svgRenderer . canvas . width , this . _svgRenderer . canvas . height ) ;
131- let testScale = 2 ;
132- for ( testScale ; maxDimension * testScale <= MAX_TEXTURE_DIMENSION ; testScale *= 2 ) {
133- this . _maxTextureScale = testScale ;
142+ if ( this . _texture ) {
143+ this . _texture = this . _scaledMIPs [ INDEX_OFFSET ] ;
144+ this . _texture . draw ( ) ;
145+
146+ for ( const index in this . _scaledMIPs ) {
147+ if ( index !== INDEX_OFFSET ) {
148+ this . _scaledMIPs [ index ] . dirty = true ;
149+ }
134150 }
151+ } else {
152+ const textureScale = 1 ;
153+ this . _texture = this . _scaledMIPs [ INDEX_OFFSET ] = this . createMIP ( textureScale ) ;
154+ }
135155
136- if ( typeof rotationCenter === 'undefined' ) rotationCenter = this . calculateRotationCenter ( ) ;
137- this . setRotationCenter . apply ( this , rotationCenter ) ;
138- this . emit ( Skin . Events . WasAltered ) ;
139- } ) ;
156+ if ( typeof rotationCenter === 'undefined' ) rotationCenter = this . calculateRotationCenter ( ) ;
157+ this . setRotationCenter . apply ( this , rotationCenter ) ;
158+ this . emit ( Skin . Events . WasAltered ) ;
140159 }
141160
142161}
0 commit comments