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+ /**
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 ;
714
815class SVGSkin extends Skin {
916 /**
@@ -22,20 +29,28 @@ class SVGSkin extends Skin {
2229 /** @type {SvgRenderer } */
2330 this . _svgRenderer = new SvgRenderer ( ) ;
2431
25- /** @type {number } */
26- this . _textureScale = 1 ;
32+ /** @type {Array.<SVGMIPs> } */
33+ this . _scaledMIPs = [ ] ;
2734
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 ;
3040 }
3141
3242 /**
3343 * Dispose of this object. Do not use it after calling this method.
3444 */
3545 dispose ( ) {
3646 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+ }
3852 this . _texture = null ;
53+ this . _scaledMIPs . length = 0 ;
3954 }
4055 super . dispose ( ) ;
4156 }
@@ -57,11 +72,33 @@ class SVGSkin extends Skin {
5772 super . setRotationCenter ( x - viewOffset [ 0 ] , y - viewOffset [ 1 ] ) ;
5873 }
5974
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+
6098 /**
6199 * @param {Array<number> } scale - The scaling factors to be used, each in the [0,100] range.
62100 * @return {WebGLTexture } The GL texture representation of this skin when drawing at the given scale.
63101 */
64- // eslint-disable-next-line no-unused-vars
65102 getTexture ( scale ) {
66103 if ( ! this . _svgRenderer . canvas . width || ! this . _svgRenderer . canvas . height ) {
67104 return super . getTexture ( ) ;
@@ -70,73 +107,70 @@ class SVGSkin extends Skin {
70107 // The texture only ever gets uniform scale. Take the larger of the two axes.
71108 const scaleMax = scale ? Math . max ( Math . abs ( scale [ 0 ] ) , Math . abs ( scale [ 1 ] ) ) : 100 ;
72109 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+ }
76123 }
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 ) ;
88127 }
89128
90- return this . _texture ;
129+ return this . _scaledMIPs [ textureIndex + INDEX_OFFSET ] . getTexture ( ) ;
91130 }
92131
93132 /**
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.
96136 * @param {Array<number> } [rotationCenter] - Optional rotation center for the SVG. If not supplied, it will be
97137 * calculated from the bounding box
98- * @fires Skin.event:WasAltered
138+ * @fires Skin.event:WasAltered
99139 */
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 ;
114143
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 ;
117146
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+ }
124151
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 ) ;
127159
128- this . _setTexture ( textureData ) ;
160+ if ( ! this . _svgRenderer . canvas . width || ! this . _svgRenderer . canvas . height ) {
161+ super . setEmptyImageData ( ) ;
162+ return ;
163+ }
129164
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+ }
135170
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 ) ) ;
140174 }
141175
142176}
0 commit comments