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
+ /**
8
+ * All scaled renderings of the SVG are stored in an array. The 1.0 scale of
9
+ * the SVG is stored at the 8th index. The smallest possible 1 / 256 scale
10
+ * rendering is stored at the 0th index.
11
+ * @const {number}
12
+ */
13
+ const INDEX_OFFSET = 8 ;
7
14
8
15
class SVGSkin extends Skin {
9
16
/**
@@ -22,20 +29,28 @@ class SVGSkin extends Skin {
22
29
/** @type {SvgRenderer } */
23
30
this . _svgRenderer = new SvgRenderer ( ) ;
24
31
25
- /** @type {number } */
26
- this . _textureScale = 1 ;
32
+ /** @type {Array.<SVGMIPs> } */
33
+ this . _scaledMIPs = [ ] ;
27
34
28
- /** @type {Number } */
29
- this . _maxTextureScale = 0 ;
35
+ /**
36
+ * Ratio of the size of the SVG and the max size of the WebGL texture
37
+ * @type {Number }
38
+ */
39
+ this . _maxTextureScale = 1 ;
30
40
}
31
41
32
42
/**
33
43
* Dispose of this object. Do not use it after calling this method.
34
44
*/
35
45
dispose ( ) {
36
46
if ( this . _texture ) {
37
- this . _renderer . gl . deleteTexture ( this . _texture ) ;
47
+ for ( const mip of this . _scaledMIPs ) {
48
+ if ( mip ) {
49
+ mip . dispose ( ) ;
50
+ }
51
+ }
38
52
this . _texture = null ;
53
+ this . _scaledMIPs . length = 0 ;
39
54
}
40
55
super . dispose ( ) ;
41
56
}
@@ -57,11 +72,33 @@ class SVGSkin extends Skin {
57
72
super . setRotationCenter ( x - viewOffset [ 0 ] , y - viewOffset [ 1 ] ) ;
58
73
}
59
74
75
+ /**
76
+ * Create a MIP for a given scale and pass it a callback for updating
77
+ * state when switching between scales and MIPs.
78
+ * @param {number } scale - The relative size of the MIP
79
+ * @param {function } resetCallback - this is a callback for doing a hard reset
80
+ * of MIPs and a reset of the rotation center. Only passed in if the MIP scale is 1.
81
+ * @return {SVGMIP } An object that handles creating and updating SVG textures.
82
+ */
83
+ createMIP ( scale , resetCallback ) {
84
+ const textureCallback = textureData => {
85
+ if ( resetCallback ) resetCallback ( ) ;
86
+ // Check if we have the largest MIP
87
+ // eslint-disable-next-line no-use-before-define
88
+ if ( ! this . _scaledMIPs . length || this . _scaledMIPs [ this . _scaledMIPs . length - 1 ] . _scale <= scale ) {
89
+ // Currently silhouette only gets scaled up
90
+ this . _silhouette . update ( textureData ) ;
91
+ }
92
+ } ;
93
+ const mip = new SVGMIP ( this . _renderer , this . _svgRenderer , scale , textureCallback ) ;
94
+
95
+ return mip ;
96
+ }
97
+
60
98
/**
61
99
* @param {Array<number> } scale - The scaling factors to be used, each in the [0,100] range.
62
100
* @return {WebGLTexture } The GL texture representation of this skin when drawing at the given scale.
63
101
*/
64
- // eslint-disable-next-line no-unused-vars
65
102
getTexture ( scale ) {
66
103
if ( ! this . _svgRenderer . canvas . width || ! this . _svgRenderer . canvas . height ) {
67
104
return super . getTexture ( ) ;
@@ -70,73 +107,70 @@ class SVGSkin extends Skin {
70
107
// The texture only ever gets uniform scale. Take the larger of the two axes.
71
108
const scaleMax = scale ? Math . max ( Math . abs ( scale [ 0 ] ) , Math . abs ( scale [ 1 ] ) ) : 100 ;
72
109
const requestedScale = Math . min ( scaleMax / 100 , this . _maxTextureScale ) ;
73
- let newScale = this . _textureScale ;
74
- while ( ( newScale < this . _maxTextureScale ) && ( requestedScale >= 1.5 * newScale ) ) {
75
- 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
+ }
76
123
}
77
- if ( this . _textureScale !== newScale ) {
78
- this . _textureScale = newScale ;
79
- this . _svgRenderer . _draw ( this . _textureScale , ( ) => {
80
- if ( this . _textureScale === newScale ) {
81
- const canvas = this . _svgRenderer . canvas ;
82
- const context = canvas . getContext ( '2d' ) ;
83
- const textureData = context . getImageData ( 0 , 0 , canvas . width , canvas . height ) ;
84
-
85
- this . _setTexture ( textureData ) ;
86
- }
87
- } ) ;
124
+
125
+ if ( ! this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] ) {
126
+ this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] = this . createMIP ( newScale ) ;
88
127
}
89
128
90
- return this . _texture ;
129
+ return this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] . getTexture ( ) ;
91
130
}
92
131
93
132
/**
94
- * Set the contents of this skin to a snapshot of the provided SVG data.
95
- * @param {string } svgData - new SVG to use.
133
+ * Do a hard reset of the existing MIPs by calling dispose(), setting a new
134
+ * scale 1 MIP in this._scaledMIPs, and finally updating the rotationCenter.
135
+ * @param {SVGMIPs } mip - An object that handles creating and updating SVG textures.
96
136
* @param {Array<number> } [rotationCenter] - Optional rotation center for the SVG. If not supplied, it will be
97
137
* calculated from the bounding box
98
- * @fires Skin.event:WasAltered
138
+ * @fires Skin.event:WasAltered
99
139
*/
100
- setSVG ( svgData , rotationCenter ) {
101
- this . _svgRenderer . fromString ( svgData , 1 , ( ) => {
102
- const gl = this . _renderer . gl ;
103
- this . _textureScale = this . _maxTextureScale = 1 ;
104
-
105
- // Pull out the ImageData from the canvas. ImageData speeds up
106
- // updating Silhouette and is better handled by more browsers in
107
- // regards to memory.
108
- const canvas = this . _svgRenderer . canvas ;
109
-
110
- if ( ! canvas . width || ! canvas . height ) {
111
- super . setEmptyImageData ( ) ;
112
- return ;
113
- }
140
+ resetMIPs ( mip , rotationCenter ) {
141
+ this . _scaledMIPs . forEach ( oldMIP => oldMIP . dispose ( ) ) ;
142
+ this . _scaledMIPs . length = 0 ;
114
143
115
- const context = canvas . getContext ( '2d' ) ;
116
- const textureData = context . getImageData ( 0 , 0 , canvas . width , canvas . height ) ;
144
+ // Set new scale 1 MIP after outdated MIPs have been disposed
145
+ this . _texture = this . _scaledMIPs [ INDEX_OFFSET ] = mip ;
117
146
118
- if ( this . _texture === null ) {
119
- // TODO: mipmaps?
120
- const textureOptions = {
121
- auto : false ,
122
- wrap : gl . CLAMP_TO_EDGE
123
- } ;
147
+ if ( typeof rotationCenter === 'undefined' ) rotationCenter = this . calculateRotationCenter ( ) ;
148
+ this . setRotationCenter . apply ( this , rotationCenter ) ;
149
+ this . emit ( Skin . Events . WasAltered ) ;
150
+ }
124
151
125
- this . _texture = twgl . createTexture ( gl , textureOptions ) ;
126
- }
152
+ /**
153
+ * Set the contents of this skin to a snapshot of the provided SVG data.
154
+ * @param {string } svgData - new SVG to use.
155
+ * @param {Array<number> } [rotationCenter] - Optional rotation center for the SVG.
156
+ */
157
+ setSVG ( svgData , rotationCenter ) {
158
+ this . _svgRenderer . loadString ( svgData ) ;
127
159
128
- this . _setTexture ( textureData ) ;
160
+ if ( ! this . _svgRenderer . canvas . width || ! this . _svgRenderer . canvas . height ) {
161
+ super . setEmptyImageData ( ) ;
162
+ return ;
163
+ }
129
164
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 ;
134
- }
165
+ const maxDimension = Math . ceil ( Math . max ( this . size [ 0 ] , this . size [ 1 ] ) ) ;
166
+ let testScale = 2 ;
167
+ for ( testScale ; maxDimension * testScale <= MAX_TEXTURE_DIMENSION ; testScale *= 2 ) {
168
+ this . _maxTextureScale = testScale ;
169
+ }
135
170
136
- if ( typeof rotationCenter === 'undefined' ) rotationCenter = this . calculateRotationCenter ( ) ;
137
- this . setRotationCenter . apply ( this , rotationCenter ) ;
138
- this . emit ( Skin . Events . WasAltered ) ;
139
- } ) ;
171
+ // Create the 1.0 scale MIP at INDEX_OFFSET.
172
+ const textureScale = 1 ;
173
+ const mip = this . createMIP ( textureScale , ( ) => this . resetMIPs ( mip , rotationCenter ) ) ;
140
174
}
141
175
142
176
}
0 commit comments