1
- const twgl = require ( 'twgl.js' ) ;
2
-
3
1
const Skin = require ( './Skin' ) ;
2
+ const SVGMIP = require ( './SVGMIP' ) ;
4
3
const SvgRenderer = require ( 'scratch-svg-renderer' ) . SVGRenderer ;
5
4
6
5
const MAX_TEXTURE_DIMENSION = 2048 ;
6
+ const MIN_TEXTURE_SCALE = 1 / 256 ;
7
+ const INDEX_OFFSET = 8 ;
7
8
8
9
class SVGSkin extends Skin {
9
10
/**
@@ -25,11 +26,14 @@ class SVGSkin extends Skin {
25
26
/** @type {WebGLTexture } */
26
27
this . _texture = null ;
27
28
28
- /** @type {number } */
29
- this . _textureScale = 1 ;
29
+ /** @type {Array.<SVGMIPs> } */
30
+ this . _scaledMIPs = [ ] ;
31
+
32
+ /** @type {Number } */
33
+ this . _maxTextureScale = 1 ;
30
34
31
35
/** @type {Number } */
32
- this . _maxTextureScale = 0 ;
36
+ this . _largestTextureScale = 1 ;
33
37
}
34
38
35
39
/**
@@ -60,6 +64,33 @@ class SVGSkin extends Skin {
60
64
super . setRotationCenter ( x - viewOffset [ 0 ] , y - viewOffset [ 1 ] ) ;
61
65
}
62
66
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
+
63
94
/**
64
95
* @param {Array<number> } scale - The scaling factors to be used, each in the [0,100] range.
65
96
* @return {WebGLTexture } The GL texture representation of this skin when drawing at the given scale.
@@ -69,27 +100,26 @@ class SVGSkin extends Skin {
69
100
// The texture only ever gets uniform scale. Take the larger of the two axes.
70
101
const scaleMax = scale ? Math . max ( Math . abs ( scale [ 0 ] ) , Math . abs ( scale [ 1 ] ) ) : 100 ;
71
102
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
+ }
75
116
}
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 ) ;
90
120
}
91
121
92
- return this . _texture ;
122
+ return this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] . getTexture ( ) ;
93
123
}
94
124
95
125
/**
@@ -100,43 +130,25 @@ class SVGSkin extends Skin {
100
130
* @fires Skin.event:WasAltered
101
131
*/
102
132
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 ( ) ;
129
138
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
+ }
134
143
}
144
+ } else {
145
+ const textureScale = 1 ;
146
+ this . _texture = this . _scaledMIPs [ INDEX_OFFSET ] = this . createMIP ( textureScale ) ;
147
+ }
135
148
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 ) ;
140
152
}
141
153
142
154
}
0 commit comments