@scratch/scratch-vm 11.0.0-UEPR-176
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +12 -0
- package/README.md +120 -0
- package/dist/node/extension-worker.js +2 -0
- package/dist/node/extension-worker.js.map +1 -0
- package/dist/node/scratch-vm.js +3 -0
- package/dist/node/scratch-vm.js.LICENSE.txt +205 -0
- package/dist/node/scratch-vm.js.map +1 -0
- package/dist/web/extension-worker.js +2 -0
- package/dist/web/extension-worker.js.map +1 -0
- package/dist/web/scratch-vm.js +3 -0
- package/dist/web/scratch-vm.js.LICENSE.txt +42 -0
- package/dist/web/scratch-vm.js.map +1 -0
- package/package.json +108 -0
- package/src/.eslintrc.js +7 -0
- package/src/blocks/scratch3_control.js +206 -0
- package/src/blocks/scratch3_core_example.js +69 -0
- package/src/blocks/scratch3_data.js +257 -0
- package/src/blocks/scratch3_event.js +137 -0
- package/src/blocks/scratch3_looks.js +612 -0
- package/src/blocks/scratch3_motion.js +288 -0
- package/src/blocks/scratch3_operators.js +154 -0
- package/src/blocks/scratch3_procedures.js +79 -0
- package/src/blocks/scratch3_sensing.js +336 -0
- package/src/blocks/scratch3_sound.js +350 -0
- package/src/dispatch/central-dispatch.js +143 -0
- package/src/dispatch/shared-dispatch.js +235 -0
- package/src/dispatch/worker-dispatch.js +110 -0
- package/src/engine/adapter.js +176 -0
- package/src/engine/block-utility.js +242 -0
- package/src/engine/blocks-execute-cache.js +19 -0
- package/src/engine/blocks-runtime-cache.js +78 -0
- package/src/engine/blocks.js +1299 -0
- package/src/engine/comment.js +56 -0
- package/src/engine/execute.js +580 -0
- package/src/engine/monitor-record.js +23 -0
- package/src/engine/mutation-adapter.js +48 -0
- package/src/engine/profiler.js +390 -0
- package/src/engine/runtime.js +2686 -0
- package/src/engine/scratch-blocks-constants.js +27 -0
- package/src/engine/sequencer.js +361 -0
- package/src/engine/stage-layering.js +29 -0
- package/src/engine/target.js +799 -0
- package/src/engine/thread.js +404 -0
- package/src/engine/variable.js +70 -0
- package/src/extension-support/argument-type.js +47 -0
- package/src/extension-support/block-type.js +50 -0
- package/src/extension-support/define-messages.js +18 -0
- package/src/extension-support/extension-manager.js +440 -0
- package/src/extension-support/extension-metadata.js +64 -0
- package/src/extension-support/extension-worker.js +59 -0
- package/src/extension-support/reporter-scope.js +18 -0
- package/src/extension-support/target-type.js +17 -0
- package/src/extensions/scratch3_boost/index.js +2113 -0
- package/src/extensions/scratch3_ev3/index.js +1355 -0
- package/src/extensions/scratch3_gdx_for/index.js +981 -0
- package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
- package/src/extensions/scratch3_makeymakey/index.js +396 -0
- package/src/extensions/scratch3_microbit/index.js +984 -0
- package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/index.js +1333 -0
- package/src/extensions/scratch3_music/manifest.js +63 -0
- package/src/extensions/scratch3_pen/index.js +770 -0
- package/src/extensions/scratch3_speech2text/index.js +700 -0
- package/src/extensions/scratch3_text2speech/index.js +766 -0
- package/src/extensions/scratch3_translate/index.js +286 -0
- package/src/extensions/scratch3_video_sensing/debug.js +13 -0
- package/src/extensions/scratch3_video_sensing/index.js +594 -0
- package/src/extensions/scratch3_video_sensing/library.js +384 -0
- package/src/extensions/scratch3_video_sensing/math.js +76 -0
- package/src/extensions/scratch3_video_sensing/view.js +509 -0
- package/src/extensions/scratch3_wedo2/index.js +1616 -0
- package/src/import/load-costume.js +416 -0
- package/src/import/load-sound.js +116 -0
- package/src/index.js +10 -0
- package/src/io/ble.js +256 -0
- package/src/io/bt.js +202 -0
- package/src/io/clock.js +39 -0
- package/src/io/cloud.js +169 -0
- package/src/io/keyboard.js +146 -0
- package/src/io/mouse.js +145 -0
- package/src/io/mouseWheel.js +28 -0
- package/src/io/userData.js +24 -0
- package/src/io/video.js +210 -0
- package/src/playground/benchmark.css +83 -0
- package/src/playground/benchmark.js +753 -0
- package/src/playground/index.html +111 -0
- package/src/playground/suite.css +78 -0
- package/src/playground/suite.html +25 -0
- package/src/playground/suite.js +544 -0
- package/src/playground/video-sensing.html +18 -0
- package/src/playground/video-sensing.js +133 -0
- package/src/serialization/deserialize-assets.js +178 -0
- package/src/serialization/sb2.js +1295 -0
- package/src/serialization/sb2_specmap.js +1818 -0
- package/src/serialization/sb3.js +1320 -0
- package/src/serialization/serialize-assets.js +60 -0
- package/src/sprites/rendered-target.js +1115 -0
- package/src/sprites/sprite.js +177 -0
- package/src/util/base64-util.js +48 -0
- package/src/util/cast.js +218 -0
- package/src/util/clone.js +17 -0
- package/src/util/color.js +204 -0
- package/src/util/fetch-with-timeout.js +53 -0
- package/src/util/get-monitor-id.js +33 -0
- package/src/util/jsonrpc.js +114 -0
- package/src/util/log.js +4 -0
- package/src/util/math-util.js +122 -0
- package/src/util/maybe-format-message.js +18 -0
- package/src/util/new-block-ids.js +33 -0
- package/src/util/rateLimiter.js +73 -0
- package/src/util/scratch-link-websocket.js +133 -0
- package/src/util/string-util.js +94 -0
- package/src/util/task-queue.js +203 -0
- package/src/util/timer.js +103 -0
- package/src/util/uid.js +29 -0
- package/src/util/variable-util.js +48 -0
- package/src/util/xml-escape.js +33 -0
- package/src/virtual-machine.js +1586 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file library.js
|
|
3
|
+
*
|
|
4
|
+
* Tony Hwang and John Maloney, January 2011
|
|
5
|
+
* Michael "Z" Goddard, March 2018
|
|
6
|
+
*
|
|
7
|
+
* Video motion sensing primitives.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const {motionVector, scratchAtan2} = require('./math');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The width of the intended resolution to analyze for motion.
|
|
14
|
+
* @type {number}
|
|
15
|
+
*/
|
|
16
|
+
const WIDTH = 480;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The height of the intended resolution to analyze for motion.
|
|
20
|
+
* @type {number}
|
|
21
|
+
*/
|
|
22
|
+
const HEIGHT = 360;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A constant value to scale the magnitude of the x and y components called u
|
|
26
|
+
* and v. This creates the motionAmount value.
|
|
27
|
+
*
|
|
28
|
+
* Old note: chosen empirically to give a range of roughly 0-100
|
|
29
|
+
*
|
|
30
|
+
* @type {number}
|
|
31
|
+
*/
|
|
32
|
+
const AMOUNT_SCALE = 100;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A constant value to scale the magnitude of the x and y components called u
|
|
36
|
+
* and v in the local motion derivative. This creates the motionAmount value on
|
|
37
|
+
* a target's motion state.
|
|
38
|
+
*
|
|
39
|
+
* Old note: note 2e-4 * activePixelNum is an experimentally tuned threshold
|
|
40
|
+
* for my logitech Pro 9000 webcam - TTH
|
|
41
|
+
*
|
|
42
|
+
* @type {number}
|
|
43
|
+
*/
|
|
44
|
+
const LOCAL_AMOUNT_SCALE = AMOUNT_SCALE * 2e-4;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The motion amount must be higher than the THRESHOLD to calculate a new
|
|
48
|
+
* direction value.
|
|
49
|
+
* @type {number}
|
|
50
|
+
*/
|
|
51
|
+
const THRESHOLD = 10;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The size of the radius of the window of summarized values when considering
|
|
55
|
+
* the motion inside the full resolution of the sample.
|
|
56
|
+
* @type {number}
|
|
57
|
+
*/
|
|
58
|
+
const WINSIZE = 8;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A ceiling for the motionAmount stored to a local target's motion state. The
|
|
62
|
+
* motionAmount is not allowed to be larger than LOCAL_MAX_AMOUNT.
|
|
63
|
+
* @type {number}
|
|
64
|
+
*/
|
|
65
|
+
const LOCAL_MAX_AMOUNT = 100;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The motion amount for a target's local motion must be higher than the
|
|
69
|
+
* LOCAL_THRESHOLD to calculate a new direction value.
|
|
70
|
+
* @type {number}
|
|
71
|
+
*/
|
|
72
|
+
const LOCAL_THRESHOLD = THRESHOLD / 3;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Store the necessary image pixel data to compares frames of a video and
|
|
76
|
+
* detect an amount and direction of motion in the full sample or in a
|
|
77
|
+
* specified area.
|
|
78
|
+
* @constructor
|
|
79
|
+
*/
|
|
80
|
+
class VideoMotion {
|
|
81
|
+
constructor () {
|
|
82
|
+
/**
|
|
83
|
+
* The number of frames that have been added from a source.
|
|
84
|
+
* @type {number}
|
|
85
|
+
*/
|
|
86
|
+
this.frameNumber = 0;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The frameNumber last analyzed.
|
|
90
|
+
* @type {number}
|
|
91
|
+
*/
|
|
92
|
+
this.lastAnalyzedFrame = 0;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The amount of motion detected in the current frame.
|
|
96
|
+
* @type {number}
|
|
97
|
+
*/
|
|
98
|
+
this.motionAmount = 0;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The direction the motion detected in the frame is general moving in.
|
|
102
|
+
* @type {number}
|
|
103
|
+
*/
|
|
104
|
+
this.motionDirection = 0;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* A copy of the current frame's pixel values. A index of the array is
|
|
108
|
+
* represented in RGBA. The lowest byte is red. The next is green. The
|
|
109
|
+
* next is blue. And the last is the alpha value of that pixel.
|
|
110
|
+
* @type {Uint32Array}
|
|
111
|
+
*/
|
|
112
|
+
this.curr = null;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A copy of the last frame's pixel values.
|
|
116
|
+
* @type {Uint32Array}
|
|
117
|
+
*/
|
|
118
|
+
this.prev = null;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A buffer for holding one component of a pixel's full value twice.
|
|
122
|
+
* One for the current value. And one for the last value.
|
|
123
|
+
* @type {number}
|
|
124
|
+
*/
|
|
125
|
+
this._arrays = new ArrayBuffer(WIDTH * HEIGHT * 2 * 1);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A clamped uint8 view of _arrays. One component of each index of the
|
|
129
|
+
* curr member is copied into this array.
|
|
130
|
+
* @type {number}
|
|
131
|
+
*/
|
|
132
|
+
this._curr = new Uint8ClampedArray(this._arrays, WIDTH * HEIGHT * 0 * 1, WIDTH * HEIGHT);
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A clamped uint8 view of _arrays. One component of each index of the
|
|
136
|
+
* prev member is copied into this array.
|
|
137
|
+
* @type {number}
|
|
138
|
+
*/
|
|
139
|
+
this._prev = new Uint8ClampedArray(this._arrays, WIDTH * HEIGHT * 1 * 1, WIDTH * HEIGHT);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Reset internal state so future frame analysis does not consider values
|
|
144
|
+
* from before this method was called.
|
|
145
|
+
*/
|
|
146
|
+
reset () {
|
|
147
|
+
this.frameNumber = 0;
|
|
148
|
+
this.lastAnalyzedFrame = 0;
|
|
149
|
+
this.motionAmount = this.motionDirection = 0;
|
|
150
|
+
this.prev = this.curr = null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Add a frame to be next analyzed. The passed array represent a pixel with
|
|
155
|
+
* each index in the RGBA format.
|
|
156
|
+
* @param {Uint32Array} source - a source frame of pixels to copy
|
|
157
|
+
*/
|
|
158
|
+
addFrame (source) {
|
|
159
|
+
this.frameNumber++;
|
|
160
|
+
|
|
161
|
+
// Swap curr to prev.
|
|
162
|
+
this.prev = this.curr;
|
|
163
|
+
// Create a clone of the array so any modifications made to the source
|
|
164
|
+
// array do not affect the work done in here.
|
|
165
|
+
this.curr = new Uint32Array(source.buffer.slice(0));
|
|
166
|
+
|
|
167
|
+
// Swap _prev and _curr. Copy one of the color components of the new
|
|
168
|
+
// array into _curr overwriting what was the old _prev data.
|
|
169
|
+
const _tmp = this._prev;
|
|
170
|
+
this._prev = this._curr;
|
|
171
|
+
this._curr = _tmp;
|
|
172
|
+
for (let i = 0; i < this.curr.length; i++) {
|
|
173
|
+
this._curr[i] = this.curr[i] & 0xff;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Analyze the current frame against the previous frame determining the
|
|
179
|
+
* amount of motion and direction of the motion.
|
|
180
|
+
*/
|
|
181
|
+
analyzeFrame () {
|
|
182
|
+
if (!this.curr || !this.prev) {
|
|
183
|
+
this.motionAmount = this.motionDirection = -1;
|
|
184
|
+
// Don't have two frames to analyze yet
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Return early if new data has not been received.
|
|
189
|
+
if (this.lastAnalyzedFrame === this.frameNumber) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.lastAnalyzedFrame = this.frameNumber;
|
|
193
|
+
|
|
194
|
+
const {
|
|
195
|
+
_curr: curr,
|
|
196
|
+
_prev: prev
|
|
197
|
+
} = this;
|
|
198
|
+
|
|
199
|
+
const winStep = (WINSIZE * 2) + 1;
|
|
200
|
+
const wmax = WIDTH - WINSIZE - 1;
|
|
201
|
+
const hmax = HEIGHT - WINSIZE - 1;
|
|
202
|
+
|
|
203
|
+
// Accumulate 2d motion vectors from groups of pixels and average it
|
|
204
|
+
// later.
|
|
205
|
+
let uu = 0;
|
|
206
|
+
let vv = 0;
|
|
207
|
+
let n = 0;
|
|
208
|
+
|
|
209
|
+
// Iterate over groups of cells building up the components to determine
|
|
210
|
+
// a motion vector for each cell instead of the whole frame to avoid
|
|
211
|
+
// integer overflows.
|
|
212
|
+
for (let i = WINSIZE + 1; i < hmax; i += winStep) {
|
|
213
|
+
for (let j = WINSIZE + 1; j < wmax; j += winStep) {
|
|
214
|
+
let A2 = 0;
|
|
215
|
+
let A1B2 = 0;
|
|
216
|
+
let B1 = 0;
|
|
217
|
+
let C1 = 0;
|
|
218
|
+
let C2 = 0;
|
|
219
|
+
|
|
220
|
+
// This is a performance critical math region.
|
|
221
|
+
let address = ((i - WINSIZE) * WIDTH) + j - WINSIZE;
|
|
222
|
+
let nextAddress = address + winStep;
|
|
223
|
+
const maxAddress = ((i + WINSIZE) * WIDTH) + j + WINSIZE;
|
|
224
|
+
for (; address <= maxAddress; address += WIDTH - winStep, nextAddress += WIDTH) {
|
|
225
|
+
for (; address <= nextAddress; address += 1) {
|
|
226
|
+
// The difference in color between the last frame and
|
|
227
|
+
// the current frame.
|
|
228
|
+
const gradT = ((prev[address]) - (curr[address]));
|
|
229
|
+
// The difference between the pixel to the left and the
|
|
230
|
+
// pixel to the right.
|
|
231
|
+
const gradX = ((curr[address - 1]) - (curr[address + 1]));
|
|
232
|
+
// The difference between the pixel above and the pixel
|
|
233
|
+
// below.
|
|
234
|
+
const gradY = ((curr[address - WIDTH]) - (curr[address + WIDTH]));
|
|
235
|
+
|
|
236
|
+
// Add the combined values of this pixel to previously
|
|
237
|
+
// considered pixels.
|
|
238
|
+
A2 += gradX * gradX;
|
|
239
|
+
A1B2 += gradX * gradY;
|
|
240
|
+
B1 += gradY * gradY;
|
|
241
|
+
C2 += gradX * gradT;
|
|
242
|
+
C1 += gradY * gradT;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Use the accumalated values from the for loop to determine a
|
|
247
|
+
// motion direction.
|
|
248
|
+
const {u, v} = motionVector(A2, A1B2, B1, C2, C1);
|
|
249
|
+
|
|
250
|
+
// If u and v are within negative winStep to positive winStep,
|
|
251
|
+
// add them to a sum that will later be averaged.
|
|
252
|
+
if (-winStep < u && u < winStep && -winStep < v && v < winStep) {
|
|
253
|
+
uu += u;
|
|
254
|
+
vv += v;
|
|
255
|
+
n++;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Average the summed vector values of all of the motion groups.
|
|
261
|
+
uu /= n;
|
|
262
|
+
vv /= n;
|
|
263
|
+
|
|
264
|
+
// Scale the magnitude of the averaged UV vector.
|
|
265
|
+
this.motionAmount = Math.round(AMOUNT_SCALE * Math.hypot(uu, vv));
|
|
266
|
+
if (this.motionAmount > THRESHOLD) {
|
|
267
|
+
// Scratch direction
|
|
268
|
+
this.motionDirection = scratchAtan2(vv, uu);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Build motion amount and direction values based on stored current and
|
|
274
|
+
* previous frame that overlaps a given drawable.
|
|
275
|
+
* @param {Drawable} drawable - touchable and bounded drawable to build motion for
|
|
276
|
+
* @param {MotionState} state - state to store built values to
|
|
277
|
+
*/
|
|
278
|
+
getLocalMotion (drawable, state) {
|
|
279
|
+
if (!this.curr || !this.prev) {
|
|
280
|
+
state.motionAmount = state.motionDirection = -1;
|
|
281
|
+
// Don't have two frames to analyze yet
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Skip if the current frame has already been considered for this state.
|
|
286
|
+
if (state.motionFrameNumber !== this.frameNumber) {
|
|
287
|
+
const {
|
|
288
|
+
_prev: prev,
|
|
289
|
+
_curr: curr
|
|
290
|
+
} = this;
|
|
291
|
+
|
|
292
|
+
// The public APIs for Renderer#isTouching manage keeping the matrix and
|
|
293
|
+
// silhouette up-to-date, which is needed for drawable#isTouching to work (used below)
|
|
294
|
+
drawable.updateCPURenderAttributes();
|
|
295
|
+
|
|
296
|
+
// Restrict the region the amount and direction are built from to
|
|
297
|
+
// the area of the current frame overlapped by the given drawable's
|
|
298
|
+
// bounding box.
|
|
299
|
+
const boundingRect = drawable.getFastBounds();
|
|
300
|
+
// Transform the bounding box from scratch space to a space from 0,
|
|
301
|
+
// 0 to WIDTH, HEIGHT.
|
|
302
|
+
const xmin = Math.max(Math.floor(boundingRect.left + (WIDTH / 2)), 1);
|
|
303
|
+
const xmax = Math.min(Math.floor(boundingRect.right + (WIDTH / 2)), WIDTH - 1);
|
|
304
|
+
const ymin = Math.max(Math.floor((HEIGHT / 2) - boundingRect.top), 1);
|
|
305
|
+
const ymax = Math.min(Math.floor((HEIGHT / 2) - boundingRect.bottom), HEIGHT - 1);
|
|
306
|
+
|
|
307
|
+
let A2 = 0;
|
|
308
|
+
let A1B2 = 0;
|
|
309
|
+
let B1 = 0;
|
|
310
|
+
let C1 = 0;
|
|
311
|
+
let C2 = 0;
|
|
312
|
+
let scaleFactor = 0;
|
|
313
|
+
|
|
314
|
+
const position = [0, 0, 0];
|
|
315
|
+
|
|
316
|
+
// This is a performance critical math region.
|
|
317
|
+
for (let i = ymin; i < ymax; i++) {
|
|
318
|
+
for (let j = xmin; j < xmax; j++) {
|
|
319
|
+
// i and j are in a coordinate planning ranging from 0 to
|
|
320
|
+
// HEIGHT and 0 to WIDTH. Transform that into Scratch's
|
|
321
|
+
// range of HEIGHT / 2 to -HEIGHT / 2 and -WIDTH / 2 to
|
|
322
|
+
// WIDTH / 2;
|
|
323
|
+
position[0] = j - (WIDTH / 2);
|
|
324
|
+
position[1] = (HEIGHT / 2) - i;
|
|
325
|
+
// Consider only pixels in the drawable that can touch the
|
|
326
|
+
// edge or other drawables. Empty space in the current skin
|
|
327
|
+
// is skipped.
|
|
328
|
+
if (drawable.isTouching(position)) {
|
|
329
|
+
const address = (i * WIDTH) + j;
|
|
330
|
+
// The difference in color between the last frame and
|
|
331
|
+
// the current frame.
|
|
332
|
+
const gradT = ((prev[address]) - (curr[address]));
|
|
333
|
+
// The difference between the pixel to the left and the
|
|
334
|
+
// pixel to the right.
|
|
335
|
+
const gradX = ((curr[address - 1]) - (curr[address + 1]));
|
|
336
|
+
// The difference between the pixel above and the pixel
|
|
337
|
+
// below.
|
|
338
|
+
const gradY = ((curr[address - WIDTH]) - (curr[address + WIDTH]));
|
|
339
|
+
|
|
340
|
+
// Add the combined values of this pixel to previously
|
|
341
|
+
// considered pixels.
|
|
342
|
+
A2 += gradX * gradX;
|
|
343
|
+
A1B2 += gradX * gradY;
|
|
344
|
+
B1 += gradY * gradY;
|
|
345
|
+
C2 += gradX * gradT;
|
|
346
|
+
C1 += gradY * gradT;
|
|
347
|
+
scaleFactor++;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Use the accumalated values from the for loop to determine a
|
|
353
|
+
// motion direction.
|
|
354
|
+
let {u, v} = motionVector(A2, A1B2, B1, C2, C1);
|
|
355
|
+
|
|
356
|
+
let activePixelNum = 0;
|
|
357
|
+
if (scaleFactor) {
|
|
358
|
+
// Store the area of the sprite in pixels
|
|
359
|
+
activePixelNum = scaleFactor;
|
|
360
|
+
|
|
361
|
+
scaleFactor /= (2 * WINSIZE * 2 * WINSIZE);
|
|
362
|
+
u = u / scaleFactor;
|
|
363
|
+
v = v / scaleFactor;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Scale the magnitude of the averaged UV vector and the number of
|
|
367
|
+
// overlapping drawable pixels.
|
|
368
|
+
state.motionAmount = Math.round(LOCAL_AMOUNT_SCALE * activePixelNum * Math.hypot(u, v));
|
|
369
|
+
if (state.motionAmount > LOCAL_MAX_AMOUNT) {
|
|
370
|
+
// Clip all magnitudes greater than 100.
|
|
371
|
+
state.motionAmount = LOCAL_MAX_AMOUNT;
|
|
372
|
+
}
|
|
373
|
+
if (state.motionAmount > LOCAL_THRESHOLD) {
|
|
374
|
+
// Scratch direction.
|
|
375
|
+
state.motionDirection = scratchAtan2(v, u);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Skip future calls on this state until a new frame is added.
|
|
379
|
+
state.motionFrameNumber = this.frameNumber;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = VideoMotion;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A constant value helping to transform a value in radians to degrees.
|
|
3
|
+
* @type {number}
|
|
4
|
+
*/
|
|
5
|
+
const TO_DEGREE = 180 / Math.PI;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A object reused to save on memory allocation returning u and v vector from
|
|
9
|
+
* motionVector.
|
|
10
|
+
* @type {UV}
|
|
11
|
+
*/
|
|
12
|
+
const _motionVectorOut = {u: 0, v: 0};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Determine a motion vector combinations of the color component difference on
|
|
16
|
+
* the x axis, y axis, and temporal axis.
|
|
17
|
+
* @param {number} A2 - a sum of x axis squared
|
|
18
|
+
* @param {number} A1B2 - a sum of x axis times y axis
|
|
19
|
+
* @param {number} B1 - a sum of y axis squared
|
|
20
|
+
* @param {number} C2 - a sum of x axis times temporal axis
|
|
21
|
+
* @param {number} C1 - a sum of y axis times temporal axis
|
|
22
|
+
* @param {UV} out - optional object to store return UV info in
|
|
23
|
+
* @returns {UV} a uv vector representing the motion for the given input
|
|
24
|
+
*/
|
|
25
|
+
const motionVector = function (A2, A1B2, B1, C2, C1, out = _motionVectorOut) {
|
|
26
|
+
// Compare sums of X * Y and sums of X squared and Y squared.
|
|
27
|
+
const delta = ((A1B2 * A1B2) - (A2 * B1));
|
|
28
|
+
if (delta) {
|
|
29
|
+
// System is not singular - solving by Kramer method.
|
|
30
|
+
const deltaX = -((C1 * A1B2) - (C2 * B1));
|
|
31
|
+
const deltaY = -((A1B2 * C2) - (A2 * C1));
|
|
32
|
+
const Idelta = 8 / delta;
|
|
33
|
+
out.u = deltaX * Idelta;
|
|
34
|
+
out.v = deltaY * Idelta;
|
|
35
|
+
} else {
|
|
36
|
+
// Singular system - find optical flow in gradient direction.
|
|
37
|
+
const Norm = ((A1B2 + A2) * (A1B2 + A2)) + ((B1 + A1B2) * (B1 + A1B2));
|
|
38
|
+
if (Norm) {
|
|
39
|
+
const IGradNorm = 8 / Norm;
|
|
40
|
+
const temp = -(C1 + C2) * IGradNorm;
|
|
41
|
+
out.u = (A1B2 + A2) * temp;
|
|
42
|
+
out.v = (B1 + A1B2) * temp;
|
|
43
|
+
} else {
|
|
44
|
+
out.u = 0;
|
|
45
|
+
out.v = 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Translate an angle in degrees with the range -180 to 180 rotated to
|
|
53
|
+
* Scratch's reference angle.
|
|
54
|
+
* @param {number} degrees - angle in range -180 to 180
|
|
55
|
+
* @returns {number} angle from Scratch's reference angle
|
|
56
|
+
*/
|
|
57
|
+
const scratchDegrees = function (degrees) {
|
|
58
|
+
return ((degrees + 270) % 360) - 180;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get the angle of the y and x component of a 2d vector in degrees in
|
|
63
|
+
* Scratch's coordinate plane.
|
|
64
|
+
* @param {number} y - the y component of a 2d vector
|
|
65
|
+
* @param {number} x - the x component of a 2d vector
|
|
66
|
+
* @returns {number} angle in degrees in Scratch's coordinate plane
|
|
67
|
+
*/
|
|
68
|
+
const scratchAtan2 = function (y, x) {
|
|
69
|
+
return scratchDegrees(Math.atan2(y, x) * TO_DEGREE);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
motionVector,
|
|
74
|
+
scratchDegrees,
|
|
75
|
+
scratchAtan2
|
|
76
|
+
};
|