littlejsengine 1.18.29 → 1.20.0
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/FAQ.md +841 -815
- package/README.md +211 -199
- package/dist/littlejs.d.ts +4383 -812
- package/dist/littlejs.esm.js +31848 -16680
- package/dist/littlejs.esm.min.js +14 -1
- package/dist/littlejs.js +31230 -16166
- package/dist/littlejs.min.js +13 -1
- package/dist/littlejs.release.js +27419 -15457
- package/package.json +10 -5
- package/plugins/audioEffects.js +375 -0
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +740 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -330
- package/plugins/math3d.js +911 -0
- package/plugins/medalSystem.js +118 -72
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +196 -115
- package/plugins/postProcess.js +284 -166
- package/plugins/render3d.js +4072 -0
- package/plugins/render3dDebug.js +109 -0
- package/plugins/render3dExtras.js +1754 -0
- package/plugins/render3dVoxels.js +502 -0
- package/plugins/textureSheet.js +86 -48
- package/plugins/threejs.js +183 -158
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +646 -526
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +8 -15
- package/src/engine.js +345 -194
- package/src/engineAudio.js +1022 -777
- package/src/engineBuild.mjs +289 -253
- package/src/engineDebug.js +912 -735
- package/src/engineDraw.js +1774 -1510
- package/src/engineEditor.js +2431 -0
- package/src/engineExport.js +419 -396
- package/src/engineInput.js +1462 -1293
- package/src/engineLogo.js +4 -3
- package/src/engineMath.js +1274 -1183
- package/src/engineObject.js +674 -551
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +67 -45
- package/src/engineSettings.js +808 -726
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +32 -13
- package/src/engineWebGL.js +1162 -941
- package/src/jsconfig.json +17 -13
- package/plugins/box2d.wasm.js +0 -630
- package/plugins/box2d.wasm.wasm +0 -0
package/src/engineDebug.js
CHANGED
|
@@ -1,736 +1,913 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LittleJS Debug System
|
|
3
|
-
* - Press Esc to toggle debug overlay with object picking
|
|
4
|
-
* - Number keys toggle debug visualizations (physics, particles, etc.)
|
|
5
|
-
* - +/- keys control time scale for slow motion/fast forward
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* @
|
|
19
|
-
* @
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* @
|
|
25
|
-
* @
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* @
|
|
31
|
-
* @
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* @
|
|
37
|
-
* @
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*
|
|
43
|
-
* @
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
* @param {
|
|
56
|
-
* @memberof Debug */
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
* @param {
|
|
97
|
-
* @param {
|
|
98
|
-
* @param {
|
|
99
|
-
* @param {
|
|
100
|
-
* @
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
ASSERT(
|
|
107
|
-
ASSERT(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
* @param {
|
|
121
|
-
* @param {
|
|
122
|
-
* @param {
|
|
123
|
-
* @param {
|
|
124
|
-
* @
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
ASSERT(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
* @param {
|
|
151
|
-
* @param {
|
|
152
|
-
* @
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** Draw a debug
|
|
169
|
-
* @param {Vector2}
|
|
170
|
-
* @param {
|
|
171
|
-
* @param {
|
|
172
|
-
* @param {
|
|
173
|
-
* @param {
|
|
174
|
-
* @
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
* @param {
|
|
201
|
-
* @param {
|
|
202
|
-
* @param {
|
|
203
|
-
* @param {
|
|
204
|
-
* @
|
|
205
|
-
|
|
206
|
-
{
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
ASSERT(
|
|
211
|
-
ASSERT(
|
|
212
|
-
ASSERT(
|
|
213
|
-
ASSERT(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* @
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* @
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
function
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
// show
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
//
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
//
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Debug System
|
|
3
|
+
* - Press Esc to toggle debug overlay with object picking
|
|
4
|
+
* - Number keys toggle debug visualizations (physics, particles, etc.) while the overlay is open
|
|
5
|
+
* - +/- keys control time scale for slow motion/fast forward while the overlay is open
|
|
6
|
+
* - setDebugKeysAlways lets those keys work with the overlay closed too
|
|
7
|
+
* - ASSERT and LOG macros for development (removed in release builds)
|
|
8
|
+
* - Debug primitive rendering (rectangles, circles, lines, points, text)
|
|
9
|
+
* - Screenshot and video capture support
|
|
10
|
+
* - FPS counter and performance watermark
|
|
11
|
+
* - Debug overlay shows mouse position and picked objects
|
|
12
|
+
* @namespace Debug
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
/** True if debug is enabled
|
|
18
|
+
* @type {boolean}
|
|
19
|
+
* @default
|
|
20
|
+
* @memberof Debug */
|
|
21
|
+
const debug = true;
|
|
22
|
+
|
|
23
|
+
/** Size to render debug points by default
|
|
24
|
+
* @type {number}
|
|
25
|
+
* @default
|
|
26
|
+
* @memberof Debug */
|
|
27
|
+
const debugPointSize = .5;
|
|
28
|
+
|
|
29
|
+
/** True if watermark with FPS should be shown, false in release builds
|
|
30
|
+
* @type {boolean}
|
|
31
|
+
* @default
|
|
32
|
+
* @memberof Debug */
|
|
33
|
+
let debugWatermark = true;
|
|
34
|
+
|
|
35
|
+
/** Key code used to toggle debug mode, Esc by default
|
|
36
|
+
* @type {string}
|
|
37
|
+
* @default
|
|
38
|
+
* @memberof Debug */
|
|
39
|
+
let debugKey = 'Escape';
|
|
40
|
+
|
|
41
|
+
/** Let the debug keys work while the overlay is closed, the number keys and the +/- time keys, for a game that does
|
|
42
|
+
* not use them; off by default, so they only work while the overlay is open
|
|
43
|
+
* @type {boolean}
|
|
44
|
+
* @default
|
|
45
|
+
* @memberof Debug */
|
|
46
|
+
let debugKeysAlways = false;
|
|
47
|
+
|
|
48
|
+
/** True if the debug overlay is active, always false in release builds
|
|
49
|
+
* @type {boolean}
|
|
50
|
+
* @default
|
|
51
|
+
* @memberof Debug */
|
|
52
|
+
let debugOverlay = false;
|
|
53
|
+
|
|
54
|
+
/** Open or close the debug overlay from code, as the debug key does; does nothing in release builds
|
|
55
|
+
* @param {boolean} [show]
|
|
56
|
+
* @memberof Debug */
|
|
57
|
+
function setDebugOverlay(show=true) { debug && (debugOverlay = !!show); }
|
|
58
|
+
|
|
59
|
+
/** True if the tweakables panel is shown, 9 toggles it while the overlay is open; set it to show the panel
|
|
60
|
+
* from the start, the panel is never shown in release builds
|
|
61
|
+
* @type {boolean}
|
|
62
|
+
* @default
|
|
63
|
+
* @memberof Debug */
|
|
64
|
+
let debugTweakables = false;
|
|
65
|
+
|
|
66
|
+
// the shadow behind debug text, so it reads over anything: the overlay menu and the mouse text both use it;
|
|
67
|
+
// a smaller blur is tighter and darker, a larger one softer and fainter
|
|
68
|
+
const debugTextShadowColor = '#000', debugTextShadowBlur = 9;
|
|
69
|
+
function debugTextShadow(context) { context.shadowColor = debugTextShadowColor; context.shadowBlur = debugTextShadowBlur; }
|
|
70
|
+
|
|
71
|
+
// Engine internal variables not exposed to documentation
|
|
72
|
+
let debugPrimitives = [], debugClearCount = 0, debugPhysics = false, debugRaycast = false, debugParticles = false, debugGamepads = false, debugSound = false, debugTiles = 0, debugTakeScreenshot;
|
|
73
|
+
// debugTiles is 0 for off, 1 for every layer, and 2 on for one layer at a time, see debugTileLayersSelected
|
|
74
|
+
|
|
75
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
76
|
+
// Debug helper functions
|
|
77
|
+
|
|
78
|
+
/** Asserts if the expression is false, does nothing in release builds
|
|
79
|
+
* Halts execution if the assert fails and throws an error
|
|
80
|
+
* @param {*} assert - any value, the assert fails when it is falsy
|
|
81
|
+
* @param {...Object} output - error message output
|
|
82
|
+
* @memberof Debug */
|
|
83
|
+
function ASSERT(assert, ...output)
|
|
84
|
+
{
|
|
85
|
+
if (assert) return;
|
|
86
|
+
console.assert(assert, ...output)
|
|
87
|
+
throw new Error('Assert failed!'); // halt execution
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Log to console if debug is enabled, does nothing in release builds
|
|
91
|
+
* @param {...Object} output - message output
|
|
92
|
+
* @memberof Debug */
|
|
93
|
+
function LOG(...output) { console.log(...output); }
|
|
94
|
+
|
|
95
|
+
/** Draw a debug rectangle in world space, or on the screen with screenSpace
|
|
96
|
+
* @param {Vector2} pos
|
|
97
|
+
* @param {Vector2} [size=vec2(0)]
|
|
98
|
+
* @param {Color|string} [color]
|
|
99
|
+
* @param {number} [time]
|
|
100
|
+
* @param {number} [angle]
|
|
101
|
+
* @param {boolean} [fill]
|
|
102
|
+
* @param {boolean} [screenSpace=drawScreenSpace]
|
|
103
|
+
* @memberof Debug */
|
|
104
|
+
function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false, screenSpace=drawScreenSpace)
|
|
105
|
+
{
|
|
106
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
107
|
+
ASSERT(isVector2(size), 'size must be a vec2');
|
|
108
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
109
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
110
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
111
|
+
|
|
112
|
+
if (headlessMode) return; // nothing draws them, they would pile up
|
|
113
|
+
if (isColor(color))
|
|
114
|
+
color = color.toString();
|
|
115
|
+
const timer = new Timer(time, true); // real time, so they still expire while the game is paused
|
|
116
|
+
debugPrimitives.push({pos:pos.copy(), size:size.copy(), color, timer, angle, fill, screenSpace});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Draw a debug poly in world space, or on the screen with screenSpace
|
|
120
|
+
* @param {Vector2} pos
|
|
121
|
+
* @param {Array<Vector2>} points
|
|
122
|
+
* @param {Color|string} [color]
|
|
123
|
+
* @param {number} [time]
|
|
124
|
+
* @param {number} [angle]
|
|
125
|
+
* @param {boolean} [fill]
|
|
126
|
+
* @param {boolean} [screenSpace=drawScreenSpace]
|
|
127
|
+
* @memberof Debug */
|
|
128
|
+
function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false, screenSpace=drawScreenSpace)
|
|
129
|
+
{
|
|
130
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
131
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
132
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
133
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
134
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
135
|
+
|
|
136
|
+
if (headlessMode) return;
|
|
137
|
+
if (isColor(color))
|
|
138
|
+
color = color.toString();
|
|
139
|
+
pos = pos.copy();
|
|
140
|
+
points = points.map(p=>p.copy());
|
|
141
|
+
const timer = new Timer(time, true); // real time, so they still expire while the game is paused
|
|
142
|
+
debugPrimitives.push({pos, points, color, timer, angle, fill, screenSpace});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Draw a debug circle in world space, or on the screen with screenSpace
|
|
146
|
+
* @param {Vector2} pos
|
|
147
|
+
* @param {number} [size] - diameter
|
|
148
|
+
* @param {Color|string} [color]
|
|
149
|
+
* @param {number} [time]
|
|
150
|
+
* @param {boolean} [fill]
|
|
151
|
+
* @param {boolean} [screenSpace=drawScreenSpace]
|
|
152
|
+
* @memberof Debug */
|
|
153
|
+
function debugCircle(pos, size=0, color=WHITE, time=0, fill=false, screenSpace=drawScreenSpace)
|
|
154
|
+
{
|
|
155
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
156
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
157
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
158
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
159
|
+
|
|
160
|
+
if (headlessMode) return;
|
|
161
|
+
if (isColor(color))
|
|
162
|
+
color = color.toString();
|
|
163
|
+
pos = pos.copy();
|
|
164
|
+
const timer = new Timer(time, true); // real time, so they still expire while the game is paused
|
|
165
|
+
debugPrimitives.push({pos, size, color, timer, angle:0, fill, screenSpace});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Draw a debug point in world space, or on the screen with screenSpace
|
|
169
|
+
* @param {Vector2} pos
|
|
170
|
+
* @param {Color|string} [color]
|
|
171
|
+
* @param {number} [time]
|
|
172
|
+
* @param {number} [angle]
|
|
173
|
+
* @param {boolean} [screenSpace=drawScreenSpace]
|
|
174
|
+
* @memberof Debug */
|
|
175
|
+
function debugPoint(pos, color, time, angle, screenSpace=drawScreenSpace)
|
|
176
|
+
{ debugRect(pos, undefined, color, time, angle, false, screenSpace); }
|
|
177
|
+
|
|
178
|
+
/** Draw a debug line in world space, or on the screen with screenSpace
|
|
179
|
+
* @param {Vector2} posA
|
|
180
|
+
* @param {Vector2} posB
|
|
181
|
+
* @param {Color|string} [color]
|
|
182
|
+
* @param {number} [width]
|
|
183
|
+
* @param {number} [time]
|
|
184
|
+
* @param {boolean} [screenSpace=drawScreenSpace]
|
|
185
|
+
* @memberof Debug */
|
|
186
|
+
function debugLine(posA, posB, color, width=.1, time=0, screenSpace=drawScreenSpace)
|
|
187
|
+
{
|
|
188
|
+
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
189
|
+
ASSERT(isVector2(posB), 'posB must be a vec2');
|
|
190
|
+
ASSERT(isNumber(width), 'width must be a number');
|
|
191
|
+
|
|
192
|
+
const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
|
|
193
|
+
const size = vec2(width, halfDelta.length()*2);
|
|
194
|
+
// screen space y points down, so its angle is measured the other way, as drawLine does
|
|
195
|
+
const angle = vec2(halfDelta.x, screenSpace ? -halfDelta.y : halfDelta.y).angle();
|
|
196
|
+
debugRect(posA.add(halfDelta), size, color, time, angle, true, screenSpace);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Draw a debug combined axis aligned bounding box in world space, or on the screen with screenSpace
|
|
200
|
+
* @param {Vector2} posA
|
|
201
|
+
* @param {Vector2} sizeA
|
|
202
|
+
* @param {Vector2} posB
|
|
203
|
+
* @param {Vector2} sizeB
|
|
204
|
+
* @param {Color|string} [color]
|
|
205
|
+
* @param {number} [time]
|
|
206
|
+
* @param {boolean} [screenSpace=drawScreenSpace]
|
|
207
|
+
* @memberof Debug */
|
|
208
|
+
function debugOverlap(posA, sizeA, posB, sizeB, color, time, screenSpace=drawScreenSpace)
|
|
209
|
+
{
|
|
210
|
+
ASSERT(isVector2(posA), 'posA must be a vec2');
|
|
211
|
+
ASSERT(isVector2(posB), 'posB must be a vec2');
|
|
212
|
+
ASSERT(isVector2(sizeA), 'sizeA must be a vec2');
|
|
213
|
+
ASSERT(isVector2(sizeB), 'sizeB must be a vec2');
|
|
214
|
+
|
|
215
|
+
const minPos = vec2(
|
|
216
|
+
min(posA.x - sizeA.x/2, posB.x - sizeB.x/2),
|
|
217
|
+
min(posA.y - sizeA.y/2, posB.y - sizeB.y/2)
|
|
218
|
+
);
|
|
219
|
+
const maxPos = vec2(
|
|
220
|
+
max(posA.x + sizeA.x/2, posB.x + sizeB.x/2),
|
|
221
|
+
max(posA.y + sizeA.y/2, posB.y + sizeB.y/2)
|
|
222
|
+
);
|
|
223
|
+
debugRect(minPos.lerp(maxPos,.5), maxPos.subtract(minPos), color, time, 0, false, screenSpace);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Draw debug text in world space, or on the screen with screenSpace
|
|
227
|
+
* @param {string|number} text
|
|
228
|
+
* @param {Vector2} pos
|
|
229
|
+
* @param {number} [size]
|
|
230
|
+
* @param {Color|string} [color]
|
|
231
|
+
* @param {number} [time]
|
|
232
|
+
* @param {number} [angle]
|
|
233
|
+
* @param {string} [font]
|
|
234
|
+
* @param {boolean} [screenSpace=drawScreenSpace]
|
|
235
|
+
* @memberof Debug */
|
|
236
|
+
function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace', screenSpace=drawScreenSpace)
|
|
237
|
+
{
|
|
238
|
+
ASSERT(isStringLike(text), 'text must be a string');
|
|
239
|
+
ASSERT(isVector2(pos), 'pos must be a vec2');
|
|
240
|
+
ASSERT(isNumber(size), 'size must be a number');
|
|
241
|
+
ASSERT(isStringLike(color) || isColor(color), 'color is invalid');
|
|
242
|
+
ASSERT(isNumber(time), 'time must be a number');
|
|
243
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
244
|
+
ASSERT(isStringLike(font), 'font must be a string');
|
|
245
|
+
|
|
246
|
+
if (headlessMode) return;
|
|
247
|
+
if (isColor(color))
|
|
248
|
+
color = color.toString();
|
|
249
|
+
pos = pos.copy();
|
|
250
|
+
const timer = new Timer(time, true); // real time, so they still expire while the game is paused
|
|
251
|
+
debugPrimitives.push({text, pos, size, color, timer, angle, font, screenSpace});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Clear all debug primitives in the list
|
|
255
|
+
* @memberof Debug */
|
|
256
|
+
function debugClear() { debugPrimitives = []; ++debugClearCount; } // the count lets plugins clear their own
|
|
257
|
+
|
|
258
|
+
/** Trigger debug system to take a screenshot
|
|
259
|
+
* @memberof Debug */
|
|
260
|
+
function debugScreenshot() { debugTakeScreenshot = 1; }
|
|
261
|
+
|
|
262
|
+
/** Breaks on all asserts/errors, hides the canvas, and shows message in plain text
|
|
263
|
+
* This is a good function to call at the start of your game to catch all errors
|
|
264
|
+
* In release builds this function has no effect
|
|
265
|
+
* @memberof Debug */
|
|
266
|
+
function debugShowErrors()
|
|
267
|
+
{
|
|
268
|
+
const showError = (message)=>
|
|
269
|
+
{
|
|
270
|
+
// replace entire page with error message
|
|
271
|
+
document.body.style.cssText = 'background-color:#111;margin:8px';
|
|
272
|
+
document.body.innerHTML = `<pre style=color:#f00;font-size:28px;white-space:pre-wrap>` + message;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const originalAssert = console.assert;
|
|
276
|
+
console.assert = (assertion, ...output)=>
|
|
277
|
+
{
|
|
278
|
+
originalAssert(assertion, ...output);
|
|
279
|
+
if (!assertion)
|
|
280
|
+
{
|
|
281
|
+
const message = output.join(' ');
|
|
282
|
+
const stack = new Error().stack;
|
|
283
|
+
throw 'Assertion failed!\n' + message + '\n' + stack;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
onunhandledrejection = (event)=>
|
|
287
|
+
showError(event.reason?.stack || event.reason);
|
|
288
|
+
onerror = (message, source, lineno, colno)=>
|
|
289
|
+
showError(`${message}\n${source}\nLn ${lineno}, Col ${colno}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
293
|
+
// Engine debug functions (called automatically)
|
|
294
|
+
|
|
295
|
+
function debugInit()
|
|
296
|
+
{
|
|
297
|
+
if (showEngineVersion)
|
|
298
|
+
console.warn("LittleJS DEBUG build loaded. Use the release build for production.");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function debugUpdate()
|
|
302
|
+
{
|
|
303
|
+
if (!debug) return;
|
|
304
|
+
|
|
305
|
+
if (keyWasPressed(debugKey)) // Esc
|
|
306
|
+
debugOverlay = !debugOverlay;
|
|
307
|
+
const debugKeys = debugOverlay || debugKeysAlways; // the keys work while the overlay is open, or always if set
|
|
308
|
+
if (debugKeys)
|
|
309
|
+
{
|
|
310
|
+
if (keyWasPressed('Digit1'))
|
|
311
|
+
debugPhysics = !debugPhysics, debugParticles = false;
|
|
312
|
+
if (keyWasPressed('Digit2'))
|
|
313
|
+
debugTiles = (debugTiles + 1) % (debugTileLayersShown().length + 2); // off, all, then each layer
|
|
314
|
+
if (keyWasPressed('Digit3'))
|
|
315
|
+
debugParticles = !debugParticles, debugPhysics = false;
|
|
316
|
+
if (keyWasPressed('Digit4'))
|
|
317
|
+
debugRaycast = !debugRaycast;
|
|
318
|
+
if (keyWasPressed('Digit5'))
|
|
319
|
+
debugGamepads = !debugGamepads;
|
|
320
|
+
if (keyWasPressed('Digit6'))
|
|
321
|
+
debugSound = !debugSound;
|
|
322
|
+
if (keyWasPressed('Digit7'))
|
|
323
|
+
debugScreenshot();
|
|
324
|
+
if (keyWasPressed('Digit9'))
|
|
325
|
+
debugTweakables = !debugTweakables;
|
|
326
|
+
if (keyWasPressed('Digit0'))
|
|
327
|
+
levelEditor.isOpen ? levelEditor.close() : levelEditor.open();
|
|
328
|
+
}
|
|
329
|
+
if (debugVideoCaptureIsActive())
|
|
330
|
+
{
|
|
331
|
+
// control to stop video capture, a capture the overlay started also stops when the overlay closes,
|
|
332
|
+
// one the game started from code runs until it calls debugVideoCaptureStop
|
|
333
|
+
if (debugKeys ? keyWasPressed('Digit8') : debugVideoCapture.fromOverlay)
|
|
334
|
+
debugVideoCaptureStop();
|
|
335
|
+
}
|
|
336
|
+
else if (debugKeys && keyWasPressed('Digit8'))
|
|
337
|
+
{
|
|
338
|
+
debugVideoCaptureStart();
|
|
339
|
+
if (debugVideoCapture)
|
|
340
|
+
debugVideoCapture.fromOverlay = true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// the text beside the mouse, with the same shadow as the overlay
|
|
345
|
+
function debugMouseText(text)
|
|
346
|
+
{
|
|
347
|
+
mainContext.save();
|
|
348
|
+
debugTextShadow(mainContext);
|
|
349
|
+
drawTextScreen(text, mousePosScreen, 24, WHITE, 0, BLACK, 'center', 'monospace', undefined, undefined, 0, mainContext);
|
|
350
|
+
mainContext.restore();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// the center of the tile under a world position, on the grid of the collision layer that has a tile there
|
|
354
|
+
function debugTileCellCenter(pos)
|
|
355
|
+
{
|
|
356
|
+
for (const layer of tileCollisionLayers)
|
|
357
|
+
{
|
|
358
|
+
const local = pos.subtract(layer.pos);
|
|
359
|
+
if (local.arrayCheck(layer.size) && layer.getCollisionData(local))
|
|
360
|
+
return local.floor().add(layer.pos).add(vec2(.5));
|
|
361
|
+
}
|
|
362
|
+
return pos.floor().add(vec2(.5));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// the tile layers Debug Tiles can show, the ones not switched off with debugShow, in render order
|
|
366
|
+
function debugTileLayersShown()
|
|
367
|
+
{
|
|
368
|
+
return engineObjects.filter(o=> o instanceof TileLayer && !o.destroyed && o.debugShow)
|
|
369
|
+
.sort((a, b)=> a.renderOrder - b.renderOrder); // a stable sort, so equal orders keep the order they were made in
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// the layers Debug Tiles shows right now, from the ones shown: every one, or the one the 2 key has stepped to,
|
|
373
|
+
// every one again if layers went away since
|
|
374
|
+
function debugTileLayersSelected(layers)
|
|
375
|
+
{
|
|
376
|
+
const layer = layers[debugTiles - 2];
|
|
377
|
+
return layer ? [layer] : layers;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// what the overlay menu says Debug Tiles is showing
|
|
381
|
+
function debugTilesLabel(layers)
|
|
382
|
+
{
|
|
383
|
+
if (!debugTiles) return '';
|
|
384
|
+
const layer = layers[debugTiles - 2];
|
|
385
|
+
return layer ? ` (layer ${debugTiles - 1} of ${layers.length}, renderOrder ${layer.renderOrder}${layer instanceof TileCollisionLayer ? ', collision' : ''})` : ' (all)';
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Debug Tiles: each layer's bounds, then the collision value of every cell on screen, tinted by value, with the
|
|
389
|
+
// number when a tile is big enough on screen to read it
|
|
390
|
+
function debugTileLayers(layers)
|
|
391
|
+
{
|
|
392
|
+
// everything the camera can see, turned or not
|
|
393
|
+
const reach = getCameraSize().length() / 2;
|
|
394
|
+
const showValues = cameraScale >= 24;
|
|
395
|
+
for (const layer of debugTileLayersSelected(layers))
|
|
396
|
+
{
|
|
397
|
+
const isCollision = layer instanceof TileCollisionLayer, size = layer.size, pos = layer.pos;
|
|
398
|
+
const color = isCollision ? '#f80' : '#0cf';
|
|
399
|
+
debugRect(pos.add(size.scale(.5)), size, color, 0, 0, false, false);
|
|
400
|
+
const label = 'layer ' + (layers.indexOf(layer) + 1) + ', ' + size.x + 'x' + size.y + ', renderOrder ' + layer.renderOrder + (isCollision ? ', collision' : '');
|
|
401
|
+
debugText(label, pos.add(vec2(size.x / 2, size.y + .4)), .6, color, 0, 0, 'monospace', false);
|
|
402
|
+
if (!isCollision) continue;
|
|
403
|
+
|
|
404
|
+
const x0 = max(0, floor(cameraPos.x - reach - pos.x)), x1 = min(size.x, ceil(cameraPos.x + reach - pos.x));
|
|
405
|
+
const y0 = max(0, floor(cameraPos.y - reach - pos.y)), y1 = min(size.y, ceil(cameraPos.y + reach - pos.y));
|
|
406
|
+
for (let y = y0; y < y1; ++y)
|
|
407
|
+
for (let x = x0; x < x1; ++x)
|
|
408
|
+
{
|
|
409
|
+
const data = layer.collisionData[y * size.x + x];
|
|
410
|
+
if (!data) continue;
|
|
411
|
+
const center = vec2(pos.x + x + .5, pos.y + y + .5), tint = hsl(data * .17 % 1, 1, .6, .8);
|
|
412
|
+
debugRect(center, vec2(.9), tint, 0, 0, false, false);
|
|
413
|
+
showValues && debugText(data, center, .5, tint, 0, 0, 'monospace', false);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// the tile and collision value under the mouse of each layer shown by Debug Tiles, as lines of the mouse text
|
|
419
|
+
function debugTileText(layers)
|
|
420
|
+
{
|
|
421
|
+
if (!debugTiles) return '';
|
|
422
|
+
// each layer by its number in the 2 key's cycle, the one the menu shows
|
|
423
|
+
let text = '';
|
|
424
|
+
for (const layer of debugTileLayersSelected(layers))
|
|
425
|
+
{
|
|
426
|
+
const local = mousePos.subtract(layer.pos);
|
|
427
|
+
if (!local.arrayCheck(layer.size)) continue;
|
|
428
|
+
// only a collision layer has collision data, the others are just drawn
|
|
429
|
+
const data = layer.getData(local);
|
|
430
|
+
const collision = layer instanceof TileCollisionLayer && layer.getCollisionData(local);
|
|
431
|
+
text += '\nlayer ' + (layers.indexOf(layer) + 1) + ': tile ' + (data?.tile ?? 'empty') + (collision ? ', collision ' + collision : '');
|
|
432
|
+
}
|
|
433
|
+
return text;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function debugRender()
|
|
437
|
+
{
|
|
438
|
+
if (debugTakeScreenshot)
|
|
439
|
+
{
|
|
440
|
+
// combine canvases, remove alpha and save, before the capture check so it is taken during video capture too
|
|
441
|
+
glFlush();
|
|
442
|
+
combineCanvases();
|
|
443
|
+
saveCanvas(mainCanvas);
|
|
444
|
+
debugTakeScreenshot = 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (debugVideoCaptureIsActive())
|
|
448
|
+
{
|
|
449
|
+
// don't show debug info when capturing video, but still drop the expired primitives so they don't pile up
|
|
450
|
+
debugPrimitives = debugPrimitives.filter(r=>r.timer<0);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// flush any gl sprites before drawing debug info
|
|
455
|
+
glFlush();
|
|
456
|
+
|
|
457
|
+
// the tile layers Debug Tiles can show, found once for everything below
|
|
458
|
+
const tileLayers = debugTiles ? debugTileLayersShown() : [];
|
|
459
|
+
|
|
460
|
+
const savedDrawCount = drawCount;
|
|
461
|
+
const savedPrimitiveCount = primitiveCount;
|
|
462
|
+
|
|
463
|
+
const debugContext = mainContext;
|
|
464
|
+
if (debugGamepads && gamepadsEnable)
|
|
465
|
+
{
|
|
466
|
+
// draw gamepads
|
|
467
|
+
const maxGamepads = 8;
|
|
468
|
+
let gamepadConnectedCount = 0;
|
|
469
|
+
for (let i = 0; i < maxGamepads; i++)
|
|
470
|
+
gamepadConnected(i) && gamepadConnectedCount++;
|
|
471
|
+
|
|
472
|
+
for (let i = 0; i < maxGamepads; i++)
|
|
473
|
+
{
|
|
474
|
+
if (!gamepadConnected(i))
|
|
475
|
+
continue;
|
|
476
|
+
|
|
477
|
+
const stickScale = 1;
|
|
478
|
+
const buttonScale = .2;
|
|
479
|
+
const cornerPos = cameraPos.add(vec2(-stickScale*2, ((gamepadConnectedCount-1)/2-i)*stickScale*3));
|
|
480
|
+
debugText(i, cornerPos.add(vec2(-stickScale, stickScale)), 1, WHITE, 0, 0, 'monospace', false);
|
|
481
|
+
if (i === gamepadPrimary)
|
|
482
|
+
debugText('Main', cornerPos.add(vec2(-stickScale*2, 0)),1, '#0f0', 0, 0, 'monospace', false);
|
|
483
|
+
|
|
484
|
+
// read analog sticks
|
|
485
|
+
const stickCount = gamepadStickCount(i); // none after an inputClear this frame
|
|
486
|
+
for (let j = 0; j < stickCount; j++)
|
|
487
|
+
{
|
|
488
|
+
if (!(j in gamepadStickData[i]))
|
|
489
|
+
continue; // skip sticks that are not present (eg a disabled touch left stick)
|
|
490
|
+
const stick = gamepadStick(j, i);
|
|
491
|
+
const drawPos = cornerPos.add(vec2(j*stickScale*2, 0));
|
|
492
|
+
const stickPos = drawPos.add(stick.scale(stickScale));
|
|
493
|
+
debugCircle(drawPos, stickScale*2, '#fff7',0,true, false);
|
|
494
|
+
debugLine(drawPos, stickPos, '#f00', .1, 0, false);
|
|
495
|
+
debugText(j, drawPos, .3, WHITE, 0, 0, 'monospace', false);
|
|
496
|
+
debugPoint(stickPos, '#f00', undefined, undefined, false);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const buttonCount = inputData[i+1].length;
|
|
500
|
+
for (let j = 0; j < buttonCount; j++)
|
|
501
|
+
{
|
|
502
|
+
const drawPos = cornerPos.add(vec2(j*buttonScale*2, -stickScale-buttonScale*2));
|
|
503
|
+
const pressed = gamepadIsDown(j, i);
|
|
504
|
+
debugCircle(drawPos, buttonScale*2, pressed ? '#f00' : '#fff7', 0, true, false);
|
|
505
|
+
debugText(j, drawPos, .3, WHITE, 0, 0, 'monospace', false);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
let debugObject;
|
|
511
|
+
if (debugOverlay)
|
|
512
|
+
{
|
|
513
|
+
// draw red rectangle around screen
|
|
514
|
+
const cameraSize = getCameraSize();
|
|
515
|
+
debugRect(cameraPos, cameraSize.subtract(vec2(.1)), '#f008', 0, 0, false, false);
|
|
516
|
+
|
|
517
|
+
// mouse pick
|
|
518
|
+
let bestDistance = Infinity;
|
|
519
|
+
for (const o of engineObjects)
|
|
520
|
+
{
|
|
521
|
+
if (o.destroyed)
|
|
522
|
+
continue;
|
|
523
|
+
|
|
524
|
+
if (o instanceof TileLayer)
|
|
525
|
+
continue; // prevent tile layers from being picked
|
|
526
|
+
|
|
527
|
+
o.renderDebugInfo();
|
|
528
|
+
if (!o.size.x || !o.size.y)
|
|
529
|
+
continue;
|
|
530
|
+
|
|
531
|
+
const distance = mousePos.distanceSquared(o.pos);
|
|
532
|
+
if (distance < bestDistance)
|
|
533
|
+
{
|
|
534
|
+
bestDistance = distance;
|
|
535
|
+
debugObject = o;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (tileCollisionTest(mousePos))
|
|
540
|
+
{
|
|
541
|
+
// show the collision tile under the mouse, on its layer's own grid
|
|
542
|
+
drawRect(debugTileCellCenter(mousePos), vec2(1), rgb(1,1,0,.5), 0, false, false);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Debug Tiles draws with the overlay closed too, like the other modes listed in the corner
|
|
547
|
+
debugTiles && debugTileLayers(tileLayers);
|
|
548
|
+
|
|
549
|
+
{
|
|
550
|
+
// draw debug primitives, the line width put back after so the game's next frame starts as it would in release
|
|
551
|
+
debugContext.save();
|
|
552
|
+
debugContext.lineWidth = 2;
|
|
553
|
+
debugPrimitives.forEach(p=>
|
|
554
|
+
{
|
|
555
|
+
debugContext.save();
|
|
556
|
+
|
|
557
|
+
// create canvas transform from world space to screen space
|
|
558
|
+
// without scaling because we want consistent pixel sizes
|
|
559
|
+
let pos = p.pos, scale = 1, angle = p.angle;
|
|
560
|
+
if (!p.screenSpace)
|
|
561
|
+
{
|
|
562
|
+
pos = worldToScreen(p.pos);
|
|
563
|
+
scale = cameraScale;
|
|
564
|
+
angle -= cameraAngle;
|
|
565
|
+
}
|
|
566
|
+
debugContext.translate(pos.x|0, pos.y|0);
|
|
567
|
+
debugContext.rotate(angle);
|
|
568
|
+
debugContext.scale(1, p.text !== undefined ? 1 : -1);
|
|
569
|
+
debugContext.fillStyle = p.color;
|
|
570
|
+
debugContext.strokeStyle = p.color;
|
|
571
|
+
if (p.text !== undefined)
|
|
572
|
+
{
|
|
573
|
+
debugContext.font = p.size*scale + 'px '+ p.font;
|
|
574
|
+
debugContext.textAlign = 'center';
|
|
575
|
+
debugContext.textBaseline = 'middle';
|
|
576
|
+
debugContext.fillText(p.text, 0, 0);
|
|
577
|
+
}
|
|
578
|
+
else if (p.points !== undefined)
|
|
579
|
+
{
|
|
580
|
+
// poly
|
|
581
|
+
debugContext.beginPath();
|
|
582
|
+
for (const point of p.points)
|
|
583
|
+
{
|
|
584
|
+
const p2 = point.scale(scale).floor();
|
|
585
|
+
debugContext.lineTo(p2.x, p2.y);
|
|
586
|
+
}
|
|
587
|
+
debugContext.closePath();
|
|
588
|
+
p.fill && debugContext.fill();
|
|
589
|
+
debugContext.stroke();
|
|
590
|
+
}
|
|
591
|
+
else if (p.size === 0 || (p.size.x === 0 && p.size.y === 0))
|
|
592
|
+
{
|
|
593
|
+
// point
|
|
594
|
+
const pointSize = debugPointSize * scale;
|
|
595
|
+
debugContext.fillRect(-pointSize/2, -1, pointSize, 3);
|
|
596
|
+
debugContext.fillRect(-1, -pointSize/2, 3, pointSize);
|
|
597
|
+
}
|
|
598
|
+
else if (p.size.x !== undefined)
|
|
599
|
+
{
|
|
600
|
+
// rect
|
|
601
|
+
const s = p.size.scale(scale).floor();
|
|
602
|
+
const w = s.x, h = s.y;
|
|
603
|
+
p.fill && debugContext.fillRect(-w/2|0, -h/2|0, w, h);
|
|
604
|
+
debugContext.strokeRect(-w/2|0, -h/2|0, w, h);
|
|
605
|
+
}
|
|
606
|
+
else
|
|
607
|
+
{
|
|
608
|
+
// circle
|
|
609
|
+
debugContext.beginPath();
|
|
610
|
+
debugContext.arc(0, 0, abs(p.size)*scale/2, 0, 9); // a negative radius would throw
|
|
611
|
+
p.fill && debugContext.fill();
|
|
612
|
+
debugContext.stroke();
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
debugContext.restore();
|
|
616
|
+
});
|
|
617
|
+
debugContext.restore();
|
|
618
|
+
|
|
619
|
+
// remove expired primitives
|
|
620
|
+
debugPrimitives = debugPrimitives.filter(r=>r.timer<0);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (debugObject)
|
|
624
|
+
{
|
|
625
|
+
const raycastHitPos = tileCollisionRaycast(debugObject.pos, mousePos);
|
|
626
|
+
raycastHitPos && drawRect(debugTileCellCenter(raycastHitPos), vec2(1), rgb(0,1,1,.3), 0, false, false);
|
|
627
|
+
drawLine(mousePos, debugObject.pos, .1, raycastHitPos ? rgb(1,0,0,.5) : rgb(0,1,0,.5), undefined, undefined, false, false);
|
|
628
|
+
|
|
629
|
+
let debugText = 'mouse pos = ' + mousePos;
|
|
630
|
+
if (tileCollisionLayers.length)
|
|
631
|
+
debugText += '\nmouse collision = ' + tileCollisionGetData(mousePos);
|
|
632
|
+
debugText += debugTileText(tileLayers);
|
|
633
|
+
debugText += '\n\n--- object info ---\n';
|
|
634
|
+
debugText += debugObject.toString();
|
|
635
|
+
debugMouseText(debugText);
|
|
636
|
+
}
|
|
637
|
+
else if (debugOverlay && debugTiles)
|
|
638
|
+
{
|
|
639
|
+
// no object to pick, the tiles under the mouse on their own
|
|
640
|
+
const text = debugTileText(tileLayers);
|
|
641
|
+
text && debugMouseText('mouse pos = ' + mousePos + text);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
{
|
|
645
|
+
// draw debug overlay
|
|
646
|
+
const fontSize = 20;
|
|
647
|
+
const lineHeight = fontSize * 1.2 | 0;
|
|
648
|
+
debugContext.save();
|
|
649
|
+
debugContext.fillStyle = '#fff';
|
|
650
|
+
debugContext.textAlign = 'left';
|
|
651
|
+
debugContext.textBaseline = 'top';
|
|
652
|
+
debugContext.font = fontSize + 'px monospace';
|
|
653
|
+
debugTextShadow(debugContext);
|
|
654
|
+
|
|
655
|
+
let x = 9, y = 0, h = lineHeight;
|
|
656
|
+
if (debugOverlay)
|
|
657
|
+
{
|
|
658
|
+
debugContext.fillText(`${engineName} v${engineVersion}`, x, y += h/2 );
|
|
659
|
+
debugContext.fillText('Time: ' + formatTime(time), x, y += h);
|
|
660
|
+
debugContext.fillText('FPS: ' + averageFPS.toFixed(1) + (glEnable?' WebGL':' Canvas2D'),
|
|
661
|
+
x, y += h);
|
|
662
|
+
debugContext.fillText('Objects: ' + engineObjects.length, x, y += h);
|
|
663
|
+
debugContext.fillText('Draw Calls: ' + drawCount, x, y += h);
|
|
664
|
+
debugContext.fillText('Primitives: ' + primitiveCount, x, y += h);
|
|
665
|
+
debugContext.fillText('---------', x, y += h);
|
|
666
|
+
debugContext.fillStyle = '#f00';
|
|
667
|
+
debugContext.fillText('ESC: Debug Overlay', x, y += h);
|
|
668
|
+
debugContext.fillStyle = debugPhysics ? '#f00' : '#fff';
|
|
669
|
+
debugContext.fillText('1: Debug Physics', x, y += h);
|
|
670
|
+
debugContext.fillStyle = debugTiles ? '#f00' : '#fff';
|
|
671
|
+
debugContext.fillText('2: Debug Tiles' + debugTilesLabel(tileLayers), x, y += h);
|
|
672
|
+
debugContext.fillStyle = debugParticles ? '#f00' : '#fff';
|
|
673
|
+
debugContext.fillText('3: Debug Particles', x, y += h);
|
|
674
|
+
debugContext.fillStyle = debugRaycast ? '#f00' : '#fff';
|
|
675
|
+
debugContext.fillText('4: Debug Raycasts', x, y += h);
|
|
676
|
+
debugContext.fillStyle = debugGamepads ? '#f00' : '#fff';
|
|
677
|
+
debugContext.fillText('5: Debug Gamepads', x, y += h);
|
|
678
|
+
debugContext.fillStyle = debugSound ? '#f00' : '#fff';
|
|
679
|
+
debugContext.fillText('6: Debug Sound', x, y += h);
|
|
680
|
+
debugContext.fillStyle = '#fff';
|
|
681
|
+
debugContext.fillText('7: Save Screenshot', x, y += h);
|
|
682
|
+
debugContext.fillText('8: Toggle Video Capture', x, y += h);
|
|
683
|
+
debugContext.fillStyle = debugTweakables ? '#f00' : '#fff';
|
|
684
|
+
debugContext.fillText('9: Tweakables', x, y += h);
|
|
685
|
+
debugContext.fillStyle = levelEditor.isOpen ? '#f00' : '#fff';
|
|
686
|
+
debugContext.fillText('0: Edit Level', x, y += h);
|
|
687
|
+
debugContext.fillStyle = '#fff';
|
|
688
|
+
|
|
689
|
+
let keysPressed = '';
|
|
690
|
+
let mousePressed = '';
|
|
691
|
+
for (const i in inputData[0])
|
|
692
|
+
{
|
|
693
|
+
// read the input state itself, keyIsDown asserts on the numbered keys for..in gives as strings
|
|
694
|
+
if (!(inputData[0][i] & 1))
|
|
695
|
+
continue;
|
|
696
|
+
if (!isNaN(+i)) // mouse buttons are numbered, keys are named
|
|
697
|
+
mousePressed += i + ' ' ;
|
|
698
|
+
else
|
|
699
|
+
keysPressed += i + ' ' ;
|
|
700
|
+
}
|
|
701
|
+
mousePressed && debugContext.fillText('Mouse: ' + mousePressed, x, y += h);
|
|
702
|
+
keysPressed && debugContext.fillText('Keys: ' + keysPressed, x, y += h);
|
|
703
|
+
|
|
704
|
+
// show gamepad buttons
|
|
705
|
+
for (let i = 1; i < inputData.length; i++)
|
|
706
|
+
{
|
|
707
|
+
let buttonsPressed = '';
|
|
708
|
+
if (inputData[i])
|
|
709
|
+
for (const j in inputData[i])
|
|
710
|
+
{
|
|
711
|
+
if (inputData[i][j] & 1)
|
|
712
|
+
buttonsPressed += j + ' ' ;
|
|
713
|
+
}
|
|
714
|
+
buttonsPressed && debugContext.fillText(`Gamepad ${i-1}: ` + buttonsPressed, x, y += h);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else
|
|
718
|
+
{
|
|
719
|
+
debugContext.fillText(debugPhysics ? 'Debug Physics' : '', x, y += h);
|
|
720
|
+
debugContext.fillText(debugTiles ? 'Debug Tiles' + debugTilesLabel(tileLayers) : '', x, y += h);
|
|
721
|
+
debugContext.fillText(debugParticles ? 'Debug Particles' : '', x, y += h);
|
|
722
|
+
debugContext.fillText(debugRaycast ? 'Debug Raycasts' : '', x, y += h);
|
|
723
|
+
debugContext.fillText(debugGamepads ? 'Debug Gamepads' : '', x, y += h);
|
|
724
|
+
debugContext.fillText(debugSound ? 'Debug Sound' : '', x, y += h);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
debugContext.restore();
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if (debugWatermark || debugOverlay)
|
|
731
|
+
{
|
|
732
|
+
// show fps stats display, the text state put back after for the game's next frame
|
|
733
|
+
mainContext.save();
|
|
734
|
+
mainContext.textAlign = 'right';
|
|
735
|
+
mainContext.textBaseline = 'top';
|
|
736
|
+
mainContext.font = '1em monospace';
|
|
737
|
+
mainContext.fillStyle = '#000';
|
|
738
|
+
const text = engineName + ' v' + engineVersion + ' / '
|
|
739
|
+
+ savedDrawCount + ' / ' + savedPrimitiveCount + ' / '
|
|
740
|
+
+ engineObjects.length + ' / ' + averageFPS.toFixed(1)
|
|
741
|
+
+ (glEnable ? ' GL' : ' 2D') ;
|
|
742
|
+
mainContext.fillText(text, mainCanvasSize.x-3, 3);
|
|
743
|
+
mainContext.fillStyle = '#fff';
|
|
744
|
+
mainContext.fillText(text, mainCanvasSize.x-2, 2);
|
|
745
|
+
mainContext.restore();
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function debugRenderPost() { debugVideoCaptureIsActive() && debugVideoCaptureUpdate(); }
|
|
750
|
+
|
|
751
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
752
|
+
// video capture - records video and audio at 60 fps using MediaRecorder API
|
|
753
|
+
|
|
754
|
+
// internal variables used to capture video
|
|
755
|
+
let debugVideoCapture, debugVideoCaptureIcon;
|
|
756
|
+
|
|
757
|
+
/** Check if video capture is active
|
|
758
|
+
* @memberof Debug */
|
|
759
|
+
function debugVideoCaptureIsActive() { return !!debugVideoCapture; }
|
|
760
|
+
|
|
761
|
+
/** Start capturing video
|
|
762
|
+
* @memberof Debug */
|
|
763
|
+
function debugVideoCaptureStart()
|
|
764
|
+
{
|
|
765
|
+
ASSERT(!debugVideoCaptureIsActive(), 'Already capturing video!');
|
|
766
|
+
|
|
767
|
+
// everything that can fail where recording is unsupported is in the try, a missing captureStream
|
|
768
|
+
// or a MediaRecorder that refuses webm throws here and is cleaned up rather than stopping the game
|
|
769
|
+
const captureTimer = new Timer(0, true);
|
|
770
|
+
const chunks = [];
|
|
771
|
+
let videoTrack, mediaRecorder, audioStreamDestination, silentAudioSource, audioTapNode;
|
|
772
|
+
try
|
|
773
|
+
{
|
|
774
|
+
// setup captureStream to capture manually by passing 0
|
|
775
|
+
const stream = mainCanvas.captureStream(0);
|
|
776
|
+
videoTrack = stream.getVideoTracks()[0];
|
|
777
|
+
videoTrack.applyConstraints({frameRate:frameRate}).catch(()=>{});
|
|
778
|
+
|
|
779
|
+
// set up the media recorder
|
|
780
|
+
mediaRecorder = new MediaRecorder(stream,
|
|
781
|
+
{mimeType:'video/webm;codecs=vp8'});
|
|
782
|
+
mediaRecorder.ondataavailable = (e)=> chunks.push(e.data);
|
|
783
|
+
mediaRecorder.onstop = ()=>
|
|
784
|
+
{
|
|
785
|
+
const blob = new Blob(chunks, {type: 'video/webm'});
|
|
786
|
+
const url = URL.createObjectURL(blob);
|
|
787
|
+
saveDataURL(url, 'capture.webm', 1e3);
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
if (soundEnable)
|
|
791
|
+
{
|
|
792
|
+
// create silent audio source
|
|
793
|
+
// fixes issue where video can not start recording without audio
|
|
794
|
+
silentAudioSource = new ConstantSourceNode(audioContext, { offset: 0 });
|
|
795
|
+
silentAudioSource.connect(audioMasterGain);
|
|
796
|
+
silentAudioSource.start();
|
|
797
|
+
|
|
798
|
+
// tap the end of the master chain so a master effect is in the recording
|
|
799
|
+
// (a master effect swapped mid-capture drops the tap, the rest records silent)
|
|
800
|
+
audioTapNode = audioMasterEffectOutput || audioMasterGain;
|
|
801
|
+
audioStreamDestination = audioContext.createMediaStreamDestination();
|
|
802
|
+
audioTapNode.connect(audioStreamDestination);
|
|
803
|
+
for (const track of audioStreamDestination.stream.getAudioTracks())
|
|
804
|
+
stream.addTrack(track); // add audio tracks to capture stream
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// start recording
|
|
808
|
+
mediaRecorder.start();
|
|
809
|
+
}
|
|
810
|
+
catch(e)
|
|
811
|
+
{
|
|
812
|
+
LOG('Video capture not supported in this browser!');
|
|
813
|
+
videoTrack?.stop();
|
|
814
|
+
silentAudioSource?.stop();
|
|
815
|
+
if (audioStreamDestination)
|
|
816
|
+
{
|
|
817
|
+
// the tap may not have been made before the failure
|
|
818
|
+
try { audioTapNode.disconnect(audioStreamDestination); }
|
|
819
|
+
catch { }
|
|
820
|
+
}
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (!debugVideoCaptureIcon)
|
|
825
|
+
{
|
|
826
|
+
// create recording icon to show it is capturing video
|
|
827
|
+
debugVideoCaptureIcon = document.createElement('div');
|
|
828
|
+
debugVideoCaptureIcon.style.position = 'absolute';
|
|
829
|
+
debugVideoCaptureIcon.style.padding = '9px';
|
|
830
|
+
debugVideoCaptureIcon.style.color = '#f00';
|
|
831
|
+
debugVideoCaptureIcon.style.font = '50px monospace';
|
|
832
|
+
document.body.appendChild(debugVideoCaptureIcon);
|
|
833
|
+
}
|
|
834
|
+
// show recording icon, only once recording has started
|
|
835
|
+
debugVideoCaptureIcon.textContent = '';
|
|
836
|
+
debugVideoCaptureIcon.style.display = '';
|
|
837
|
+
|
|
838
|
+
LOG('Video capture started.');
|
|
839
|
+
|
|
840
|
+
// save debug video info
|
|
841
|
+
debugVideoCapture =
|
|
842
|
+
{
|
|
843
|
+
mediaRecorder,
|
|
844
|
+
captureTimer,
|
|
845
|
+
videoTrack,
|
|
846
|
+
silentAudioSource,
|
|
847
|
+
audioStreamDestination,
|
|
848
|
+
audioTapNode
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/** Stop capturing video and save to disk
|
|
853
|
+
* @memberof Debug */
|
|
854
|
+
function debugVideoCaptureStop()
|
|
855
|
+
{
|
|
856
|
+
ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
|
|
857
|
+
|
|
858
|
+
// stop recording
|
|
859
|
+
LOG(`Video capture ended. ${debugVideoCapture.captureTimer.get().toFixed(2)} seconds recorded.`);
|
|
860
|
+
debugVideoCaptureIcon.style.display = 'none';
|
|
861
|
+
debugVideoCapture.silentAudioSource?.stop();
|
|
862
|
+
debugVideoCapture.mediaRecorder?.stop();
|
|
863
|
+
debugVideoCapture.videoTrack?.stop();
|
|
864
|
+
if (debugVideoCapture.audioStreamDestination)
|
|
865
|
+
{
|
|
866
|
+
// the tap is already gone if the master effect changed during the capture
|
|
867
|
+
try { debugVideoCapture.audioTapNode.disconnect(debugVideoCapture.audioStreamDestination); }
|
|
868
|
+
catch { }
|
|
869
|
+
}
|
|
870
|
+
debugVideoCapture = undefined;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// update video capture, called automatically by engine
|
|
874
|
+
function debugVideoCaptureUpdate()
|
|
875
|
+
{
|
|
876
|
+
ASSERT(debugVideoCaptureIsActive(), 'Not capturing video!');
|
|
877
|
+
|
|
878
|
+
// save the video frame
|
|
879
|
+
combineCanvases();
|
|
880
|
+
debugVideoCapture.videoTrack.requestFrame();
|
|
881
|
+
debugVideoCaptureIcon.textContent = '● REC '
|
|
882
|
+
+ formatTime(debugVideoCapture.captureTimer.get());
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
886
|
+
// debug utility functions
|
|
887
|
+
|
|
888
|
+
// make color constants immutable with debug assertions
|
|
889
|
+
function debugProtectConstant(obj)
|
|
890
|
+
{
|
|
891
|
+
if (debug)
|
|
892
|
+
{
|
|
893
|
+
// get properties and store original values
|
|
894
|
+
const props = Object.keys(obj), values = {};
|
|
895
|
+
props.forEach(prop => values[prop] = obj[prop]);
|
|
896
|
+
|
|
897
|
+
// replace with getters/setters that assert
|
|
898
|
+
props.forEach(prop =>
|
|
899
|
+
{
|
|
900
|
+
Object.defineProperty(obj, prop, {
|
|
901
|
+
get: ()=> values[prop],
|
|
902
|
+
set: (value)=>
|
|
903
|
+
{
|
|
904
|
+
ASSERT(false, `Cannot modify engine constant. Attempted to set constant (${obj}) property '${prop}' to '${value}'.`);
|
|
905
|
+
},
|
|
906
|
+
enumerable: true
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// freeze the object to prevent adding new properties
|
|
912
|
+
return Object.freeze(obj);
|
|
736
913
|
}
|