@@ -23,6 +23,32 @@ const menuIconURI = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53M
2323// eslint-disable-next-line max-len
2424const blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDIzLjg0IDIxLjQ2Ij48Y2lyY2xlIGN4PSI4LjM1IiBjeT0iOS42NSIgcj0iLjk3IiBmaWxsPSIjZmZmIi8+PGNpcmNsZSBjeD0iMTQuMTkiIGN5PSI5LjY1IiByPSIuOTciIGZpbGw9IiNmZmYiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTEuMjcgNC4xNGMtMy45NCAwLTcuMTMgMy4xOS03LjEzIDcuMTNzMy4xOSA3LjEzIDcuMTMgNy4xMyA3LjEzLTMuMTkgNy4xMy03LjEzLTMuMTktNy4xMy03LjEzLTcuMTNtMCAxLjNjMy4yMiAwIDUuODQgMi42MSA1Ljg0IDUuODRzLTIuNjEgNS44NC01Ljg0IDUuODQtNS44NC0yLjYxLTUuODQtNS44NCAyLjYxLTUuODQgNS44NC01Ljg0Ii8+PHBhdGggZmlsbD0iI2ZmYmYwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwYjhlNjkiIHN0cm9rZS1taXRlcmxpbWl0PSIyIiBzdHJva2Utd2lkdGg9Ii41IiBkPSJNMTcuNTcgMTMuODhjLjU3LS4xNSAxLjAyLS42IDEuMTctMS4xN2wuMzUtMS4zYy4xNi0uNjEgMS4wNC0uNjEgMS4yIDBsLjM1IDEuM2MuMTUuNTcuNiAxLjAyIDEuMTggMS4xN2wxLjMuMzVjLjYxLjE2LjYxIDEuMDQgMCAxLjJsLTEuMy4zNWMtLjU3LjE1LTEuMDIuNi0xLjE4IDEuMTdsLS4zNSAxLjNjLS4xNy42Mi0xLjA0LjYyLTEuMiAwbC0uMzUtMS4zYy0uMTUtLjU3LS42LTEuMDItMS4xNy0xLjE3bC0xLjMtLjM1Yy0uNjEtLjE3LS42MS0xLjA0IDAtMS4ybDEuMy0uMzVabS0xNi0xMS40M2MuNDMtLjEyLjc2LS40NS44OC0uODhsLjI2LS45OGMuMTItLjQ2Ljc4LS40Ni45IDBsLjI2Ljk4Yy4xMi40My40NS43Ni44OC44OGwuOTguMjZjLjQ2LjEyLjQ2Ljc4IDAgLjlsLS45OC4yNmMtLjQzLjExLS43Ny40NS0uODguODhsLS4yNi45OGMtLjEyLjQ2LS43OC40Ni0uOSAwbC0uMjYtLjk4YTEuMjYgMS4yNiAwIDAgMC0uODgtLjg4bC0uOTgtLjI2Yy0uNDYtLjEyLS40Ni0uNzggMC0uOXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTIuNjggMTIuNTNjLjItLjMuNi0uMzguOS0uMThzLjM4LjYuMTguOWMtLjU2LjgzLTEuNDEgMS4yNi0yLjQ4IDEuMjZzLTEuOTMtLjQzLTIuNDgtMS4yNmMtLjItLjMtLjEyLS43LjE4LS45cy43LS4xMi45LjE4Yy4zMS40Ni43NS42OCAxLjQxLjY4czEuMS0uMjIgMS40MS0uNjhaIi8+PHBhdGggZmlsbD0iIzBiOGU2OSIgZD0iTTIwLjg5IDYuMDZhLjU3LjU3IDAgMCAxLS41Ny0uNTdWMi4yaC0zLjMxYy0uMzEgMC0uNTctLjI1LS41Ny0uNTdzLjI1LS41Ny41Ny0uNTdoMy44OGMuMzEgMCAuNTcuMjUuNTcuNTd2My44NmMwIC4zMS0uMjUuNTctLjU3LjU3TTUuNDQgMjEuNDZIMS41OWEuNTcuNTcgMCAwIDEtLjU3LS41N3YtMy44MmMwLS4zMS4yNS0uNTcuNTctLjU3cy41Ny4yNS41Ny41N3YzLjI1aDMuMjhjLjMxIDAgLjU3LjI1LjU3LjU3cy0uMjUuNTctLjU3LjU3Ii8+PC9zdmc+' ;
2525
26+ /**
27+ * Face detection keypoints from TensorFlow's face sensing model.
28+ * @readonly
29+ * @enum {string}
30+ */
31+ const PARTS = {
32+ NOSE : '2' ,
33+ MOUTH : '3' ,
34+ LEFT_EYE : '0' ,
35+ RIGHT_EYE : '1' ,
36+ BETWEEN_EYES : '6' ,
37+ LEFT_EAR : '4' ,
38+ RIGHT_EAR : '5' ,
39+ TOP_OF_HEAD : '7'
40+ } ;
41+
42+ /**
43+ * Possible tilt directions.
44+ * @readonly
45+ * @enum {string}
46+ */
47+ const TILT = {
48+ LEFT : 'left' ,
49+ RIGHT : 'right'
50+ } ;
51+
2652/**
2753 * Class for the Face sensing blocks in Scratch 3.0
2854 * @param {Runtime } runtime - the runtime instantiating this block package.
@@ -116,6 +142,22 @@ class Scratch3FaceSensingBlocks {
116142 return 5 ;
117143 }
118144
145+ /**
146+ * Minimum tilt (in degrees) needed before counting as a tilt event.
147+ * @type {number }
148+ */
149+ static get TILT_THRESHOLD ( ) {
150+ return 10 ;
151+ }
152+
153+ /**
154+ * Default face part position when no facial keypoints are detected.
155+ * @type {{x: number, y: number} }
156+ */
157+ static get DEFAULT_PART_POSITION ( ) {
158+ return { x : 0 , y : 0 } ;
159+ }
160+
119161 /**
120162 * An array of info about the face part menu choices.
121163 * @type {object[] }
@@ -128,63 +170,63 @@ class Scratch3FaceSensingBlocks {
128170 description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
129171
130172 } ) ,
131- value : '2'
173+ value : PARTS . NOSE
132174 } , {
133175 text : formatMessage ( {
134176 id : 'faceSensing.mouth' ,
135177 default : 'mouth' ,
136178 description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
137179
138180 } ) ,
139- value : '3'
181+ value : PARTS . MOUTH
140182 } , {
141183 text : formatMessage ( {
142184 id : 'faceSensing.leftEye' ,
143185 default : 'left eye' ,
144186 description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
145187
146188 } ) ,
147- value : '0'
189+ value : PARTS . LEFT_EYE
148190 } , {
149191 text : formatMessage ( {
150192 id : 'faceSensing.rightEye' ,
151193 default : 'right eye' ,
152194 description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
153195
154196 } ) ,
155- value : '1'
197+ value : PARTS . RIGHT_EYE
156198 } , {
157199 text : formatMessage ( {
158200 id : 'faceSensing.betweenEyes' ,
159201 default : 'between eyes' ,
160202 description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
161203
162204 } ) ,
163- value : '6'
205+ value : PARTS . BETWEEN_EYES
164206 } , {
165207 text : formatMessage ( {
166208 id : 'faceSensing.leftEar' ,
167209 default : 'left ear' ,
168210 description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
169211
170212 } ) ,
171- value : '4'
213+ value : PARTS . LEFT_EAR
172214 } , {
173215 text : formatMessage ( {
174216 id : 'faceSensing.rightEar' ,
175217 default : 'right ear' ,
176218 description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
177219
178220 } ) ,
179- value : '5'
221+ value : PARTS . RIGHT_EAR
180222 } , {
181223 text : formatMessage ( {
182224 id : 'faceSensing.topOfHead' ,
183225 default : 'top of head' ,
184226 description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
185227
186228 } ) ,
187- value : '7'
229+ value : PARTS . TOP_OF_HEAD
188230 } ] ;
189231 }
190232
@@ -200,15 +242,15 @@ class Scratch3FaceSensingBlocks {
200242 description : 'Argument for the "when face tilts [DIRECTION]" block'
201243
202244 } ) ,
203- value : 'left'
245+ value : TILT . LEFT
204246 } , {
205247 text : formatMessage ( {
206248 id : 'faceSensing.right' ,
207249 default : 'right' ,
208250 description : 'Argument for the "when face tilts [DIRECTION]" block'
209251
210252 } ) ,
211- value : 'right'
253+ value : TILT . RIGHT
212254 } ] ;
213255 }
214256
@@ -304,7 +346,7 @@ class Scratch3FaceSensingBlocks {
304346 PART : {
305347 type : ArgumentType . STRING ,
306348 menu : 'PART' ,
307- defaultValue : '2'
349+ defaultValue : PARTS . NOSE
308350 }
309351 } ,
310352 filter : [ TargetType . SPRITE ]
@@ -342,7 +384,7 @@ class Scratch3FaceSensingBlocks {
342384 DIRECTION : {
343385 type : ArgumentType . STRING ,
344386 menu : 'TILT' ,
345- defaultValue : 'left'
387+ defaultValue : TILT . LEFT
346388 }
347389 }
348390 } ,
@@ -357,7 +399,7 @@ class Scratch3FaceSensingBlocks {
357399 PART : {
358400 type : ArgumentType . STRING ,
359401 menu : 'PART' ,
360- defaultValue : '2'
402+ defaultValue : PARTS . NOSE
361403 }
362404 } ,
363405 blockType : BlockType . HAT ,
@@ -415,8 +457,8 @@ class Scratch3FaceSensingBlocks {
415457 * @private
416458 */
417459 _getBetweenEyesPosition ( ) {
418- const leftEye = this . _getPartPosition ( 0 ) ;
419- const rightEye = this . _getPartPosition ( 1 ) ;
460+ const leftEye = this . _getPartPosition ( PARTS . LEFT_EYE ) ;
461+ const rightEye = this . _getPartPosition ( PARTS . RIGHT_EYE ) ;
420462 const betweenEyes = { x : 0 , y : 0 } ;
421463 betweenEyes . x = leftEye . x + ( ( rightEye . x - leftEye . x ) / 2 ) ;
422464 betweenEyes . y = leftEye . y + ( ( rightEye . y - leftEye . y ) / 2 ) ;
@@ -433,9 +475,9 @@ class Scratch3FaceSensingBlocks {
433475 * @private
434476 */
435477 _getTopOfHeadPosition ( ) {
436- const leftEyePos = this . _getPartPosition ( 0 ) ;
437- const rightEyePos = this . _getPartPosition ( 1 ) ;
438- const mouthPos = this . _getPartPosition ( 3 ) ;
478+ const leftEyePos = this . _getPartPosition ( PARTS . LEFT_EYE ) ;
479+ const rightEyePos = this . _getPartPosition ( PARTS . RIGHT_EYE ) ;
480+ const mouthPos = this . _getPartPosition ( PARTS . MOUTH ) ;
439481 const dx = rightEyePos . x - leftEyePos . x ;
440482 const dy = rightEyePos . y - leftEyePos . y ;
441483 const directionRads = Math . atan2 ( dy , dx ) + ( Math . PI / 2 ) ;
@@ -453,20 +495,20 @@ class Scratch3FaceSensingBlocks {
453495 * Get the position of a given facial keypoint.
454496 * Returns {0,0} if no face or keypoints are available.
455497 *
456- * @param {number } part - Part of the face to be detected
498+ * @param {string } part - Part of the face to be detected
457499 * @returns {{x: number, y: number} } Coordinates of the detected keypoint.
458500 * @private
459501 */
460502 _getPartPosition ( part ) {
461- const defaultPos = { x : 0 , y : 0 } ;
503+ const defaultPos = Scratch3FaceSensingBlocks . DEFAULT_PART_POSITION ;
462504
463505 if ( ! this . _currentFace ) return defaultPos ;
464506 if ( ! this . _currentFace . keypoints ) return defaultPos ;
465507
466- if ( Number ( part ) === 6 ) {
508+ if ( part === PARTS . BETWEEN_EYES ) {
467509 return this . _getBetweenEyesPosition ( ) ;
468510 }
469- if ( Number ( part ) === 7 ) {
511+ if ( part === PARTS . TOP_OF_HEAD ) {
470512 return this . _getTopOfHeadPosition ( ) ;
471513 }
472514
@@ -547,8 +589,8 @@ class Scratch3FaceSensingBlocks {
547589 faceTilt ( ) {
548590 if ( ! this . _currentFace ) return this . _cachedTilt ;
549591
550- const leftEyePos = this . _getPartPosition ( 0 ) ;
551- const rightEyePos = this . _getPartPosition ( 1 ) ;
592+ const leftEyePos = this . _getPartPosition ( PARTS . LEFT_EYE ) ;
593+ const rightEyePos = this . _getPartPosition ( PARTS . RIGHT_EYE ) ;
552594 const dx = rightEyePos . x - leftEyePos . x ;
553595 const dy = rightEyePos . y - leftEyePos . y ;
554596 const direction = 90 - MathUtil . radToDeg ( Math . atan2 ( dy , dx ) ) ;
@@ -567,13 +609,11 @@ class Scratch3FaceSensingBlocks {
567609 * @returns {boolean } - true if the face is tilted
568610 */
569611 whenTilted ( args ) {
570- const TILT_THRESHOLD = 10 ;
571-
572- if ( args . DIRECTION === 'left' ) {
573- return this . faceTilt ( ) < ( 90 - TILT_THRESHOLD ) ;
612+ if ( args . DIRECTION === TILT . LEFT ) {
613+ return this . faceTilt ( ) < ( 90 - Scratch3FaceSensingBlocks . TILT_THRESHOLD ) ;
574614 }
575- if ( args . DIRECTION === 'right' ) {
576- return this . faceTilt ( ) > ( 90 + TILT_THRESHOLD ) ;
615+ if ( args . DIRECTION === TILT . RIGHT ) {
616+ return this . faceTilt ( ) > ( 90 + Scratch3FaceSensingBlocks . TILT_THRESHOLD ) ;
577617 }
578618 return false ;
579619 }
0 commit comments