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,11 +26,14 @@ 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 /**
@@ -60,6 +64,33 @@ class SVGSkin extends Skin {
6064 super . setRotationCenter ( x - viewOffset [ 0 ] , y - viewOffset [ 1 ] ) ;
6165 }
6266
67+ /**
68+ * Create a MIP for a given scale and pass it a callback for updating
69+ * state when switching between scales and MIPs.
70+ * @param {number } scale - The relative size of the MIP
71+ * @return {SVGMIP } An object that handles creating and updating SVG textures.
72+ */
73+ createMIP ( scale ) {
74+ const callback = textureData => {
75+ if ( scale > this . _largestTextureScale ) {
76+ this . _largestTextureScale = scale ;
77+ // Currently silhouette only gets scaled up
78+ this . _silhouette . update ( textureData ) ;
79+ }
80+
81+ if ( scale === 1 ) {
82+ const maxDimension = Math . max ( textureData . width , textureData . height ) ;
83+ let testScale = 2 ;
84+ for ( testScale ; maxDimension * testScale <= MAX_TEXTURE_DIMENSION ; testScale *= 2 ) {
85+ this . _maxTextureScale = testScale ;
86+ }
87+ }
88+ } ;
89+ const mip = new SVGMIP ( this . _renderer , this . _svgRenderer , scale , callback ) ;
90+
91+ return mip ;
92+ }
93+
6394 /**
6495 * @param {Array<number> } scale - The scaling factors to be used, each in the [0,100] range.
6596 * @return {WebGLTexture } The GL texture representation of this skin when drawing at the given scale.
@@ -69,27 +100,26 @@ class SVGSkin extends Skin {
69100 // The texture only ever gets uniform scale. Take the larger of the two axes.
70101 const scaleMax = scale ? Math . max ( Math . abs ( scale [ 0 ] ) , Math . abs ( scale [ 1 ] ) ) : 100 ;
71102 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 ;
103+ let newScale = 1 ;
104+ let textureIndex = 0 ;
105+
106+ if ( requestedScale < 1 ) {
107+ while ( ( newScale > MIN_TEXTURE_SCALE ) && ( requestedScale <= newScale * .75 ) ) {
108+ newScale /= 2 ;
109+ textureIndex -= 1 ;
110+ }
111+ } else {
112+ while ( ( newScale < this . _maxTextureScale ) && ( requestedScale >= 1.5 * newScale ) ) {
113+ newScale *= 2 ;
114+ textureIndex += 1 ;
115+ }
75116 }
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- } ) ;
117+
118+ if ( ! this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] ) {
119+ this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] = this . createMIP ( newScale ) ;
90120 }
91121
92- return this . _texture ;
122+ return this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] . getTexture ( ) ;
93123 }
94124
95125 /**
@@ -100,43 +130,25 @@ class SVGSkin extends Skin {
100130 * @fires Skin.event:WasAltered
101131 */
102132 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- }
133+ this . _svgRenderer . loadString ( svgData ) ;
134+
135+ if ( this . _texture ) {
136+ this . _texture = this . _scaledMIPs [ INDEX_OFFSET ] ;
137+ this . _texture . draw ( ) ;
129138
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 ;
139+ for ( const index in this . _scaledMIPs ) {
140+ if ( index !== INDEX_OFFSET ) {
141+ this . _scaledMIPs [ index ] . dirty = true ;
142+ }
134143 }
144+ } else {
145+ const textureScale = 1 ;
146+ this . _texture = this . _scaledMIPs [ INDEX_OFFSET ] = this . createMIP ( textureScale ) ;
147+ }
135148
136- if ( typeof rotationCenter === 'undefined' ) rotationCenter = this . calculateRotationCenter ( ) ;
137- this . setRotationCenter . apply ( this , rotationCenter ) ;
138- this . emit ( Skin . Events . WasAltered ) ;
139- } ) ;
149+ if ( typeof rotationCenter === 'undefined' ) rotationCenter = this . calculateRotationCenter ( ) ;
150+ this . setRotationCenter . apply ( this , rotationCenter ) ;
151+ this . emit ( Skin . Events . WasAltered ) ;
140152 }
141153
142154}
0 commit comments