UNPKG

7.07 kBJavaScriptView Raw
1const EventEmitter = require('events');
2
3const twgl = require('twgl.js');
4
5const RenderConstants = require('./RenderConstants');
6const Silhouette = require('./Silhouette');
7
8class Skin extends EventEmitter {
9 /**
10 * Create a Skin, which stores and/or generates textures for use in rendering.
11 * @param {int} id - The unique ID for this Skin.
12 * @constructor
13 */
14 constructor (id) {
15 super();
16
17 /** @type {int} */
18 this._id = id;
19
20 /** @type {Vec3} */
21 this._rotationCenter = twgl.v3.create(0, 0);
22
23 /** @type {WebGLTexture} */
24 this._texture = null;
25
26 /**
27 * The uniforms to be used by the vertex and pixel shaders.
28 * Some of these are used by other parts of the renderer as well.
29 * @type {Object.<string,*>}
30 * @private
31 */
32 this._uniforms = {
33 /**
34 * The nominal (not necessarily current) size of the current skin.
35 * @type {Array<number>}
36 */
37 u_skinSize: [0, 0],
38
39 /**
40 * The actual WebGL texture object for the skin.
41 * @type {WebGLTexture}
42 */
43 u_skin: null
44 };
45
46 /**
47 * A silhouette to store touching data, skins are responsible for keeping it up to date.
48 * @private
49 */
50 this._silhouette = new Silhouette();
51
52 this.setMaxListeners(RenderConstants.SKIN_SHARE_SOFT_LIMIT);
53 }
54
55 /**
56 * Dispose of this object. Do not use it after calling this method.
57 */
58 dispose () {
59 this._id = RenderConstants.ID_NONE;
60 }
61
62 /**
63 * @returns {boolean} true for a raster-style skin (like a BitmapSkin), false for vector-style (like SVGSkin).
64 */
65 get isRaster () {
66 return false;
67 }
68
69 /**
70 * @return {int} the unique ID for this Skin.
71 */
72 get id () {
73 return this._id;
74 }
75
76 /**
77 * @returns {Vec3} the origin, in object space, about which this Skin should rotate.
78 */
79 get rotationCenter () {
80 return this._rotationCenter;
81 }
82
83 /**
84 * @abstract
85 * @return {Array<number>} the "native" size, in texels, of this skin.
86 */
87 get size () {
88 return [0, 0];
89 }
90
91 /**
92 * Get the center of the current bounding box
93 * @return {Array<number>} the center of the current bounding box
94 */
95 calculateRotationCenter () {
96 return [this.size[0] / 2, this.size[1] / 2];
97 }
98
99 /**
100 * @abstract
101 * @param {Array<number>} scale - The scaling factors to be used.
102 * @return {WebGLTexture} The GL texture representation of this skin when drawing at the given size.
103 */
104 // eslint-disable-next-line no-unused-vars
105 getTexture (scale) {
106 return this._emptyImageTexture;
107 }
108
109 /**
110 * Get the bounds of the drawable for determining its fenced position.
111 * @param {Array<number>} drawable - The Drawable instance this skin is using.
112 * @param {?Rectangle} result - Optional destination for bounds calculation.
113 * @return {!Rectangle} The drawable's bounds. For compatibility with Scratch 2, we always use getAABB.
114 */
115 getFenceBounds (drawable, result) {
116 return drawable.getAABB(result);
117 }
118
119 /**
120 * Update and returns the uniforms for this skin.
121 * @param {Array<number>} scale - The scaling factors to be used.
122 * @returns {object.<string, *>} the shader uniforms to be used when rendering with this Skin.
123 */
124 getUniforms (scale) {
125 this._uniforms.u_skin = this.getTexture(scale);
126 this._uniforms.u_skinSize = this.size;
127 return this._uniforms;
128 }
129
130 /**
131 * If the skin defers silhouette operations until the last possible minute,
132 * this will be called before isTouching uses the silhouette.
133 * @abstract
134 */
135 updateSilhouette () {}
136
137 /**
138 * Set this skin's texture to the given image.
139 * @param {ImageData|HTMLCanvasElement} textureData - The canvas or image data to set the texture to.
140 */
141 _setTexture (textureData) {
142 const gl = this._renderer.gl;
143
144 gl.bindTexture(gl.TEXTURE_2D, this._texture);
145 // Premultiplied alpha is necessary for proper blending.
146 // See http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
147 gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
148 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureData);
149 gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
150
151 this._silhouette.update(textureData);
152 }
153
154 /**
155 * Set the contents of this skin to an empty skin.
156 * @fires Skin.event:WasAltered
157 */
158 setEmptyImageData () {
159 // Free up the current reference to the _texture
160 this._texture = null;
161
162 if (!this._emptyImageData) {
163 // Create a transparent pixel
164 this._emptyImageData = new ImageData(1, 1);
165
166 // Create a new texture and update the silhouette
167 const gl = this._renderer.gl;
168
169 const textureOptions = {
170 auto: true,
171 wrap: gl.CLAMP_TO_EDGE,
172 src: this._emptyImageData
173 };
174
175 // Note: we're using _emptyImageTexture here instead of _texture
176 // so that we can cache this empty texture for later use as needed.
177 // this._texture can get modified by other skins (e.g. BitmapSkin
178 // and SVGSkin, so we can't use that same field for caching)
179 this._emptyImageTexture = twgl.createTexture(gl, textureOptions);
180 }
181
182 this._rotationCenter[0] = 0;
183 this._rotationCenter[1] = 0;
184
185 this._silhouette.update(this._emptyImageData);
186 this.emit(Skin.Events.WasAltered);
187 }
188
189 /**
190 * Does this point touch an opaque or translucent point on this skin?
191 * Nearest Neighbor version
192 * The caller is responsible for ensuring this skin's silhouette is up-to-date.
193 * @see updateSilhouette
194 * @see Drawable.updateCPURenderAttributes
195 * @param {twgl.v3} vec A texture coordinate.
196 * @return {boolean} Did it touch?
197 */
198 isTouchingNearest (vec) {
199 return this._silhouette.isTouchingNearest(vec);
200 }
201
202 /**
203 * Does this point touch an opaque or translucent point on this skin?
204 * Linear Interpolation version
205 * The caller is responsible for ensuring this skin's silhouette is up-to-date.
206 * @see updateSilhouette
207 * @see Drawable.updateCPURenderAttributes
208 * @param {twgl.v3} vec A texture coordinate.
209 * @return {boolean} Did it touch?
210 */
211 isTouchingLinear (vec) {
212 return this._silhouette.isTouchingLinear(vec);
213 }
214
215}
216
217/**
218 * These are the events which can be emitted by instances of this class.
219 * @enum {string}
220 */
221Skin.Events = {
222 /**
223 * Emitted when anything about the Skin has been altered, such as the appearance or rotation center.
224 * @event Skin.event:WasAltered
225 */
226 WasAltered: 'WasAltered'
227};
228
229module.exports = Skin;