@@ -23,6 +23,32 @@ const menuIconURI = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53M
23
23
// eslint-disable-next-line max-len
24
24
const blockIconURI = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDIzLjg0IDIxLjQ2Ij48Y2lyY2xlIGN4PSI4LjM1IiBjeT0iOS42NSIgcj0iLjk3IiBmaWxsPSIjZmZmIi8+PGNpcmNsZSBjeD0iMTQuMTkiIGN5PSI5LjY1IiByPSIuOTciIGZpbGw9IiNmZmYiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTEuMjcgNC4xNGMtMy45NCAwLTcuMTMgMy4xOS03LjEzIDcuMTNzMy4xOSA3LjEzIDcuMTMgNy4xMyA3LjEzLTMuMTkgNy4xMy03LjEzLTMuMTktNy4xMy03LjEzLTcuMTNtMCAxLjNjMy4yMiAwIDUuODQgMi42MSA1Ljg0IDUuODRzLTIuNjEgNS44NC01Ljg0IDUuODQtNS44NC0yLjYxLTUuODQtNS44NCAyLjYxLTUuODQgNS44NC01Ljg0Ii8+PHBhdGggZmlsbD0iI2ZmYmYwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2U9IiMwYjhlNjkiIHN0cm9rZS1taXRlcmxpbWl0PSIyIiBzdHJva2Utd2lkdGg9Ii41IiBkPSJNMTcuNTcgMTMuODhjLjU3LS4xNSAxLjAyLS42IDEuMTctMS4xN2wuMzUtMS4zYy4xNi0uNjEgMS4wNC0uNjEgMS4yIDBsLjM1IDEuM2MuMTUuNTcuNiAxLjAyIDEuMTggMS4xN2wxLjMuMzVjLjYxLjE2LjYxIDEuMDQgMCAxLjJsLTEuMy4zNWMtLjU3LjE1LTEuMDIuNi0xLjE4IDEuMTdsLS4zNSAxLjNjLS4xNy42Mi0xLjA0LjYyLTEuMiAwbC0uMzUtMS4zYy0uMTUtLjU3LS42LTEuMDItMS4xNy0xLjE3bC0xLjMtLjM1Yy0uNjEtLjE3LS42MS0xLjA0IDAtMS4ybDEuMy0uMzVabS0xNi0xMS40M2MuNDMtLjEyLjc2LS40NS44OC0uODhsLjI2LS45OGMuMTItLjQ2Ljc4LS40Ni45IDBsLjI2Ljk4Yy4xMi40My40NS43Ni44OC44OGwuOTguMjZjLjQ2LjEyLjQ2Ljc4IDAgLjlsLS45OC4yNmMtLjQzLjExLS43Ny40NS0uODguODhsLS4yNi45OGMtLjEyLjQ2LS43OC40Ni0uOSAwbC0uMjYtLjk4YTEuMjYgMS4yNiAwIDAgMC0uODgtLjg4bC0uOTgtLjI2Yy0uNDYtLjEyLS40Ni0uNzggMC0uOXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTIuNjggMTIuNTNjLjItLjMuNi0uMzguOS0uMThzLjM4LjYuMTguOWMtLjU2LjgzLTEuNDEgMS4yNi0yLjQ4IDEuMjZzLTEuOTMtLjQzLTIuNDgtMS4yNmMtLjItLjMtLjEyLS43LjE4LS45cy43LS4xMi45LjE4Yy4zMS40Ni43NS42OCAxLjQxLjY4czEuMS0uMjIgMS40MS0uNjhaIi8+PHBhdGggZmlsbD0iIzBiOGU2OSIgZD0iTTIwLjg5IDYuMDZhLjU3LjU3IDAgMCAxLS41Ny0uNTdWMi4yaC0zLjMxYy0uMzEgMC0uNTctLjI1LS41Ny0uNTdzLjI1LS41Ny41Ny0uNTdoMy44OGMuMzEgMCAuNTcuMjUuNTcuNTd2My44NmMwIC4zMS0uMjUuNTctLjU3LjU3TTUuNDQgMjEuNDZIMS41OWEuNTcuNTcgMCAwIDEtLjU3LS41N3YtMy44MmMwLS4zMS4yNS0uNTcuNTctLjU3cy41Ny4yNS41Ny41N3YzLjI1aDMuMjhjLjMxIDAgLjU3LjI1LjU3LjU3cy0uMjUuNTctLjU3LjU3Ii8+PC9zdmc+' ;
25
25
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
+
26
52
/**
27
53
* Class for the Face sensing blocks in Scratch 3.0
28
54
* @param {Runtime } runtime - the runtime instantiating this block package.
@@ -116,6 +142,22 @@ class Scratch3FaceSensingBlocks {
116
142
return 5 ;
117
143
}
118
144
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
+
119
161
/**
120
162
* An array of info about the face part menu choices.
121
163
* @type {object[] }
@@ -128,63 +170,63 @@ class Scratch3FaceSensingBlocks {
128
170
description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
129
171
130
172
} ) ,
131
- value : '2'
173
+ value : PARTS . NOSE
132
174
} , {
133
175
text : formatMessage ( {
134
176
id : 'faceSensing.mouth' ,
135
177
default : 'mouth' ,
136
178
description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
137
179
138
180
} ) ,
139
- value : '3'
181
+ value : PARTS . MOUTH
140
182
} , {
141
183
text : formatMessage ( {
142
184
id : 'faceSensing.leftEye' ,
143
185
default : 'left eye' ,
144
186
description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
145
187
146
188
} ) ,
147
- value : '0'
189
+ value : PARTS . LEFT_EYE
148
190
} , {
149
191
text : formatMessage ( {
150
192
id : 'faceSensing.rightEye' ,
151
193
default : 'right eye' ,
152
194
description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
153
195
154
196
} ) ,
155
- value : '1'
197
+ value : PARTS . RIGHT_EYE
156
198
} , {
157
199
text : formatMessage ( {
158
200
id : 'faceSensing.betweenEyes' ,
159
201
default : 'between eyes' ,
160
202
description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
161
203
162
204
} ) ,
163
- value : '6'
205
+ value : PARTS . BETWEEN_EYES
164
206
} , {
165
207
text : formatMessage ( {
166
208
id : 'faceSensing.leftEar' ,
167
209
default : 'left ear' ,
168
210
description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
169
211
170
212
} ) ,
171
- value : '4'
213
+ value : PARTS . LEFT_EAR
172
214
} , {
173
215
text : formatMessage ( {
174
216
id : 'faceSensing.rightEar' ,
175
217
default : 'right ear' ,
176
218
description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
177
219
178
220
} ) ,
179
- value : '5'
221
+ value : PARTS . RIGHT_EAR
180
222
} , {
181
223
text : formatMessage ( {
182
224
id : 'faceSensing.topOfHead' ,
183
225
default : 'top of head' ,
184
226
description : 'Option for the "go to [PART]" and "when sprite touches [PART] blocks'
185
227
186
228
} ) ,
187
- value : '7'
229
+ value : PARTS . TOP_OF_HEAD
188
230
} ] ;
189
231
}
190
232
@@ -200,15 +242,15 @@ class Scratch3FaceSensingBlocks {
200
242
description : 'Argument for the "when face tilts [DIRECTION]" block'
201
243
202
244
} ) ,
203
- value : 'left'
245
+ value : TILT . LEFT
204
246
} , {
205
247
text : formatMessage ( {
206
248
id : 'faceSensing.right' ,
207
249
default : 'right' ,
208
250
description : 'Argument for the "when face tilts [DIRECTION]" block'
209
251
210
252
} ) ,
211
- value : 'right'
253
+ value : TILT . RIGHT
212
254
} ] ;
213
255
}
214
256
@@ -304,7 +346,7 @@ class Scratch3FaceSensingBlocks {
304
346
PART : {
305
347
type : ArgumentType . STRING ,
306
348
menu : 'PART' ,
307
- defaultValue : '2'
349
+ defaultValue : PARTS . NOSE
308
350
}
309
351
} ,
310
352
filter : [ TargetType . SPRITE ]
@@ -342,7 +384,7 @@ class Scratch3FaceSensingBlocks {
342
384
DIRECTION : {
343
385
type : ArgumentType . STRING ,
344
386
menu : 'TILT' ,
345
- defaultValue : 'left'
387
+ defaultValue : TILT . LEFT
346
388
}
347
389
}
348
390
} ,
@@ -357,7 +399,7 @@ class Scratch3FaceSensingBlocks {
357
399
PART : {
358
400
type : ArgumentType . STRING ,
359
401
menu : 'PART' ,
360
- defaultValue : '2'
402
+ defaultValue : PARTS . NOSE
361
403
}
362
404
} ,
363
405
blockType : BlockType . HAT ,
@@ -415,8 +457,8 @@ class Scratch3FaceSensingBlocks {
415
457
* @private
416
458
*/
417
459
_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 ) ;
420
462
const betweenEyes = { x : 0 , y : 0 } ;
421
463
betweenEyes . x = leftEye . x + ( ( rightEye . x - leftEye . x ) / 2 ) ;
422
464
betweenEyes . y = leftEye . y + ( ( rightEye . y - leftEye . y ) / 2 ) ;
@@ -433,9 +475,9 @@ class Scratch3FaceSensingBlocks {
433
475
* @private
434
476
*/
435
477
_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 ) ;
439
481
const dx = rightEyePos . x - leftEyePos . x ;
440
482
const dy = rightEyePos . y - leftEyePos . y ;
441
483
const directionRads = Math . atan2 ( dy , dx ) + ( Math . PI / 2 ) ;
@@ -453,20 +495,20 @@ class Scratch3FaceSensingBlocks {
453
495
* Get the position of a given facial keypoint.
454
496
* Returns {0,0} if no face or keypoints are available.
455
497
*
456
- * @param {number } part - Part of the face to be detected
498
+ * @param {string } part - Part of the face to be detected
457
499
* @returns {{x: number, y: number} } Coordinates of the detected keypoint.
458
500
* @private
459
501
*/
460
502
_getPartPosition ( part ) {
461
- const defaultPos = { x : 0 , y : 0 } ;
503
+ const defaultPos = Scratch3FaceSensingBlocks . DEFAULT_PART_POSITION ;
462
504
463
505
if ( ! this . _currentFace ) return defaultPos ;
464
506
if ( ! this . _currentFace . keypoints ) return defaultPos ;
465
507
466
- if ( Number ( part ) === 6 ) {
508
+ if ( part === PARTS . BETWEEN_EYES ) {
467
509
return this . _getBetweenEyesPosition ( ) ;
468
510
}
469
- if ( Number ( part ) === 7 ) {
511
+ if ( part === PARTS . TOP_OF_HEAD ) {
470
512
return this . _getTopOfHeadPosition ( ) ;
471
513
}
472
514
@@ -547,8 +589,8 @@ class Scratch3FaceSensingBlocks {
547
589
faceTilt ( ) {
548
590
if ( ! this . _currentFace ) return this . _cachedTilt ;
549
591
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 ) ;
552
594
const dx = rightEyePos . x - leftEyePos . x ;
553
595
const dy = rightEyePos . y - leftEyePos . y ;
554
596
const direction = 90 - MathUtil . radToDeg ( Math . atan2 ( dy , dx ) ) ;
@@ -567,13 +609,11 @@ class Scratch3FaceSensingBlocks {
567
609
* @returns {boolean } - true if the face is tilted
568
610
*/
569
611
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 ) ;
574
614
}
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 ) ;
577
617
}
578
618
return false ;
579
619
}
0 commit comments