littlejsengine 1.19.3 → 1.21.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 -820
- package/README.md +29 -25
- package/dist/littlejs.d.ts +2857 -955
- package/dist/littlejs.esm.js +33428 -20995
- package/dist/littlejs.esm.min.js +4 -3
- package/dist/littlejs.js +33386 -20996
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +27841 -20422
- package/package.json +10 -5
- package/plugins/audioEffects.js +28 -24
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +786 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -332
- package/plugins/math3d.js +148 -27
- package/plugins/medalSystem.js +326 -280
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +25 -0
- package/plugins/postProcess.js +96 -24
- package/plugins/render3d.js +2067 -1632
- package/plugins/render3dDebug.js +110 -0
- package/plugins/render3dEditor.js +1812 -0
- package/plugins/render3dExtras.js +1948 -0
- package/plugins/render3dLevel.js +189 -0
- package/plugins/render3dVoxels.js +514 -0
- package/plugins/sceneSystem.js +58 -0
- package/plugins/textureSheet.js +496 -458
- package/plugins/threejs.js +183 -163
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +265 -145
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +159 -159
- package/src/engine.js +378 -97
- package/src/engineAudio.js +231 -127
- package/src/engineBuild.mjs +292 -256
- package/src/engineDebug.js +967 -743
- package/src/engineDraw.js +391 -198
- package/src/engineEditor.js +2452 -0
- package/src/engineExport.js +18 -0
- package/src/engineInput.js +1466 -1293
- package/src/engineLogo.js +19 -0
- package/src/engineMath.js +1274 -1252
- package/src/engineObject.js +197 -82
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +79 -45
- package/src/engineSettings.js +115 -29
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +19 -13
- package/src/engineWebGL.js +239 -82
- package/src/jsconfig.json +17 -13
- package/plugins/box2d.wasm.js +0 -630
- package/plugins/box2d.wasm.wasm +0 -0
package/plugins/box2d.js
CHANGED
|
@@ -1,2074 +1,2517 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LittleJS Box2D Physics Plugin
|
|
3
|
-
* - Box2dObject extends EngineObject with Box2D physics
|
|
4
|
-
* - Call box2dInit() to enable
|
|
5
|
-
* - You will also need to include box2d.wasm.js
|
|
6
|
-
* - Uses a super fast web assembly port of Box2D v2.3.1
|
|
7
|
-
* - More info: https://github.com/kripken/box2d.js
|
|
8
|
-
* - Functions to create polygon, circle, and edge shapes
|
|
9
|
-
* - Contact begin and end callbacks
|
|
10
|
-
* - Wraps b2Vec2 type to/from Vector2
|
|
11
|
-
* - Raycasting and querying
|
|
12
|
-
* - Box2dTileLayer for grid based collision
|
|
13
|
-
* - Every type of joint
|
|
14
|
-
* - Debug physics drawing
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
* @param {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
* @param {
|
|
287
|
-
* @param {
|
|
288
|
-
* @param {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
* @param {
|
|
304
|
-
* @param {number} [
|
|
305
|
-
* @param {number} [
|
|
306
|
-
* @param {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
ASSERT(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
* @param {
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
* @param {number} [
|
|
333
|
-
* @param {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return
|
|
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
|
-
* @param {number}
|
|
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
|
-
* @param {
|
|
428
|
-
|
|
429
|
-
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
* @param {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
* @param {
|
|
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
|
-
* @param {
|
|
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
|
-
|
|
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
|
-
this.
|
|
572
|
-
this.
|
|
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
|
-
* @param {
|
|
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
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
this.
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
*
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
* @
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
/**
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
* @
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
* @
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
* @param {number}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
///////////////////////////////////////////////////////////////////////////////
|
|
951
|
-
/**
|
|
952
|
-
*
|
|
953
|
-
* -
|
|
954
|
-
*
|
|
955
|
-
* @
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
const
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
/**
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
{
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
* @param {
|
|
1138
|
-
* @
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
/**
|
|
1167
|
-
* @
|
|
1168
|
-
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
*
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
* @
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
* @
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
* @param {number}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
/**
|
|
1252
|
-
* @return {
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
/**
|
|
1256
|
-
* @param {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
/**
|
|
1260
|
-
* @
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
/**
|
|
1264
|
-
* @
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
/**
|
|
1268
|
-
* @
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
* @
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
{
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
* @param {
|
|
1392
|
-
* @param {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
* @return {number} */
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
* @
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
* @param {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
* @
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
/**
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
/**
|
|
1587
|
-
* @
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
/**
|
|
1591
|
-
* @
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
const
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
{
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
}
|
|
1790
|
-
|
|
1791
|
-
/**
|
|
1792
|
-
* @param {
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
* @param {Vector2}
|
|
1814
|
-
* @param {boolean}
|
|
1815
|
-
|
|
1816
|
-
{
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
* @param {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
* @
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
ASSERT(
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
/**
|
|
1900
|
-
* @
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
/**
|
|
1908
|
-
* @
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
/**
|
|
1912
|
-
* @
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
/**
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
{
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
{
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
{
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
const
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
};
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
const
|
|
2061
|
-
const
|
|
2062
|
-
const
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Box2D Physics Plugin
|
|
3
|
+
* - Box2dObject extends EngineObject with Box2D physics
|
|
4
|
+
* - Call box2dInit() to enable
|
|
5
|
+
* - You will also need to include box2d.wasm.js
|
|
6
|
+
* - Uses a super fast web assembly port of Box2D v2.3.1
|
|
7
|
+
* - More info: https://github.com/kripken/box2d.js
|
|
8
|
+
* - Functions to create polygon, circle, and edge shapes
|
|
9
|
+
* - Contact begin and end callbacks
|
|
10
|
+
* - Wraps b2Vec2 type to/from Vector2
|
|
11
|
+
* - Raycasting and querying
|
|
12
|
+
* - Box2dTileLayer for grid based collision
|
|
13
|
+
* - Every type of joint
|
|
14
|
+
* - Debug physics drawing
|
|
15
|
+
* - Box2D works per second: its velocities and accelerations are in units per second, and it reads the engine's
|
|
16
|
+
* gravity as units per second squared, where an EngineObject's are per frame
|
|
17
|
+
* @namespace Box2D
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
/** Global Box2d Plugin object
|
|
23
|
+
* @type {Box2dPlugin}
|
|
24
|
+
* @memberof Box2D */
|
|
25
|
+
let box2d;
|
|
26
|
+
|
|
27
|
+
/** Enable Box2D debug drawing
|
|
28
|
+
* @type {boolean}
|
|
29
|
+
* @default
|
|
30
|
+
* @memberof Box2D */
|
|
31
|
+
let box2dDebug = false;
|
|
32
|
+
|
|
33
|
+
// Box2D copies every vector it is given, so the plugin hands it these two instead of making a new one each call,
|
|
34
|
+
// which the binding would keep; a call that takes two vectors uses both
|
|
35
|
+
let box2dTempVectors;
|
|
36
|
+
function box2dTemp(v, slot=0)
|
|
37
|
+
{
|
|
38
|
+
ASSERT(isVector2(v));
|
|
39
|
+
const temp = (box2dTempVectors ||= [new box2d.instance.b2Vec2(), new box2d.instance.b2Vec2()])[slot];
|
|
40
|
+
temp.Set(v.x, v.y);
|
|
41
|
+
return temp;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// the native objects a query needs, one of each kind made once and reused, since the binding keeps every one made;
|
|
45
|
+
// a query sets the callback's ReportFixture before each use, so the one callback serves every query of its kind
|
|
46
|
+
const box2dQueryObjects = {};
|
|
47
|
+
const box2dGravity = {x:NaN, y:NaN}; // the gravity the world was last given
|
|
48
|
+
function box2dQueryObject(key, type) { return box2dQueryObjects[key] ||= new box2d.instance[type](); }
|
|
49
|
+
|
|
50
|
+
// Box2D finds fixtures by boxes it pads and stretches ahead along the velocity, so a query checks the shape's own box
|
|
51
|
+
function box2dFixtureOverlaps(fixture, aabb)
|
|
52
|
+
{
|
|
53
|
+
const shape = fixture.GetShape(), transform = fixture.GetBody().GetTransform();
|
|
54
|
+
const shapeBox = box2dQueryObject('shapeAABB', 'b2AABB');
|
|
55
|
+
const lower = aabb.get_lowerBound(), upper = aabb.get_upperBound();
|
|
56
|
+
for (let i = shape.GetChildCount(); i--;)
|
|
57
|
+
{
|
|
58
|
+
shape.ComputeAABB(shapeBox, transform, i);
|
|
59
|
+
const a = shapeBox.get_lowerBound(), b = shapeBox.get_upperBound();
|
|
60
|
+
if (a.get_x() <= upper.get_x() && b.get_x() >= lower.get_x() &&
|
|
61
|
+
a.get_y() <= upper.get_y() && b.get_y() >= lower.get_y())
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// the points of an edge list or loop, without one that repeats the one before, or a loop's closing repeat of the first;
|
|
68
|
+
// an edge of no length gives its neighbors a ghost vertex on their own end, and bodies fall through them
|
|
69
|
+
function box2dEdgePoints(points, loop)
|
|
70
|
+
{
|
|
71
|
+
// each point is kept if it is far enough from the last one kept, so a densely sampled curve keeps every few of
|
|
72
|
+
// its points rather than losing them all
|
|
73
|
+
const slop2 = .005**2; // Box2D's linear slop, what its own chain shape asserts consecutive points are apart
|
|
74
|
+
const kept = [];
|
|
75
|
+
for (const p of points)
|
|
76
|
+
if (!kept.length || p.distanceSquared(kept[kept.length-1]) > slop2)
|
|
77
|
+
kept.push(p);
|
|
78
|
+
while (loop && kept.length > 1 && kept[kept.length-1].distanceSquared(kept[0]) <= slop2)
|
|
79
|
+
kept.pop();
|
|
80
|
+
return kept;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// wake a body and whatever touches it, Box2D does not when a body is moved, and a sleeping pair never updates
|
|
84
|
+
function box2dWakeWithContacts(body)
|
|
85
|
+
{
|
|
86
|
+
body.SetAwake(true);
|
|
87
|
+
for (let edge = body.GetContactList(); !box2d.isNull(edge); edge = edge.get_next())
|
|
88
|
+
edge.get_other().SetAwake(true);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// wake both bodies of a joint whose length, spring, ratio or strength changed, a sleeping body would stay where it was
|
|
92
|
+
function box2dWakeJoint(joint)
|
|
93
|
+
{
|
|
94
|
+
joint.GetBodyA().SetAwake(true);
|
|
95
|
+
joint.GetBodyB().SetAwake(true);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// what Box2D adds to the inertia for a center of mass away from the origin, in float32 as it does it, so taking it
|
|
99
|
+
// off again gives exactly what Box2D keeps: an inertia of 0, a locked rotation, stays 0 and not a speck either side
|
|
100
|
+
function box2dCenterInertia(mass, x, y)
|
|
101
|
+
{
|
|
102
|
+
const f = Math.fround;
|
|
103
|
+
return f(f(mass)*f(f(f(x)*f(x)) + f(f(y)*f(y))));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// what cannot happen while the world steps, like losing a body from a contact callback: done now, or queued until
|
|
107
|
+
// the step is done; a body going calls endContact for what it touched, and a destroy from there waits in the queue
|
|
108
|
+
// too, since the contact it would free is still in use, so the queue runs one at a time in the order things came
|
|
109
|
+
const box2dPending = [];
|
|
110
|
+
let box2dPendingBusy = 0;
|
|
111
|
+
function box2dWhenUnlocked(f)
|
|
112
|
+
{
|
|
113
|
+
box2dPending.push(f);
|
|
114
|
+
if (!box2d.world.IsLocked() && !box2dPendingBusy)
|
|
115
|
+
box2dRunPending();
|
|
116
|
+
}
|
|
117
|
+
function box2dRunPending()
|
|
118
|
+
{
|
|
119
|
+
++box2dPendingBusy;
|
|
120
|
+
try { while (box2dPending.length) box2dPending.shift()(); }
|
|
121
|
+
finally { --box2dPendingBusy; }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// each Box2dJoint by its native pointer, so a joint Box2D destroys along with a body can let go of its wrapper
|
|
125
|
+
const box2dJoints = new Map;
|
|
126
|
+
|
|
127
|
+
// the gear joints, kept apart from the others, since every joint that goes looks through them
|
|
128
|
+
const box2dGearJoints = new Set;
|
|
129
|
+
|
|
130
|
+
// a gear joint keeps pointers to the joints it gears and to their bodies, so it goes before either joint does
|
|
131
|
+
function box2dDestroyGears(joint)
|
|
132
|
+
{
|
|
133
|
+
for (const gear of box2dGearJoints)
|
|
134
|
+
{
|
|
135
|
+
if (!gear.box2dJoint)
|
|
136
|
+
box2dGearJoints.delete(gear); // gone already, with a body
|
|
137
|
+
else if (gear.joint1 === joint || gear.joint2 === joint)
|
|
138
|
+
{
|
|
139
|
+
box2dGearJoints.delete(gear);
|
|
140
|
+
gear.destroy();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Enable Box2D debug drawing
|
|
146
|
+
* @param {boolean} enable
|
|
147
|
+
* @memberof Box2D */
|
|
148
|
+
function box2dSetDebug(enable) { box2dDebug = enable; }
|
|
149
|
+
|
|
150
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
151
|
+
/**
|
|
152
|
+
* Box2D Object - extend with your own custom physics objects
|
|
153
|
+
* - A LittleJS object with Box2D physics, dynamic by default
|
|
154
|
+
* - Provides interface for Box2D body and fixture functions
|
|
155
|
+
* - Each object can have multiple fixtures and joints
|
|
156
|
+
* - Angular values are clockwise like angle: angular velocity, torque, joint angles, limits and motor speeds
|
|
157
|
+
* @extends EngineObject
|
|
158
|
+
* @memberof Box2D
|
|
159
|
+
*/
|
|
160
|
+
class Box2dObject extends EngineObject
|
|
161
|
+
{
|
|
162
|
+
/** Create a LittleJS object with Box2d physics
|
|
163
|
+
* @param {Vector2} [pos]
|
|
164
|
+
* @param {Vector2} [size]
|
|
165
|
+
* @param {TileInfo} [tileInfo]
|
|
166
|
+
* @param {number} [angle]
|
|
167
|
+
* @param {Color} [color]
|
|
168
|
+
* @param {number} [bodyType]
|
|
169
|
+
* @param {number} [renderOrder] */
|
|
170
|
+
constructor(pos=vec2(), size=vec2(), tileInfo, angle=0, color, bodyType=box2d.bodyTypeDynamic, renderOrder=0)
|
|
171
|
+
{
|
|
172
|
+
ASSERT(!box2d.world.IsLocked(), 'cannot create Box2D bodies during a contact callback');
|
|
173
|
+
super(pos, size, tileInfo, angle, color, renderOrder);
|
|
174
|
+
|
|
175
|
+
// create physics body, Box2D copies the def
|
|
176
|
+
const bodyDef = new box2d.instance.b2BodyDef();
|
|
177
|
+
bodyDef.set_type(bodyType);
|
|
178
|
+
bodyDef.set_position(box2dTemp(pos));
|
|
179
|
+
bodyDef.set_angle(-angle);
|
|
180
|
+
|
|
181
|
+
/** @property {Object} - The Box2d body, undefined once it is destroyed */
|
|
182
|
+
this.body = box2d.world.CreateBody(bodyDef);
|
|
183
|
+
box2d.instance.destroy(bodyDef);
|
|
184
|
+
/** @property {Color} - Line color used for default box2d drawing */
|
|
185
|
+
this.lineColor = BLACK.copy();
|
|
186
|
+
/** @property {number} - Line width used for default box2d drawing */
|
|
187
|
+
this.lineWidth = .1;
|
|
188
|
+
/** @property {Array<Array<Vector2>>} - List of all edges for default box2d drawing
|
|
189
|
+
* @type {Array<Array<Vector2>>} */
|
|
190
|
+
this.edgeLists = [];
|
|
191
|
+
/** @property {Array<Array<Vector2>>} - List of all edge loops for default box2d drawing
|
|
192
|
+
* @type {Array<Array<Vector2>>} */
|
|
193
|
+
this.edgeLoops = [];
|
|
194
|
+
// the fixtures of the edge lists and loops, by pointer, each to the points it is drawn with
|
|
195
|
+
this.edgeListFixtures = new Map;
|
|
196
|
+
|
|
197
|
+
this.body.object = this; // link body to this object
|
|
198
|
+
box2d.objects.push(this); // keep track of all box2d objects
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Destroy this object and its physics body
|
|
202
|
+
* @param {boolean} [immediate] - Remove it now, as EngineObject.destroy does, children included */
|
|
203
|
+
destroy(immediate=false)
|
|
204
|
+
{
|
|
205
|
+
if (this.destroyed) return;
|
|
206
|
+
|
|
207
|
+
// destroy physics body, fixtures, and joints; from a contact callback the world is still
|
|
208
|
+
// stepping and cannot lose a body, so it goes as soon as the step is done; the object
|
|
209
|
+
// leaves box2d.objects at the next step, or the next frame while paused or time is stopped;
|
|
210
|
+
// the body lets go of it after, since destroying it calls endContact, which finds it there;
|
|
211
|
+
// that endContact can destroy this same object again, before it is marked destroyed, so the body is
|
|
212
|
+
// destroyed only by the call that still finds it, Box2D would lose count of its bodies on a second
|
|
213
|
+
ASSERT(this.body, 'Box2dObject has no body to destroy');
|
|
214
|
+
const body = this.body;
|
|
215
|
+
box2dWhenUnlocked(()=>
|
|
216
|
+
{
|
|
217
|
+
if (this.body !== body) return; // destroyed already
|
|
218
|
+
box2d.world.DestroyBody(body);
|
|
219
|
+
body.object = undefined;
|
|
220
|
+
this.body = undefined;
|
|
221
|
+
});
|
|
222
|
+
super.destroy(immediate);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Box2d objects updated with Box2d world step */
|
|
226
|
+
updatePhysics() {}
|
|
227
|
+
|
|
228
|
+
/** Update the object transform, called automatically by engine even when paused;
|
|
229
|
+
* its body places it, so it can be a parent but not a child, connect it to another with a joint
|
|
230
|
+
* @param {boolean} [updateChildren] - Also update the children's transforms */
|
|
231
|
+
updateTransforms(updateChildren=true)
|
|
232
|
+
{
|
|
233
|
+
ASSERT(!this.parent, 'a Box2dObject cannot be a child, its body would stay behind, connect it with a joint');
|
|
234
|
+
super.updateTransforms(updateChildren);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Render the object, uses box2d drawing if no tile info exists */
|
|
238
|
+
render()
|
|
239
|
+
{
|
|
240
|
+
// use default render or draw fixtures
|
|
241
|
+
if (this.tileInfo)
|
|
242
|
+
super.render();
|
|
243
|
+
else
|
|
244
|
+
this.drawFixtures(this.color, this.lineColor, this.lineWidth);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Render debug info */
|
|
248
|
+
renderDebugInfo()
|
|
249
|
+
{
|
|
250
|
+
const isAsleep = !this.getIsAwake();
|
|
251
|
+
const isStatic = this.getBodyType() === box2d.bodyTypeStatic;
|
|
252
|
+
const color = rgb(isAsleep?1:0, isAsleep?1:0, isStatic?1:0, .5);
|
|
253
|
+
this.drawFixtures(color);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Draws all this object's fixtures
|
|
257
|
+
* @param {Color} [color]
|
|
258
|
+
* @param {Color} [lineColor]
|
|
259
|
+
* @param {number} [lineWidth]
|
|
260
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
261
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
262
|
+
drawFixtures(color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
263
|
+
{
|
|
264
|
+
// draw each fixture, but the edges of an edge list or loop, which draw below as one line
|
|
265
|
+
const edgeFixtures = this.edgeListFixtures;
|
|
266
|
+
this.getFixtureList().forEach((fixture)=>
|
|
267
|
+
{
|
|
268
|
+
if (!edgeFixtures.has(box2d.instance.getPointer(fixture)))
|
|
269
|
+
box2d.drawFixture(fixture, this.pos, this.angle, color, lineColor, lineWidth, useWebGL, context);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// draw edges using a single draw line for better connections
|
|
273
|
+
this.edgeLists.forEach(points=>
|
|
274
|
+
drawLineList(points, lineWidth, lineColor, false, this.pos, this.angle, useWebGL, false, context));
|
|
275
|
+
this.edgeLoops.forEach(points=>
|
|
276
|
+
drawLineList(points, lineWidth, lineColor, true, this.pos, this.angle, useWebGL, false, context));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
280
|
+
// physics contact callbacks
|
|
281
|
+
|
|
282
|
+
/** Called when a contact begins, while the world steps: a destroy or a setter waits until the step is done,
|
|
283
|
+
* and creating objects, fixtures or joints must wait until after the step
|
|
284
|
+
* - The fixtures say which shapes touched, the same objects addBox and the others returned, so a small sensor
|
|
285
|
+
* under a player's feet can tell standing on the ground from touching a wall
|
|
286
|
+
* @param {Box2dObject} otherObject
|
|
287
|
+
* @param {Object} [fixture] - This object's fixture that touched
|
|
288
|
+
* @param {Object} [otherFixture] - The other object's fixture that touched */
|
|
289
|
+
beginContact(otherObject, fixture, otherFixture) {}
|
|
290
|
+
|
|
291
|
+
/** Called when a contact ends, while the world steps or a body is destroyed: a destroy or a setter waits
|
|
292
|
+
* until the step is done, and creating objects, fixtures or joints must wait until after the step
|
|
293
|
+
* @param {Box2dObject} otherObject
|
|
294
|
+
* @param {Object} [fixture] - This object's fixture that touched
|
|
295
|
+
* @param {Object} [otherFixture] - The other object's fixture that touched */
|
|
296
|
+
endContact(otherObject, fixture, otherFixture) {}
|
|
297
|
+
|
|
298
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
299
|
+
// physics fixtures and shapes
|
|
300
|
+
|
|
301
|
+
/** Add a shape fixture to the body
|
|
302
|
+
* @param {Object} shape
|
|
303
|
+
* @param {number} [density]
|
|
304
|
+
* @param {number} [friction]
|
|
305
|
+
* @param {number} [restitution]
|
|
306
|
+
* @param {boolean} [isSensor] */
|
|
307
|
+
addShape(shape, density=1, friction=.2, restitution=0, isSensor=false)
|
|
308
|
+
{
|
|
309
|
+
ASSERT(isNumber(density), 'density must be a number');
|
|
310
|
+
ASSERT(isNumber(friction), 'friction must be a number');
|
|
311
|
+
ASSERT(isNumber(restitution), 'restitution must be a number');
|
|
312
|
+
ASSERT(!box2d.world.IsLocked(), 'cannot create Box2D fixtures during a contact callback');
|
|
313
|
+
|
|
314
|
+
// Box2D copies the def and the shape
|
|
315
|
+
const fd = new box2d.instance.b2FixtureDef();
|
|
316
|
+
fd.set_shape(shape);
|
|
317
|
+
fd.set_density(density);
|
|
318
|
+
fd.set_friction(friction);
|
|
319
|
+
fd.set_restitution(restitution);
|
|
320
|
+
fd.set_isSensor(isSensor);
|
|
321
|
+
const fixture = this.body.CreateFixture(fd);
|
|
322
|
+
box2d.instance.destroy(fd);
|
|
323
|
+
return fixture;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Add a box shape to the body
|
|
327
|
+
* @param {Vector2} [size]
|
|
328
|
+
* @param {Vector2} [offset]
|
|
329
|
+
* @param {number} [angle] - LittleJS convention (clockwise positive).
|
|
330
|
+
* Negated internally to match Box2D's CCW-positive convention so the
|
|
331
|
+
* fixture aligns with the same angle passed to drawRect/drawTile.
|
|
332
|
+
* @param {number} [density]
|
|
333
|
+
* @param {number} [friction]
|
|
334
|
+
* @param {number} [restitution]
|
|
335
|
+
* @param {boolean} [isSensor] */
|
|
336
|
+
addBox(size=vec2(1), offset=vec2(), angle=0, density, friction, restitution, isSensor)
|
|
337
|
+
{
|
|
338
|
+
ASSERT(isVector2(size), 'size must be a Vector2');
|
|
339
|
+
ASSERT(size.x > 0 && size.y > 0, 'size must be positive');
|
|
340
|
+
ASSERT(isVector2(offset), 'offset must be a Vector2');
|
|
341
|
+
ASSERT(isNumber(angle), 'angle must be a number');
|
|
342
|
+
|
|
343
|
+
// Box2D stops for good on a box with almost no area, like addPoly no fixture is made from one
|
|
344
|
+
ASSERT(size.x * size.y > 1e-6, 'box is too small for Box2D');
|
|
345
|
+
if (!(size.x * size.y > 1e-6)) return;
|
|
346
|
+
|
|
347
|
+
const shape = new box2d.instance.b2PolygonShape();
|
|
348
|
+
shape.SetAsBox(size.x/2, size.y/2, box2dTemp(offset), -angle);
|
|
349
|
+
const fixture = this.addShape(shape, density, friction, restitution, isSensor);
|
|
350
|
+
box2d.instance.destroy(shape); // the fixture has its own copy
|
|
351
|
+
return fixture;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Add a polygon shape to the body, the convex hull of its points; Box2D takes 3 to 8 points,
|
|
355
|
+
* not all in a line, and no fixture is made from any other; points closer than .001 count as one
|
|
356
|
+
* @param {Array<Vector2>} points
|
|
357
|
+
* @param {number} [density]
|
|
358
|
+
* @param {number} [friction]
|
|
359
|
+
* @param {number} [restitution]
|
|
360
|
+
* @param {boolean} [isSensor] */
|
|
361
|
+
addPoly(points, density, friction, restitution, isSensor)
|
|
362
|
+
{
|
|
363
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
364
|
+
|
|
365
|
+
function box2dCreatePolygonShape(points)
|
|
366
|
+
{
|
|
367
|
+
// Box2D stops for good on two hull points that nearly meet, like a loop whose last point is its first
|
|
368
|
+
points = points.filter((p, i)=> points.slice(0, i).every(q=> p.distanceSquared(q) > 1e-6));
|
|
369
|
+
|
|
370
|
+
// Box2D stops for good on a polygon it cannot take, so one with too many points or no area makes none;
|
|
371
|
+
// it takes the convex hull, which is at least as big as the biggest triangle of the points
|
|
372
|
+
let area = 0; // twice the biggest triangle's
|
|
373
|
+
if (3 <= points.length && points.length <= 8)
|
|
374
|
+
for (const a of points) for (const b of points) for (const c of points)
|
|
375
|
+
area = max(area, abs(b.subtract(a).cross(c.subtract(a))));
|
|
376
|
+
ASSERT(area >= 1e-6, 'Box2D polygons need 3 to 8 points, not all in a line');
|
|
377
|
+
if (!(area >= 1e-6)) return;
|
|
378
|
+
|
|
379
|
+
const buffer = box2d.instance._malloc(points.length * 8);
|
|
380
|
+
for (let i=0, offset=0; i<points.length; ++i)
|
|
381
|
+
{
|
|
382
|
+
box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].x;
|
|
383
|
+
offset += 4;
|
|
384
|
+
box2d.instance.HEAPF32[buffer + offset >> 2] = points[i].y;
|
|
385
|
+
offset += 4;
|
|
386
|
+
}
|
|
387
|
+
const box2dPoints = box2d.instance.wrapPointer(buffer, box2d.instance.b2Vec2);
|
|
388
|
+
const shape = new box2d.instance.b2PolygonShape();
|
|
389
|
+
shape.Set(box2dPoints, points.length);
|
|
390
|
+
box2d.instance._free(buffer);
|
|
391
|
+
return shape;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const shape = box2dCreatePolygonShape(points);
|
|
395
|
+
if (!shape) return;
|
|
396
|
+
const fixture = this.addShape(shape, density, friction, restitution, isSensor);
|
|
397
|
+
box2d.instance.destroy(shape); // the fixture has its own copy
|
|
398
|
+
return fixture;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Add a regular polygon shape to the body
|
|
402
|
+
* @param {number} [diameter]
|
|
403
|
+
* @param {number} [sides] - 3 to 8, the most Box2D polygons have
|
|
404
|
+
* @param {number} [density]
|
|
405
|
+
* @param {number} [friction]
|
|
406
|
+
* @param {number} [restitution]
|
|
407
|
+
* @param {boolean} [isSensor] */
|
|
408
|
+
addRegularPoly(diameter=1, sides=8, density, friction, restitution, isSensor)
|
|
409
|
+
{
|
|
410
|
+
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
411
|
+
ASSERT(isNumber(sides) && sides>2, 'sides must be a positive number greater than 2');
|
|
412
|
+
ASSERT(sides <= 8, 'Box2D polygons have at most 8 sides');
|
|
413
|
+
sides = min(sides, 8); // more would stop Box2D for good
|
|
414
|
+
|
|
415
|
+
const points = [];
|
|
416
|
+
const radius = diameter/2;
|
|
417
|
+
for (let i=sides; i--;)
|
|
418
|
+
points.push(vec2(radius,0).rotate((i+.5)/sides*PI*2));
|
|
419
|
+
return this.addPoly(points, density, friction, restitution, isSensor);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Add a random polygon shape to the body
|
|
423
|
+
* @param {number} [diameter]
|
|
424
|
+
* @param {number} [density]
|
|
425
|
+
* @param {number} [friction]
|
|
426
|
+
* @param {number} [restitution]
|
|
427
|
+
* @param {boolean} [isSensor] */
|
|
428
|
+
addRandomPoly(diameter=1, density, friction, restitution, isSensor)
|
|
429
|
+
{
|
|
430
|
+
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
431
|
+
|
|
432
|
+
const sides = randInt(3, 9);
|
|
433
|
+
const points = [];
|
|
434
|
+
const radius = diameter/2;
|
|
435
|
+
for (let i=sides; i--;)
|
|
436
|
+
points.push(vec2(rand(radius/2,radius*1.5),0).rotate(i/sides*PI*2));
|
|
437
|
+
return this.addPoly(points, density, friction, restitution, isSensor);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/** Add a circle shape to the body
|
|
441
|
+
* @param {number} [diameter]
|
|
442
|
+
* @param {Vector2} [offset]
|
|
443
|
+
* @param {number} [density]
|
|
444
|
+
* @param {number} [friction]
|
|
445
|
+
* @param {number} [restitution]
|
|
446
|
+
* @param {boolean} [isSensor] */
|
|
447
|
+
addCircle(diameter=1, offset=vec2(), density, friction, restitution, isSensor)
|
|
448
|
+
{
|
|
449
|
+
ASSERT(isNumber(diameter) && diameter>0, 'diameter must be a positive number');
|
|
450
|
+
ASSERT(isVector2(offset), 'offset must be a Vector2');
|
|
451
|
+
|
|
452
|
+
const shape = new box2d.instance.b2CircleShape();
|
|
453
|
+
shape.set_m_p(box2dTemp(offset));
|
|
454
|
+
shape.set_m_radius(diameter/2);
|
|
455
|
+
const fixture = this.addShape(shape, density, friction, restitution, isSensor);
|
|
456
|
+
box2d.instance.destroy(shape); // the fixture has its own copy
|
|
457
|
+
return fixture;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Add an edge shape to the body
|
|
461
|
+
* @param {Vector2} point1
|
|
462
|
+
* @param {Vector2} point2
|
|
463
|
+
* @param {number} [density]
|
|
464
|
+
* @param {number} [friction]
|
|
465
|
+
* @param {number} [restitution]
|
|
466
|
+
* @param {boolean} [isSensor] */
|
|
467
|
+
addEdge(point1, point2, density, friction, restitution, isSensor)
|
|
468
|
+
{
|
|
469
|
+
ASSERT(isVector2(point1), 'point1 must be a Vector2');
|
|
470
|
+
ASSERT(isVector2(point2), 'point2 must be a Vector2');
|
|
471
|
+
|
|
472
|
+
const shape = new box2d.instance.b2EdgeShape();
|
|
473
|
+
shape.Set(box2dTemp(point1), box2dTemp(point2, 1));
|
|
474
|
+
const fixture = this.addShape(shape, density, friction, restitution, isSensor);
|
|
475
|
+
box2d.instance.destroy(shape); // the fixture has its own copy
|
|
476
|
+
return fixture;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Add an edge list to the body
|
|
480
|
+
* @param {Array<Vector2>} points
|
|
481
|
+
* @param {number} [density]
|
|
482
|
+
* @param {number} [friction]
|
|
483
|
+
* @param {number} [restitution]
|
|
484
|
+
* @param {boolean} [isSensor] */
|
|
485
|
+
addEdgeList(points, density, friction, restitution, isSensor)
|
|
486
|
+
{
|
|
487
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
488
|
+
points = box2dEdgePoints(points);
|
|
489
|
+
const fixtures = [], edgePoints = [];
|
|
490
|
+
if (points.length < 2) return fixtures;
|
|
491
|
+
for (let i=0; i<points.length-1; ++i)
|
|
492
|
+
{
|
|
493
|
+
// the ghost vertices, where there is a neighbor, make the edges one smooth surface
|
|
494
|
+
const shape = new box2d.instance.b2EdgeShape();
|
|
495
|
+
points[i-1] && shape.set_m_vertex0(box2dTemp(points[i-1]));
|
|
496
|
+
points[i+0] && shape.set_m_vertex1(box2dTemp(points[i+0]));
|
|
497
|
+
points[i+1] && shape.set_m_vertex2(box2dTemp(points[i+1]));
|
|
498
|
+
points[i+2] && shape.set_m_vertex3(box2dTemp(points[i+2]));
|
|
499
|
+
shape.set_m_hasVertex0(!!points[i-1]);
|
|
500
|
+
shape.set_m_hasVertex3(!!points[i+2]);
|
|
501
|
+
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
502
|
+
box2d.instance.destroy(shape); // the fixture has its own copy
|
|
503
|
+
fixtures.push(f);
|
|
504
|
+
edgePoints.push(points[i].copy());
|
|
505
|
+
}
|
|
506
|
+
edgePoints.push(points[points.length-1].copy());
|
|
507
|
+
this.edgeLists.push(edgePoints);
|
|
508
|
+
fixtures.forEach(f=> this.edgeListFixtures.set(box2d.instance.getPointer(f), edgePoints));
|
|
509
|
+
return fixtures;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/** Add an edge loop to the body, an edge loop connects the end points
|
|
513
|
+
* @param {Array<Vector2>} points
|
|
514
|
+
* @param {number} [density]
|
|
515
|
+
* @param {number} [friction]
|
|
516
|
+
* @param {number} [restitution]
|
|
517
|
+
* @param {boolean} [isSensor] */
|
|
518
|
+
addEdgeLoop(points, density, friction, restitution, isSensor)
|
|
519
|
+
{
|
|
520
|
+
ASSERT(isArray(points), 'points must be an array');
|
|
521
|
+
points = box2dEdgePoints(points, true);
|
|
522
|
+
const fixtures = [], edgePoints = [];
|
|
523
|
+
if (points.length < 2) return fixtures;
|
|
524
|
+
const getPoint = i=> points[mod(i,points.length)];
|
|
525
|
+
for (let i=0; i<points.length; ++i)
|
|
526
|
+
{
|
|
527
|
+
const shape = new box2d.instance.b2EdgeShape();
|
|
528
|
+
shape.set_m_vertex0(box2dTemp(getPoint(i-1)));
|
|
529
|
+
shape.set_m_vertex1(box2dTemp(getPoint(i+0)));
|
|
530
|
+
shape.set_m_vertex2(box2dTemp(getPoint(i+1)));
|
|
531
|
+
shape.set_m_vertex3(box2dTemp(getPoint(i+2)));
|
|
532
|
+
shape.set_m_hasVertex0(true);
|
|
533
|
+
shape.set_m_hasVertex3(true);
|
|
534
|
+
const f = this.addShape(shape, density, friction, restitution, isSensor);
|
|
535
|
+
box2d.instance.destroy(shape); // the fixture has its own copy
|
|
536
|
+
fixtures.push(f);
|
|
537
|
+
edgePoints.push(points[i].copy());
|
|
538
|
+
}
|
|
539
|
+
this.edgeLoops.push(edgePoints);
|
|
540
|
+
fixtures.forEach(f=> this.edgeListFixtures.set(box2d.instance.getPointer(f), edgePoints));
|
|
541
|
+
return fixtures;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** Destroy a fixture from the body, from a contact callback once the step is done
|
|
545
|
+
* @param {Object} fixture */
|
|
546
|
+
destroyFixture(fixture)
|
|
547
|
+
{
|
|
548
|
+
const pointer = box2d.instance.getPointer(fixture);
|
|
549
|
+
|
|
550
|
+
// an edge list or loop that loses a fixture is no longer one line, what is left of it draws edge by edge
|
|
551
|
+
const edgeFixtures = this.edgeListFixtures, points = edgeFixtures.get(pointer);
|
|
552
|
+
if (points)
|
|
553
|
+
{
|
|
554
|
+
this.edgeLists = this.edgeLists.filter(p=> p !== points);
|
|
555
|
+
this.edgeLoops = this.edgeLoops.filter(p=> p !== points);
|
|
556
|
+
edgeFixtures.forEach((p, pointer)=> p === points && edgeFixtures.delete(pointer));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// not once the body is gone, which takes its fixtures with it, or once the fixture is,
|
|
560
|
+
// since a second destroy of it, like one from each of two contacts in a step, stops Box2D for good
|
|
561
|
+
box2dWhenUnlocked(()=> this.body && this.getFixtureList().some(f=> box2d.instance.getPointer(f) === pointer)
|
|
562
|
+
&& this.body.DestroyFixture(fixture));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** Destroy all fixtures from the body, from a contact callback once the step is done */
|
|
566
|
+
destroyAllFixtures()
|
|
567
|
+
{
|
|
568
|
+
// the fixtures it has now, each destroyed if still there, in one pass so a big tile layer rebuilds quickly
|
|
569
|
+
this.edgeLists = [];
|
|
570
|
+
this.edgeLoops = [];
|
|
571
|
+
this.edgeListFixtures.clear();
|
|
572
|
+
const fixtures = this.getFixtureList(), getPointer = box2d.instance.getPointer;
|
|
573
|
+
box2dWhenUnlocked(()=>
|
|
574
|
+
{
|
|
575
|
+
if (!this.body) return;
|
|
576
|
+
const alive = new Set(this.getFixtureList().map(getPointer));
|
|
577
|
+
for (const fixture of fixtures)
|
|
578
|
+
alive.has(getPointer(fixture)) && this.body.DestroyFixture(fixture);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
583
|
+
// physics get functions
|
|
584
|
+
|
|
585
|
+
/** Gets the center of mass in world space
|
|
586
|
+
* @return {Vector2} */
|
|
587
|
+
getCenterOfMass() { return box2d.vec2From(this.body.GetWorldCenter()); }
|
|
588
|
+
|
|
589
|
+
/** Gets the linear velocity
|
|
590
|
+
* @return {Vector2} */
|
|
591
|
+
getLinearVelocity() { return box2d.vec2From(this.body.GetLinearVelocity()); }
|
|
592
|
+
|
|
593
|
+
/** Gets the angular velocity, clockwise like angle
|
|
594
|
+
* @return {number} */
|
|
595
|
+
getAngularVelocity() { return -this.body.GetAngularVelocity(); } // box2d uses reverse angle
|
|
596
|
+
|
|
597
|
+
/** Gets the mass
|
|
598
|
+
* @return {number} */
|
|
599
|
+
getMass() { return this.body.GetMass(); }
|
|
600
|
+
|
|
601
|
+
/** Gets the rotational inertia about the center of mass
|
|
602
|
+
* @return {number} */
|
|
603
|
+
getInertia()
|
|
604
|
+
{
|
|
605
|
+
// Box2D gives it about the body origin
|
|
606
|
+
const center = this.body.GetLocalCenter();
|
|
607
|
+
return max(0, Math.fround(this.body.GetInertia() - box2dCenterInertia(this.getMass(), center.get_x(), center.get_y())));
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/** Check if this object is awake
|
|
611
|
+
* @return {boolean} */
|
|
612
|
+
getIsAwake() { return this.body.IsAwake(); }
|
|
613
|
+
|
|
614
|
+
/** Gets the physics body type
|
|
615
|
+
* @return {number} */
|
|
616
|
+
getBodyType() { return this.body.GetType(); }
|
|
617
|
+
|
|
618
|
+
/** Get the speed of this object
|
|
619
|
+
* @return {number} */
|
|
620
|
+
getSpeed() { return this.getLinearVelocity().length(); }
|
|
621
|
+
|
|
622
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
623
|
+
// physics set functions
|
|
624
|
+
|
|
625
|
+
/** Sets the position and angle, from a contact callback the body moves once the step is done
|
|
626
|
+
* @param {Vector2} pos
|
|
627
|
+
* @param {number} angle */
|
|
628
|
+
setTransform(pos, angle)
|
|
629
|
+
{
|
|
630
|
+
this.pos = pos.copy();
|
|
631
|
+
this.angle = angle;
|
|
632
|
+
// box2d uses reverse angle
|
|
633
|
+
const x = pos.x, y = pos.y;
|
|
634
|
+
box2dWhenUnlocked(()=>
|
|
635
|
+
{
|
|
636
|
+
if (!this.body) return;
|
|
637
|
+
this.body.SetTransform(box2dTemp(vec2(x, y)), -angle);
|
|
638
|
+
box2dWakeWithContacts(this.body); // Box2D leaves a sleeping body, and what rests on it, in the air
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Sets the position, from a contact callback the body moves once the step is done, keeping the angle it has then
|
|
643
|
+
* @param {Vector2} pos */
|
|
644
|
+
setPosition(pos)
|
|
645
|
+
{
|
|
646
|
+
this.pos = pos.copy();
|
|
647
|
+
const x = pos.x, y = pos.y;
|
|
648
|
+
box2dWhenUnlocked(()=>
|
|
649
|
+
{
|
|
650
|
+
if (!this.body) return;
|
|
651
|
+
this.body.SetTransform(box2dTemp(vec2(x, y)), this.body.GetAngle());
|
|
652
|
+
box2dWakeWithContacts(this.body);
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** Sets the angle, from a contact callback the body turns once the step is done, keeping the position it has then
|
|
657
|
+
* @param {number} angle */
|
|
658
|
+
setAngle(angle)
|
|
659
|
+
{
|
|
660
|
+
this.angle = angle;
|
|
661
|
+
box2dWhenUnlocked(()=>
|
|
662
|
+
{
|
|
663
|
+
if (!this.body) return;
|
|
664
|
+
this.body.SetTransform(this.body.GetPosition(), -angle); // box2d uses reverse angle
|
|
665
|
+
box2dWakeWithContacts(this.body);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** Sets the linear velocity
|
|
670
|
+
* @param {Vector2} velocity */
|
|
671
|
+
setLinearVelocity(velocity)
|
|
672
|
+
{ this.body.SetLinearVelocity(box2dTemp(velocity)); }
|
|
673
|
+
|
|
674
|
+
/** Sets the angular velocity, clockwise like angle
|
|
675
|
+
* @param {number} angularVelocity */
|
|
676
|
+
setAngularVelocity(angularVelocity)
|
|
677
|
+
{ this.body.SetAngularVelocity(-angularVelocity); }
|
|
678
|
+
|
|
679
|
+
/** Sets the linear damping
|
|
680
|
+
* @param {number} damping */
|
|
681
|
+
setLinearDamping(damping)
|
|
682
|
+
{ this.body.SetLinearDamping(damping); }
|
|
683
|
+
|
|
684
|
+
/** Sets the angular damping
|
|
685
|
+
* @param {number} damping */
|
|
686
|
+
setAngularDamping(damping)
|
|
687
|
+
{ this.body.SetAngularDamping(damping); }
|
|
688
|
+
|
|
689
|
+
/** Sets the gravity scale
|
|
690
|
+
* @param {number} [scale] */
|
|
691
|
+
setGravityScale(scale=1)
|
|
692
|
+
{
|
|
693
|
+
this.body.SetGravityScale(this.gravityScale = scale);
|
|
694
|
+
this.body.SetAwake(true); // a sleeping body would not feel it
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/** Should be like a bullet for continuous collision detection?
|
|
698
|
+
* @param {boolean} [isBullet] */
|
|
699
|
+
setBullet(isBullet=true) { this.body.SetBullet(isBullet); }
|
|
700
|
+
|
|
701
|
+
/** Set the sleep state of the body
|
|
702
|
+
* @param {boolean} [isAwake] */
|
|
703
|
+
setAwake(isAwake=true) { this.body.SetAwake(isAwake); }
|
|
704
|
+
|
|
705
|
+
/** Set the physics body type, from a contact callback it changes once the step is done
|
|
706
|
+
* @param {number} type */
|
|
707
|
+
setBodyType(type) { box2dWhenUnlocked(()=> this.body && this.body.SetType(type)); }
|
|
708
|
+
|
|
709
|
+
/** Set whether the body is allowed to sleep
|
|
710
|
+
* @param {boolean} [isAllowed] */
|
|
711
|
+
setSleepingAllowed(isAllowed=true)
|
|
712
|
+
{ this.body.SetSleepingAllowed(isAllowed); }
|
|
713
|
+
|
|
714
|
+
/** Set whether the body can rotate
|
|
715
|
+
* @param {boolean} [isFixed] */
|
|
716
|
+
setFixedRotation(isFixed=true)
|
|
717
|
+
{
|
|
718
|
+
this.body.SetFixedRotation(isFixed);
|
|
719
|
+
this.body.SetAwake(true); // a sleeping body would not tip over once it can turn
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/** Set the center of mass of the body, local to it
|
|
723
|
+
* @param {Vector2} center */
|
|
724
|
+
setCenterOfMass(center) { this.setMassData(center) }
|
|
725
|
+
|
|
726
|
+
/** Set the mass of the body
|
|
727
|
+
* @param {number} mass */
|
|
728
|
+
setMass(mass) { this.setMassData(undefined, mass) }
|
|
729
|
+
|
|
730
|
+
/** Set the moment of inertia of the body, about its center of mass
|
|
731
|
+
* @param {number} momentOfInertia */
|
|
732
|
+
setMomentOfInertia(momentOfInertia)
|
|
733
|
+
{ this.setMassData(undefined, undefined, momentOfInertia) }
|
|
734
|
+
|
|
735
|
+
/** Reset the mass, center of mass, and moment, from a contact callback once the step is done */
|
|
736
|
+
resetMassData() { box2dWhenUnlocked(()=> this.body && this.body.ResetMassData()); }
|
|
737
|
+
|
|
738
|
+
/** Set the mass data of the body, from a contact callback once the step is done;
|
|
739
|
+
* a mass of 0 or less becomes 1, use setBodyType for a static body; call it after adding fixtures and after
|
|
740
|
+
* setFixedRotation, both of which put the mass back to what the fixtures give
|
|
741
|
+
* @param {Vector2} [localCenter]
|
|
742
|
+
* @param {number} [mass]
|
|
743
|
+
* @param {number} [momentOfInertia] - About the center of mass */
|
|
744
|
+
setMassData(localCenter, mass, momentOfInertia)
|
|
745
|
+
{
|
|
746
|
+
localCenter = localCenter && localCenter.copy(); // as it is now, even if it waits for the step
|
|
747
|
+
box2dWhenUnlocked(()=>
|
|
748
|
+
{
|
|
749
|
+
if (!this.body) return;
|
|
750
|
+
const data = box2dQueryObject('massData', 'b2MassData'); // reused, GetMassData fills it in
|
|
751
|
+
this.body.GetMassData(data);
|
|
752
|
+
|
|
753
|
+
// Box2D's inertia is about the body origin, so it is turned to the center of mass and back; kept as
|
|
754
|
+
// it was, one for the old mass and center goes below 0 about the new ones, which stops Box2D for good
|
|
755
|
+
const center = data.get_center(), oldMass = data.get_mass();
|
|
756
|
+
const cx = localCenter ? localCenter.x : center.get_x(), cy = localCenter ? localCenter.y : center.get_y();
|
|
757
|
+
// it is worked out in float32 as Box2D does it, which must come out above 0 or the rotation is locked
|
|
758
|
+
const f = Math.fround;
|
|
759
|
+
const oldInertia = f(data.get_I() - box2dCenterInertia(oldMass, center.get_x(), center.get_y()));
|
|
760
|
+
const inertia = momentOfInertia ?? oldInertia;
|
|
761
|
+
mass ??= oldMass;
|
|
762
|
+
const offset = box2dCenterInertia(mass > 0 ? mass : 1, cx, cy); // a mass of 0 or less is 1 to Box2D
|
|
763
|
+
const I = f(inertia + offset);
|
|
764
|
+
data.set_mass(mass);
|
|
765
|
+
data.set_center(box2dTemp(vec2(cx, cy)));
|
|
766
|
+
data.set_I(inertia > 0 && f(I - offset) > 0 ? I : 0);
|
|
767
|
+
this.body.SetMassData(data);
|
|
768
|
+
this.body.SetAwake(true); // a sleeping body would not tip over a new center of mass
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/** Set the collision filter data for the fixtures this body has now, a fixture added later has the default
|
|
773
|
+
* filter, category 1 colliding with everything
|
|
774
|
+
* @param {number} [categoryBits]
|
|
775
|
+
* @param {number} [ignoreCategoryBits]
|
|
776
|
+
* @param {number} [groupIndex] */
|
|
777
|
+
setFilterData(categoryBits=1, ignoreCategoryBits=0, groupIndex=0)
|
|
778
|
+
{
|
|
779
|
+
this.getFixtureList().forEach(fixture=>
|
|
780
|
+
{
|
|
781
|
+
const filter = fixture.GetFilterData();
|
|
782
|
+
filter.set_categoryBits(categoryBits);
|
|
783
|
+
filter.set_maskBits(0xffff & ~ignoreCategoryBits);
|
|
784
|
+
filter.set_groupIndex(groupIndex);
|
|
785
|
+
fixture.SetFilterData(filter); // applies and refilters contacts
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/** Set if this body is a sensor
|
|
790
|
+
* @param {boolean} [isSensor] */
|
|
791
|
+
setSensor(isSensor=true)
|
|
792
|
+
{
|
|
793
|
+
this.getFixtureList().forEach(f=>f.SetSensor(isSensor));
|
|
794
|
+
box2dWakeWithContacts(this.body); // what rests on it or sits in it moves again, a sleeping pair never updates
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
798
|
+
// physics force and torque functions
|
|
799
|
+
|
|
800
|
+
/** Apply force to this object
|
|
801
|
+
* @param {Vector2} force
|
|
802
|
+
* @param {Vector2} [pos] */
|
|
803
|
+
applyForce(force, pos)
|
|
804
|
+
{
|
|
805
|
+
pos ||= this.getCenterOfMass();
|
|
806
|
+
this.setAwake();
|
|
807
|
+
this.body.ApplyForce(box2dTemp(force), box2dTemp(pos, 1));
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/** Apply acceleration to this object (changes velocity by acceleration,
|
|
811
|
+
* mass-independent like EngineObject.applyAcceleration, but in units per second).
|
|
812
|
+
* Use applyImpulse if you want the mass-dependent velocity change
|
|
813
|
+
* Δv = impulse / mass, or applyForce for a Newton-style sustained force.
|
|
814
|
+
* @param {Vector2} acceleration
|
|
815
|
+
* @param {Vector2} [pos] */
|
|
816
|
+
applyAcceleration(acceleration, pos)
|
|
817
|
+
{
|
|
818
|
+
pos ||= this.getCenterOfMass();
|
|
819
|
+
this.setAwake();
|
|
820
|
+
const impulse = acceleration.scale(this.getMass());
|
|
821
|
+
this.body.ApplyLinearImpulse(box2dTemp(impulse), box2dTemp(pos, 1));
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/** Apply an instantaneous linear impulse. Changes velocity immediately by
|
|
825
|
+
* impulse / mass (so heavier bodies move less for the same impulse).
|
|
826
|
+
* @param {Vector2} impulse
|
|
827
|
+
* @param {Vector2} [pos] */
|
|
828
|
+
applyImpulse(impulse, pos)
|
|
829
|
+
{
|
|
830
|
+
pos ||= this.getCenterOfMass();
|
|
831
|
+
this.setAwake();
|
|
832
|
+
this.body.ApplyLinearImpulse(box2dTemp(impulse), box2dTemp(pos, 1));
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
/** Apply torque to this object, clockwise like angle
|
|
836
|
+
* @param {number} torque */
|
|
837
|
+
applyTorque(torque)
|
|
838
|
+
{
|
|
839
|
+
this.setAwake();
|
|
840
|
+
this.body.ApplyTorque(-torque);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/** Apply angular acceleration to this object (changes angular velocity by
|
|
844
|
+
* acceleration, mass-independent, clockwise — matches EngineObject.applyAngularAcceleration).
|
|
845
|
+
* @param {number} acceleration */
|
|
846
|
+
applyAngularAcceleration(acceleration)
|
|
847
|
+
{
|
|
848
|
+
// the velocity itself, since the inertia Box2D gives is about the origin, not the center of mass;
|
|
849
|
+
// like the impulse this was, only a dynamic body turns
|
|
850
|
+
if (this.getBodyType() !== box2d.bodyTypeDynamic || this.body.IsFixedRotation()) return;
|
|
851
|
+
this.setAwake();
|
|
852
|
+
this.setAngularVelocity(this.getAngularVelocity() + acceleration);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/** Apply an instantaneous angular impulse. Changes angular velocity by
|
|
856
|
+
* impulse / inertia immediately, clockwise like angle.
|
|
857
|
+
* @param {number} impulse */
|
|
858
|
+
applyAngularImpulse(impulse)
|
|
859
|
+
{
|
|
860
|
+
this.setAwake();
|
|
861
|
+
this.body.ApplyAngularImpulse(-impulse);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
865
|
+
// lists of fixtures and joints
|
|
866
|
+
|
|
867
|
+
/** Check if this object has any fixtures
|
|
868
|
+
* @return {boolean} */
|
|
869
|
+
hasFixtures() { return !box2d.isNull(this.body.GetFixtureList()); }
|
|
870
|
+
|
|
871
|
+
/** Get list of fixtures for this object
|
|
872
|
+
* @return {Array<Object>} */
|
|
873
|
+
getFixtureList()
|
|
874
|
+
{
|
|
875
|
+
const fixtures = [];
|
|
876
|
+
for (let fixture=this.body.GetFixtureList(); !box2d.isNull(fixture); )
|
|
877
|
+
{
|
|
878
|
+
fixtures.push(fixture);
|
|
879
|
+
fixture = fixture.GetNext();
|
|
880
|
+
}
|
|
881
|
+
return fixtures;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/** Check if this object has any joints
|
|
885
|
+
* @return {boolean} */
|
|
886
|
+
hasJoints() { return this.getJointList().length > 0; } // not the ones destroyed and waiting for the step to end
|
|
887
|
+
|
|
888
|
+
/** Get list of joints for this object, the Box2dJoint for each one made through LittleJS,
|
|
889
|
+
* and the Box2D joint, cast to its type, for any made on the world directly
|
|
890
|
+
* @return {Array<Box2dJoint|Object>} */
|
|
891
|
+
getJointList()
|
|
892
|
+
{
|
|
893
|
+
// the body keeps a list of edges, each holding a joint and the next edge
|
|
894
|
+
const joints = [];
|
|
895
|
+
for (let edge=this.body.GetJointList(); !box2d.isNull(edge); edge = edge.get_next())
|
|
896
|
+
{
|
|
897
|
+
const joint = edge.get_joint(), wrapper = box2dJoints.get(box2d.instance.getPointer(joint));
|
|
898
|
+
if (wrapper && !wrapper.box2dJoint)
|
|
899
|
+
continue; // destroyed in a contact callback, Box2D lets go of it once the step is done
|
|
900
|
+
joints.push(wrapper || box2d.castJointObject(joint));
|
|
901
|
+
}
|
|
902
|
+
return joints;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
907
|
+
/**
|
|
908
|
+
* Box2D Static Object - Box2d with a static physics body
|
|
909
|
+
* @extends Box2dObject
|
|
910
|
+
* @memberof Box2D
|
|
911
|
+
*/
|
|
912
|
+
class Box2dStaticObject extends Box2dObject
|
|
913
|
+
{
|
|
914
|
+
/** Create a LittleJS object with Box2d physics
|
|
915
|
+
* @param {Vector2} [pos]
|
|
916
|
+
* @param {Vector2} [size]
|
|
917
|
+
* @param {TileInfo} [tileInfo]
|
|
918
|
+
* @param {number} [angle]
|
|
919
|
+
* @param {Color} [color]
|
|
920
|
+
* @param {number} [renderOrder] */
|
|
921
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
922
|
+
{
|
|
923
|
+
const bodyType = box2d.bodyTypeStatic;
|
|
924
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
929
|
+
/**
|
|
930
|
+
* Box2D Kinematic Object - Box2d with a kinematic physics body
|
|
931
|
+
* @extends Box2dObject
|
|
932
|
+
* @memberof Box2D
|
|
933
|
+
*/
|
|
934
|
+
class Box2dKinematicObject extends Box2dObject
|
|
935
|
+
{
|
|
936
|
+
/** Create a LittleJS object with Box2d physics
|
|
937
|
+
* @param {Vector2} [pos]
|
|
938
|
+
* @param {Vector2} [size]
|
|
939
|
+
* @param {TileInfo} [tileInfo]
|
|
940
|
+
* @param {number} [angle]
|
|
941
|
+
* @param {Color} [color]
|
|
942
|
+
* @param {number} [renderOrder] */
|
|
943
|
+
constructor(pos, size, tileInfo, angle=0, color, renderOrder=0)
|
|
944
|
+
{
|
|
945
|
+
const bodyType = box2d.bodyTypeKinematic;
|
|
946
|
+
super(pos, size, tileInfo, angle, color, bodyType, renderOrder);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
951
|
+
/**
|
|
952
|
+
* Box2d Tile Layer
|
|
953
|
+
* - adds Box2d support to tile layers
|
|
954
|
+
* - creates static box2d fixtures for solid tiles, call buildCollision to rebuild them after the tiles change
|
|
955
|
+
* @extends Box2dStaticObject
|
|
956
|
+
* @memberof Box2D
|
|
957
|
+
*/
|
|
958
|
+
class Box2dTileLayer extends Box2dStaticObject
|
|
959
|
+
{
|
|
960
|
+
/** Create a Box2d tile layer object
|
|
961
|
+
* @param {TileCollisionLayer} tileLayer - Tile layer for this object */
|
|
962
|
+
constructor(tileLayer)
|
|
963
|
+
{
|
|
964
|
+
ASSERT(tileLayer instanceof TileCollisionLayer, 'tileLayer must be a TileCollisionLayer');
|
|
965
|
+
super(tileLayer.pos, tileLayer.size);
|
|
966
|
+
|
|
967
|
+
/** @property {TileCollisionLayer} - The tile layer */
|
|
968
|
+
this.tileLayer = tileLayer;
|
|
969
|
+
this.addChild(tileLayer);
|
|
970
|
+
|
|
971
|
+
// collision for the solid tiles it has now, call buildCollision again after changing them
|
|
972
|
+
this.buildCollision();
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
render()
|
|
976
|
+
{
|
|
977
|
+
// do not render fixtures, tile layer handles rendering
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/** Create box2d collision fixtures for solid tiles
|
|
981
|
+
* @param {number} [friction]
|
|
982
|
+
* @param {number} [restitution] */
|
|
983
|
+
buildCollision(friction=.2, restitution=0)
|
|
984
|
+
{
|
|
985
|
+
// destroy all fixtures and create new ones
|
|
986
|
+
this.destroyAllFixtures();
|
|
987
|
+
|
|
988
|
+
// track which tiles have been processed
|
|
989
|
+
const processed = [];
|
|
990
|
+
const getIndex = (x, y)=> x + y * this.size.x;
|
|
991
|
+
const isSolidUnprocessed = (x, y)=>
|
|
992
|
+
!processed[getIndex(x, y)] &&
|
|
993
|
+
this.tileLayer.getCollisionData(vec2(x, y)) > 0;
|
|
994
|
+
|
|
995
|
+
// combine tiles into larger boxes
|
|
996
|
+
for (let x = 0; x < this.size.x; ++x)
|
|
997
|
+
for (let y = 0; y < this.size.y; ++y)
|
|
998
|
+
{
|
|
999
|
+
if (!isSolidUnprocessed(x, y)) continue;
|
|
1000
|
+
|
|
1001
|
+
// find max width by scanning right
|
|
1002
|
+
let width = 1, height = 1, canExpand = true;
|
|
1003
|
+
while (isSolidUnprocessed(x + width, y))
|
|
1004
|
+
++width;
|
|
1005
|
+
|
|
1006
|
+
// find max height by scanning up, ensuring all rows have the same width
|
|
1007
|
+
while (canExpand)
|
|
1008
|
+
{
|
|
1009
|
+
for (let checkX = 0; checkX < width; ++checkX)
|
|
1010
|
+
{
|
|
1011
|
+
if (!isSolidUnprocessed(x + checkX, y + height))
|
|
1012
|
+
{
|
|
1013
|
+
canExpand = false;
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
if (canExpand)
|
|
1018
|
+
++height;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// mark all tiles in this rectangle as processed
|
|
1022
|
+
for (let rectX = width; rectX--;)
|
|
1023
|
+
for (let rectY = height; rectY--;)
|
|
1024
|
+
processed[getIndex(x + rectX, y + rectY)] = true;
|
|
1025
|
+
|
|
1026
|
+
// create a single fixture for the entire rectangle
|
|
1027
|
+
const shapeSize = vec2(width, height);
|
|
1028
|
+
const offset = vec2(x + width/2, y + height/2);
|
|
1029
|
+
this.addBox(shapeSize, offset, 0, 0, friction, restitution);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1035
|
+
/**
|
|
1036
|
+
* Box2D Raycast Result
|
|
1037
|
+
* - Holds results from a box2d raycast queries
|
|
1038
|
+
* - Automatically created by box2d raycast functions
|
|
1039
|
+
* @memberof Box2D
|
|
1040
|
+
*/
|
|
1041
|
+
class Box2dRaycastResult
|
|
1042
|
+
{
|
|
1043
|
+
/** Create a raycast result
|
|
1044
|
+
* @param {Object} fixture
|
|
1045
|
+
* @param {Vector2} point
|
|
1046
|
+
* @param {Vector2} normal
|
|
1047
|
+
* @param {number} fraction */
|
|
1048
|
+
constructor(fixture, point, normal, fraction)
|
|
1049
|
+
{
|
|
1050
|
+
/** @property {Box2dObject} - The box2d object
|
|
1051
|
+
* @type {Box2dObject} */
|
|
1052
|
+
this.object = fixture.GetBody().object;
|
|
1053
|
+
/** @property {Object} - The fixture that was hit */
|
|
1054
|
+
this.fixture = fixture;
|
|
1055
|
+
/** @property {Vector2} - The hit point */
|
|
1056
|
+
this.point = point;
|
|
1057
|
+
/** @property {Vector2} - The hit normal */
|
|
1058
|
+
this.normal = normal;
|
|
1059
|
+
/** @property {number} - Distance fraction at the point of intersection */
|
|
1060
|
+
this.fraction = fraction;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1065
|
+
/**
|
|
1066
|
+
* Box2D Joint
|
|
1067
|
+
* - Base class for Box2D joints
|
|
1068
|
+
* - A joint is used to connect objects together
|
|
1069
|
+
* - Angular values are clockwise like angle: joint angles and speeds, limits, motor speeds and torques
|
|
1070
|
+
* @memberof Box2D
|
|
1071
|
+
*/
|
|
1072
|
+
class Box2dJoint
|
|
1073
|
+
{
|
|
1074
|
+
/** Create a box2d joint, the base class is not intended to be used directly
|
|
1075
|
+
* @param {Object} jointDef - Freed once the joint is made, Box2D copies it */
|
|
1076
|
+
constructor(jointDef)
|
|
1077
|
+
{
|
|
1078
|
+
ASSERT(!box2d.world.IsLocked(), 'cannot create Box2D joints during a contact callback');
|
|
1079
|
+
const bodyA = jointDef.get_bodyA(), bodyB = jointDef.get_bodyB();
|
|
1080
|
+
const bothLive = !box2d.isNull(bodyA) && !box2d.isNull(bodyB);
|
|
1081
|
+
ASSERT(bothLive, 'a joint needs two objects that are not destroyed');
|
|
1082
|
+
ASSERT(box2d.instance.getPointer(bodyA) !== box2d.instance.getPointer(bodyB), 'a joint needs two different objects');
|
|
1083
|
+
|
|
1084
|
+
/** @property {Object} - The Box2d joint, 0 once it is destroyed, as it is when either object is */
|
|
1085
|
+
this.box2dJoint = 0;
|
|
1086
|
+
if (!bothLive)
|
|
1087
|
+
{
|
|
1088
|
+
// one of its objects is gone, so it is made destroyed, as it would be had it gone after
|
|
1089
|
+
box2d.instance.destroy(jointDef);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
this.box2dJoint = box2d.castJointObject(box2d.world.CreateJoint(jointDef));
|
|
1093
|
+
box2d.instance.destroy(jointDef);
|
|
1094
|
+
box2dJoints.set(box2d.instance.getPointer(this.box2dJoint), this);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/** Destroy this joint */
|
|
1098
|
+
destroy()
|
|
1099
|
+
{
|
|
1100
|
+
const joint = this.box2dJoint;
|
|
1101
|
+
if (!joint) return; // destroyed already, or with one of its objects
|
|
1102
|
+
this.box2dJoint = 0;
|
|
1103
|
+
box2dDestroyGears(this);
|
|
1104
|
+
|
|
1105
|
+
// a body destroyed before it, in the same step, takes the joint with it and lets go of it here
|
|
1106
|
+
const pointer = box2d.instance.getPointer(joint);
|
|
1107
|
+
box2dWhenUnlocked(()=>
|
|
1108
|
+
{
|
|
1109
|
+
if (box2dJoints.get(pointer) !== this) return;
|
|
1110
|
+
box2dJoints.delete(pointer);
|
|
1111
|
+
box2d.world.DestroyJoint(joint);
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/** Get the first object attached to this joint
|
|
1116
|
+
* @return {Box2dObject} */
|
|
1117
|
+
getObjectA() { return this.box2dJoint.GetBodyA().object; }
|
|
1118
|
+
|
|
1119
|
+
/** Get the second object attached to this joint
|
|
1120
|
+
* @return {Box2dObject} */
|
|
1121
|
+
getObjectB() { return this.box2dJoint.GetBodyB().object; }
|
|
1122
|
+
|
|
1123
|
+
/** Get the first anchor for this joint in world coordinates
|
|
1124
|
+
* @return {Vector2} */
|
|
1125
|
+
getAnchorA() { return box2d.vec2From(this.box2dJoint.GetAnchorA());}
|
|
1126
|
+
|
|
1127
|
+
/** Get the second anchor for this joint in world coordinates
|
|
1128
|
+
* @return {Vector2} */
|
|
1129
|
+
getAnchorB() { return box2d.vec2From(this.box2dJoint.GetAnchorB());}
|
|
1130
|
+
|
|
1131
|
+
/** Get the reaction force on bodyB at the joint anchor over the last step
|
|
1132
|
+
* @param {number} [time] - The step length in seconds, the world steps by timeDelta
|
|
1133
|
+
* @return {Vector2} */
|
|
1134
|
+
getReactionForce(time=timeDelta) { return box2d.vec2From(this.box2dJoint.GetReactionForce(1/time));}
|
|
1135
|
+
|
|
1136
|
+
/** Get the reaction torque on bodyB in N*m over the last step, clockwise like angle
|
|
1137
|
+
* @param {number} [time] - The step length in seconds, the world steps by timeDelta
|
|
1138
|
+
* @return {number} */
|
|
1139
|
+
getReactionTorque(time=timeDelta) { return -this.box2dJoint.GetReactionTorque(1/time);} // box2d uses reverse angle
|
|
1140
|
+
|
|
1141
|
+
/** Check if the connected bodies should collide
|
|
1142
|
+
* @return {boolean} */
|
|
1143
|
+
getCollideConnected() { return this.box2dJoint.GetCollideConnected();}
|
|
1144
|
+
|
|
1145
|
+
/** Check if either connected body is active
|
|
1146
|
+
* @return {boolean} */
|
|
1147
|
+
isActive() { return !!this.box2dJoint && this.box2dJoint.IsActive(); }
|
|
1148
|
+
|
|
1149
|
+
/** Check if the joint is gone, destroyed or taken along with one of its objects; its other methods can not be used then
|
|
1150
|
+
* @return {boolean} */
|
|
1151
|
+
isDestroyed() { return !this.box2dJoint; }
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1155
|
+
/**
|
|
1156
|
+
* Box2D Target Joint, also known as a mouse joint
|
|
1157
|
+
* - Used to make a point on a object track a specific world point target
|
|
1158
|
+
* - This a soft constraint with a max force
|
|
1159
|
+
* - This allows the constraint to stretch and without applying huge forces
|
|
1160
|
+
* - The object must be dynamic, and stay dynamic while the joint holds it, Box2D stops for good on one with no mass
|
|
1161
|
+
* @extends Box2dJoint
|
|
1162
|
+
* @memberof Box2D
|
|
1163
|
+
*/
|
|
1164
|
+
class Box2dTargetJoint extends Box2dJoint
|
|
1165
|
+
{
|
|
1166
|
+
/** Create a target joint
|
|
1167
|
+
* @param {Box2dObject} object
|
|
1168
|
+
* @param {Box2dObject} fixedObject
|
|
1169
|
+
* @param {Vector2} worldPos */
|
|
1170
|
+
constructor(object, fixedObject, worldPos)
|
|
1171
|
+
{
|
|
1172
|
+
ASSERT(object.getBodyType() === box2d.bodyTypeDynamic, 'a target joint needs a dynamic object');
|
|
1173
|
+
object.setAwake();
|
|
1174
|
+
const jointDef = new box2d.instance.b2MouseJointDef();
|
|
1175
|
+
jointDef.set_bodyA(fixedObject.body);
|
|
1176
|
+
jointDef.set_bodyB(object.body);
|
|
1177
|
+
jointDef.set_target(box2dTemp(worldPos));
|
|
1178
|
+
jointDef.set_maxForce(2e3 * object.getMass());
|
|
1179
|
+
super(jointDef);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
/** Set the target point in world coordinates
|
|
1183
|
+
* @param {Vector2} pos */
|
|
1184
|
+
setTarget(pos) { this.box2dJoint.SetTarget(box2dTemp(pos)); }
|
|
1185
|
+
|
|
1186
|
+
/** Get the target point in world coordinates
|
|
1187
|
+
* @return {Vector2} */
|
|
1188
|
+
getTarget(){ return box2d.vec2From(this.box2dJoint.GetTarget()); }
|
|
1189
|
+
|
|
1190
|
+
/** Sets the maximum force in Newtons
|
|
1191
|
+
* @param {number} force */
|
|
1192
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(force); box2dWakeJoint(this.box2dJoint); }
|
|
1193
|
+
|
|
1194
|
+
/** Gets the maximum force in Newtons
|
|
1195
|
+
* @return {number} */
|
|
1196
|
+
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
|
|
1197
|
+
|
|
1198
|
+
/** Sets the joint frequency in Hertz, above 0, Box2D stops for good on 0
|
|
1199
|
+
* @param {number} hz */
|
|
1200
|
+
setFrequency(hz) { this.box2dJoint.SetFrequency(max(hz, 1e-3)); box2dWakeJoint(this.box2dJoint); }
|
|
1201
|
+
|
|
1202
|
+
/** Gets the joint frequency in Hertz
|
|
1203
|
+
* @return {number} */
|
|
1204
|
+
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1208
|
+
/**
|
|
1209
|
+
* Box2D Distance Joint
|
|
1210
|
+
* - Constrains two points on two objects to remain at a fixed distance
|
|
1211
|
+
* - You can view this as a massless, rigid rod
|
|
1212
|
+
* @extends Box2dJoint
|
|
1213
|
+
* @memberof Box2D
|
|
1214
|
+
*/
|
|
1215
|
+
class Box2dDistanceJoint extends Box2dJoint
|
|
1216
|
+
{
|
|
1217
|
+
/** Create a distance joint
|
|
1218
|
+
* @param {Box2dObject} objectA
|
|
1219
|
+
* @param {Box2dObject} objectB
|
|
1220
|
+
* @param {Vector2} [anchorA] - World position, objectA's position if not given
|
|
1221
|
+
* @param {Vector2} [anchorB] - World position, objectB's position if not given
|
|
1222
|
+
* @param {boolean} [collide] */
|
|
1223
|
+
constructor(objectA, objectB, anchorA, anchorB, collide=false)
|
|
1224
|
+
{
|
|
1225
|
+
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
|
|
1226
|
+
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1227
|
+
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
1228
|
+
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
1229
|
+
const jointDef = new box2d.instance.b2DistanceJointDef();
|
|
1230
|
+
jointDef.set_bodyA(objectA.body);
|
|
1231
|
+
jointDef.set_bodyB(objectB.body);
|
|
1232
|
+
jointDef.set_localAnchorA(box2dTemp(localAnchorA));
|
|
1233
|
+
jointDef.set_localAnchorB(box2dTemp(localAnchorB));
|
|
1234
|
+
jointDef.set_length(anchorA.distance(anchorB));
|
|
1235
|
+
jointDef.set_collideConnected(collide);
|
|
1236
|
+
super(jointDef);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/** Get the local anchor point relative to objectA's origin
|
|
1240
|
+
* @return {Vector2} */
|
|
1241
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
1242
|
+
|
|
1243
|
+
/** Get the local anchor point relative to objectB's origin
|
|
1244
|
+
* @return {Vector2} */
|
|
1245
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
1246
|
+
|
|
1247
|
+
/** Set the length of the joint
|
|
1248
|
+
* @param {number} length */
|
|
1249
|
+
setLength(length) { this.box2dJoint.SetLength(length); box2dWakeJoint(this.box2dJoint); }
|
|
1250
|
+
|
|
1251
|
+
/** Get the length of the joint
|
|
1252
|
+
* @return {number} */
|
|
1253
|
+
getLength() { return this.box2dJoint.GetLength(); }
|
|
1254
|
+
|
|
1255
|
+
/** Set the frequency in Hertz
|
|
1256
|
+
* @param {number} hz */
|
|
1257
|
+
setFrequency(hz) { this.box2dJoint.SetFrequency(hz); box2dWakeJoint(this.box2dJoint); }
|
|
1258
|
+
|
|
1259
|
+
/** Get the frequency in Hertz
|
|
1260
|
+
* @return {number} */
|
|
1261
|
+
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
1262
|
+
|
|
1263
|
+
/** Set the damping ratio
|
|
1264
|
+
* @param {number} ratio */
|
|
1265
|
+
setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); box2dWakeJoint(this.box2dJoint); }
|
|
1266
|
+
|
|
1267
|
+
/** Get the damping ratio
|
|
1268
|
+
* @return {number} */
|
|
1269
|
+
getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1273
|
+
/**
|
|
1274
|
+
* Box2D Rope Joint
|
|
1275
|
+
* - Enforces a maximum distance between two points on two objects
|
|
1276
|
+
* @extends Box2dJoint
|
|
1277
|
+
* @memberof Box2D
|
|
1278
|
+
*/
|
|
1279
|
+
class Box2dRopeJoint extends Box2dJoint
|
|
1280
|
+
{
|
|
1281
|
+
/** Create a rope joint
|
|
1282
|
+
* @param {Box2dObject} objectA
|
|
1283
|
+
* @param {Box2dObject} objectB
|
|
1284
|
+
* @param {Vector2} [anchorA] - World position, objectA's position if not given
|
|
1285
|
+
* @param {Vector2} [anchorB] - World position, objectB's position if not given
|
|
1286
|
+
* @param {number} [extraLength]
|
|
1287
|
+
* @param {boolean} [collide] */
|
|
1288
|
+
constructor(objectA, objectB, anchorA, anchorB, extraLength=0, collide=false)
|
|
1289
|
+
{
|
|
1290
|
+
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
|
|
1291
|
+
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1292
|
+
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
1293
|
+
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
1294
|
+
const jointDef = new box2d.instance.b2RopeJointDef();
|
|
1295
|
+
jointDef.set_bodyA(objectA.body);
|
|
1296
|
+
jointDef.set_bodyB(objectB.body);
|
|
1297
|
+
jointDef.set_localAnchorA(box2dTemp(localAnchorA));
|
|
1298
|
+
jointDef.set_localAnchorB(box2dTemp(localAnchorB));
|
|
1299
|
+
jointDef.set_maxLength(anchorA.distance(anchorB)+extraLength);
|
|
1300
|
+
jointDef.set_collideConnected(collide);
|
|
1301
|
+
super(jointDef);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/** Get the local anchor point relative to objectA's origin
|
|
1305
|
+
* @return {Vector2} */
|
|
1306
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
1307
|
+
|
|
1308
|
+
/** Get the local anchor point relative to objectB's origin
|
|
1309
|
+
* @return {Vector2} */
|
|
1310
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
1311
|
+
|
|
1312
|
+
/** Set the max length of the joint
|
|
1313
|
+
* @param {number} length */
|
|
1314
|
+
setMaxLength(length) { this.box2dJoint.SetMaxLength(length); box2dWakeJoint(this.box2dJoint); }
|
|
1315
|
+
|
|
1316
|
+
/** Get the max length of the joint
|
|
1317
|
+
* @return {number} */
|
|
1318
|
+
getMaxLength() { return this.box2dJoint.GetMaxLength(); }
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1322
|
+
/**
|
|
1323
|
+
* Box2D Revolute Joint
|
|
1324
|
+
* - Constrains two objects to share a point while they are free to rotate around the point
|
|
1325
|
+
* - The relative rotation about the shared point is the joint angle
|
|
1326
|
+
* - You can limit the relative rotation with a joint limit
|
|
1327
|
+
* - You can use a motor to drive the relative rotation about the shared point
|
|
1328
|
+
* - A maximum motor torque is provided so that infinite forces are not generated
|
|
1329
|
+
* @extends Box2dJoint
|
|
1330
|
+
* @memberof Box2D
|
|
1331
|
+
*/
|
|
1332
|
+
class Box2dRevoluteJoint extends Box2dJoint
|
|
1333
|
+
{
|
|
1334
|
+
/** Create a revolute joint
|
|
1335
|
+
* @param {Box2dObject} objectA
|
|
1336
|
+
* @param {Box2dObject} objectB
|
|
1337
|
+
* @param {Vector2} [anchor] - World position, objectB's position if not given
|
|
1338
|
+
* @param {boolean} [collide] */
|
|
1339
|
+
constructor(objectA, objectB, anchor, collide=false)
|
|
1340
|
+
{
|
|
1341
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1342
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
1343
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
1344
|
+
const jointDef = new box2d.instance.b2RevoluteJointDef();
|
|
1345
|
+
jointDef.set_bodyA(objectA.body);
|
|
1346
|
+
jointDef.set_bodyB(objectB.body);
|
|
1347
|
+
jointDef.set_localAnchorA(box2dTemp(localAnchorA));
|
|
1348
|
+
jointDef.set_localAnchorB(box2dTemp(localAnchorB));
|
|
1349
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
1350
|
+
jointDef.set_collideConnected(collide);
|
|
1351
|
+
super(jointDef);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
/** Get the local anchor point relative to objectA's origin
|
|
1355
|
+
* @return {Vector2} */
|
|
1356
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
1357
|
+
|
|
1358
|
+
/** Get the local anchor point relative to objectB's origin
|
|
1359
|
+
* @return {Vector2} */
|
|
1360
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
1361
|
+
|
|
1362
|
+
/** Get the reference angle, objectB angle minus objectA angle in the reference state
|
|
1363
|
+
* @return {number} */
|
|
1364
|
+
getReferenceAngle() { return -this.box2dJoint.GetReferenceAngle(); } // box2d uses reverse angle
|
|
1365
|
+
|
|
1366
|
+
/** Get the current joint angle, clockwise like angle
|
|
1367
|
+
* @return {number} */
|
|
1368
|
+
getJointAngle() { return -this.box2dJoint.GetJointAngle(); }
|
|
1369
|
+
|
|
1370
|
+
/** Get the current joint angle speed in radians per second, clockwise like angle
|
|
1371
|
+
* @return {number} */
|
|
1372
|
+
getJointSpeed() { return -this.box2dJoint.GetJointSpeed(); }
|
|
1373
|
+
|
|
1374
|
+
/** Is the joint limit enabled?
|
|
1375
|
+
* @return {boolean} */
|
|
1376
|
+
isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
|
|
1377
|
+
|
|
1378
|
+
/** Enable/disable the joint limit
|
|
1379
|
+
* @param {boolean} [enable] */
|
|
1380
|
+
enableLimit(enable=true) { this.box2dJoint.EnableLimit(enable); }
|
|
1381
|
+
|
|
1382
|
+
/** Get the lower joint limit, clockwise like angle
|
|
1383
|
+
* @return {number} */
|
|
1384
|
+
getLowerLimit() { return -this.box2dJoint.GetUpperLimit(); } // reversed, so Box2D's upper is the lower
|
|
1385
|
+
|
|
1386
|
+
/** Get the upper joint limit, clockwise like angle
|
|
1387
|
+
* @return {number} */
|
|
1388
|
+
getUpperLimit() { return -this.box2dJoint.GetLowerLimit(); }
|
|
1389
|
+
|
|
1390
|
+
/** Set the joint limits, clockwise like angle
|
|
1391
|
+
* @param {number} min
|
|
1392
|
+
* @param {number} max */
|
|
1393
|
+
setLimits(min, max)
|
|
1394
|
+
{
|
|
1395
|
+
ASSERT(min <= max, 'the lower limit must not be above the upper one');
|
|
1396
|
+
if (min > max) [min, max] = [max, min]; // Box2D stops on them reversed
|
|
1397
|
+
this.box2dJoint.SetLimits(-max, -min);
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/** Is the joint motor enabled?
|
|
1401
|
+
* @return {boolean} */
|
|
1402
|
+
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
|
|
1403
|
+
|
|
1404
|
+
/** Enable/disable the joint motor
|
|
1405
|
+
* @param {boolean} [enable] */
|
|
1406
|
+
enableMotor(enable=true) { this.box2dJoint.EnableMotor(enable); }
|
|
1407
|
+
|
|
1408
|
+
/** Set the motor speed, clockwise like angle
|
|
1409
|
+
* @param {number} speed */
|
|
1410
|
+
setMotorSpeed(speed) { this.box2dJoint.SetMotorSpeed(-speed); }
|
|
1411
|
+
|
|
1412
|
+
/** Get the motor speed, clockwise like angle
|
|
1413
|
+
* @return {number} */
|
|
1414
|
+
getMotorSpeed() { return -this.box2dJoint.GetMotorSpeed(); }
|
|
1415
|
+
|
|
1416
|
+
/** Set the max motor torque, a magnitude
|
|
1417
|
+
* @param {number} torque */
|
|
1418
|
+
setMaxMotorTorque(torque) { this.box2dJoint.SetMaxMotorTorque(torque); }
|
|
1419
|
+
|
|
1420
|
+
/** Get the max motor torque
|
|
1421
|
+
* @return {number} */
|
|
1422
|
+
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
|
|
1423
|
+
|
|
1424
|
+
/** Get the motor torque over the last step, clockwise like angle
|
|
1425
|
+
* @param {number} [time] - The step length in seconds, the world steps by timeDelta
|
|
1426
|
+
* @return {number} */
|
|
1427
|
+
getMotorTorque(time=timeDelta) { return -this.box2dJoint.GetMotorTorque(1/time); }
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1431
|
+
/**
|
|
1432
|
+
* Box2D Pin Joint
|
|
1433
|
+
* - Pins two objects together at a point, where they still turn freely, like a nail through two boards
|
|
1434
|
+
* - A revolute joint at that point, so it holds exactly and its limits and motor work too
|
|
1435
|
+
* @extends Box2dRevoluteJoint
|
|
1436
|
+
* @memberof Box2D
|
|
1437
|
+
*/
|
|
1438
|
+
class Box2dPinJoint extends Box2dRevoluteJoint
|
|
1439
|
+
{
|
|
1440
|
+
/** Create a pin joint
|
|
1441
|
+
* @param {Box2dObject} objectA
|
|
1442
|
+
* @param {Box2dObject} objectB
|
|
1443
|
+
* @param {Vector2} [pos] - World position, objectA's position if not given
|
|
1444
|
+
* @param {boolean} [collide] */
|
|
1445
|
+
constructor(objectA, objectB, pos=objectA.pos, collide=false)
|
|
1446
|
+
{
|
|
1447
|
+
super(objectA, objectB, pos, collide);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1452
|
+
/**
|
|
1453
|
+
* Box2D Gear Joint
|
|
1454
|
+
* - A gear joint is used to connect two joints together
|
|
1455
|
+
* - Either joint can be a revolute or prismatic joint
|
|
1456
|
+
* - You specify a gear ratio to bind the motions together
|
|
1457
|
+
* - joint1's angle or translation plus ratio times joint2's stays constant, angles clockwise like angle
|
|
1458
|
+
* - It is destroyed along with either joint, or an object either joint is on
|
|
1459
|
+
* - It turns objectB of each joint, so make each with its fixed or carrying object first, and a dynamic objectB
|
|
1460
|
+
* @extends Box2dJoint
|
|
1461
|
+
* @memberof Box2D
|
|
1462
|
+
*/
|
|
1463
|
+
class Box2dGearJoint extends Box2dJoint
|
|
1464
|
+
{
|
|
1465
|
+
/** Create a gear joint
|
|
1466
|
+
* @param {Box2dObject} objectA - objectB of joint1, Box2D joins that one whatever is passed
|
|
1467
|
+
* @param {Box2dObject} objectB - objectB of joint2, Box2D joins that one whatever is passed
|
|
1468
|
+
* @param {Box2dJoint} joint1
|
|
1469
|
+
* @param {Box2dJoint} joint2
|
|
1470
|
+
* @param {number} [ratio] */
|
|
1471
|
+
constructor(objectA, objectB, joint1, joint2, ratio=1)
|
|
1472
|
+
{
|
|
1473
|
+
// Box2D's angles are reversed and its translations are not, so a revolute joint geared to a prismatic one
|
|
1474
|
+
// needs the ratio reversed too, two of a kind keep it
|
|
1475
|
+
const isGearable = (j)=> (j instanceof Box2dRevoluteJoint || j instanceof Box2dPrismaticJoint) && !!j.box2dJoint;
|
|
1476
|
+
ASSERT(isGearable(joint1) && isGearable(joint2), 'a gear joint needs two revolute or prismatic joints that exist');
|
|
1477
|
+
// Box2D turns objectB of each joint, with objectA as its carrier, so a static objectB leaves the gear doing nothing
|
|
1478
|
+
ASSERT(joint1.getObjectB()?.getBodyType() === box2d.bodyTypeDynamic &&
|
|
1479
|
+
joint2.getObjectB()?.getBodyType() === box2d.bodyTypeDynamic,
|
|
1480
|
+
'a gear joint turns objectB of each joint, make each joint with its fixed or carrying object first');
|
|
1481
|
+
ASSERT(objectA === joint1.getObjectB() && objectB === joint2.getObjectB(),
|
|
1482
|
+
'a gear joint joins objectB of joint1 and objectB of joint2, pass those');
|
|
1483
|
+
const ratioSign = (joint1 instanceof Box2dRevoluteJoint) === (joint2 instanceof Box2dRevoluteJoint) ? 1 : -1;
|
|
1484
|
+
const jointDef = new box2d.instance.b2GearJointDef();
|
|
1485
|
+
jointDef.set_bodyA(objectA.body);
|
|
1486
|
+
jointDef.set_bodyB(objectB.body);
|
|
1487
|
+
jointDef.set_joint1(joint1.box2dJoint);
|
|
1488
|
+
jointDef.set_joint2(joint2.box2dJoint);
|
|
1489
|
+
jointDef.set_ratio(ratio * ratioSign);
|
|
1490
|
+
super(jointDef);
|
|
1491
|
+
|
|
1492
|
+
this.joint1 = joint1;
|
|
1493
|
+
this.joint2 = joint2;
|
|
1494
|
+
this.ratioSign = ratioSign;
|
|
1495
|
+
box2dGearJoints.add(this);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
/** Get the first joint
|
|
1499
|
+
* @return {Box2dJoint} */
|
|
1500
|
+
getJoint1() { return this.joint1; }
|
|
1501
|
+
|
|
1502
|
+
/** Get the second joint
|
|
1503
|
+
* @return {Box2dJoint} */
|
|
1504
|
+
getJoint2() { return this.joint2; }
|
|
1505
|
+
|
|
1506
|
+
/** Set the gear ratio
|
|
1507
|
+
* @param {number} ratio */
|
|
1508
|
+
setRatio(ratio) { this.box2dJoint.SetRatio(ratio * this.ratioSign); box2dWakeJoint(this.box2dJoint); }
|
|
1509
|
+
|
|
1510
|
+
/** Get the gear ratio
|
|
1511
|
+
* @return {number} */
|
|
1512
|
+
getRatio() { return this.box2dJoint.GetRatio() * this.ratioSign; }
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1516
|
+
/**
|
|
1517
|
+
* Box2D Prismatic Joint
|
|
1518
|
+
* - Provides one degree of freedom: translation along an axis fixed in objectA
|
|
1519
|
+
* - Relative rotation is prevented
|
|
1520
|
+
* - You can use a joint limit to restrict the range of motion
|
|
1521
|
+
* - You can use a joint motor to drive the motion or to model joint friction
|
|
1522
|
+
* @extends Box2dJoint
|
|
1523
|
+
* @memberof Box2D
|
|
1524
|
+
*/
|
|
1525
|
+
class Box2dPrismaticJoint extends Box2dJoint
|
|
1526
|
+
{
|
|
1527
|
+
/** Create a prismatic joint
|
|
1528
|
+
* @param {Box2dObject} objectA
|
|
1529
|
+
* @param {Box2dObject} objectB
|
|
1530
|
+
* @param {Vector2} [anchor] - World position, objectB's position if not given
|
|
1531
|
+
* @param {Vector2} [worldAxis]
|
|
1532
|
+
* @param {boolean} [collide] */
|
|
1533
|
+
constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
|
|
1534
|
+
{
|
|
1535
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1536
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
1537
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
1538
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis);
|
|
1539
|
+
const jointDef = new box2d.instance.b2PrismaticJointDef();
|
|
1540
|
+
jointDef.set_bodyA(objectA.body);
|
|
1541
|
+
jointDef.set_bodyB(objectB.body);
|
|
1542
|
+
jointDef.set_localAnchorA(box2dTemp(localAnchorA));
|
|
1543
|
+
jointDef.set_localAnchorB(box2dTemp(localAnchorB));
|
|
1544
|
+
jointDef.set_localAxisA(box2dTemp(localAxisA));
|
|
1545
|
+
jointDef.set_referenceAngle(objectB.body.GetAngle() - objectA.body.GetAngle());
|
|
1546
|
+
jointDef.set_collideConnected(collide);
|
|
1547
|
+
super(jointDef);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/** Get the local anchor point relative to objectA's origin
|
|
1551
|
+
* @return {Vector2} */
|
|
1552
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
1553
|
+
|
|
1554
|
+
/** Get the local anchor point relative to objectB's origin
|
|
1555
|
+
* @return {Vector2} */
|
|
1556
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
1557
|
+
|
|
1558
|
+
/** Get the local joint axis relative to bodyA
|
|
1559
|
+
* @return {Vector2} */
|
|
1560
|
+
getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
|
|
1561
|
+
|
|
1562
|
+
/** Get the reference angle, objectB angle minus objectA angle in the reference state
|
|
1563
|
+
* @return {number} */
|
|
1564
|
+
getReferenceAngle() { return -this.box2dJoint.GetReferenceAngle(); } // box2d uses reverse angle
|
|
1565
|
+
|
|
1566
|
+
/** Get the current joint translation
|
|
1567
|
+
* @return {number} */
|
|
1568
|
+
getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
|
|
1569
|
+
|
|
1570
|
+
/** Get the current joint translation speed
|
|
1571
|
+
* @return {number} */
|
|
1572
|
+
getJointSpeed() { return this.box2dJoint.GetJointSpeed(); }
|
|
1573
|
+
|
|
1574
|
+
/** Is the joint limit enabled?
|
|
1575
|
+
* @return {boolean} */
|
|
1576
|
+
isLimitEnabled() { return this.box2dJoint.IsLimitEnabled(); }
|
|
1577
|
+
|
|
1578
|
+
/** Enable/disable the joint limit
|
|
1579
|
+
* @param {boolean} [enable] */
|
|
1580
|
+
enableLimit(enable=true) { this.box2dJoint.EnableLimit(enable); }
|
|
1581
|
+
|
|
1582
|
+
/** Get the lower joint limit
|
|
1583
|
+
* @return {number} */
|
|
1584
|
+
getLowerLimit() { return this.box2dJoint.GetLowerLimit(); }
|
|
1585
|
+
|
|
1586
|
+
/** Get the upper joint limit
|
|
1587
|
+
* @return {number} */
|
|
1588
|
+
getUpperLimit() { return this.box2dJoint.GetUpperLimit(); }
|
|
1589
|
+
|
|
1590
|
+
/** Set the joint limits
|
|
1591
|
+
* @param {number} min
|
|
1592
|
+
* @param {number} max */
|
|
1593
|
+
setLimits(min, max)
|
|
1594
|
+
{
|
|
1595
|
+
ASSERT(min <= max, 'the lower limit must not be above the upper one');
|
|
1596
|
+
if (min > max) [min, max] = [max, min]; // Box2D stops on them reversed
|
|
1597
|
+
this.box2dJoint.SetLimits(min, max);
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
/** Is the motor enabled?
|
|
1601
|
+
* @return {boolean} */
|
|
1602
|
+
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
|
|
1603
|
+
|
|
1604
|
+
/** Enable/disable the joint motor
|
|
1605
|
+
* @param {boolean} [enable] */
|
|
1606
|
+
enableMotor(enable=true) { this.box2dJoint.EnableMotor(enable); }
|
|
1607
|
+
|
|
1608
|
+
/** Set the motor speed
|
|
1609
|
+
* @param {number} speed */
|
|
1610
|
+
setMotorSpeed(speed) { this.box2dJoint.SetMotorSpeed(speed); }
|
|
1611
|
+
|
|
1612
|
+
/** Get the motor speed
|
|
1613
|
+
* @return {number} */
|
|
1614
|
+
getMotorSpeed() { return this.box2dJoint.GetMotorSpeed(); }
|
|
1615
|
+
|
|
1616
|
+
/** Set the maximum motor force
|
|
1617
|
+
* @param {number} force */
|
|
1618
|
+
setMaxMotorForce(force) { this.box2dJoint.SetMaxMotorForce(force); }
|
|
1619
|
+
|
|
1620
|
+
/** Get the maximum motor force
|
|
1621
|
+
* @return {number} */
|
|
1622
|
+
getMaxMotorForce() { return this.box2dJoint.GetMaxMotorForce(); }
|
|
1623
|
+
|
|
1624
|
+
/** Get the motor force over the last step
|
|
1625
|
+
* @param {number} [time] - The step length in seconds, the world steps by timeDelta
|
|
1626
|
+
* @return {number} */
|
|
1627
|
+
getMotorForce(time=timeDelta) { return this.box2dJoint.GetMotorForce(1/time); }
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1631
|
+
/**
|
|
1632
|
+
* Box2D Wheel Joint
|
|
1633
|
+
* - Provides two degrees of freedom: translation along an axis fixed in objectA and rotation
|
|
1634
|
+
* - You can use a joint motor to drive the motion or to model joint friction
|
|
1635
|
+
* - This joint is designed for vehicle suspensions
|
|
1636
|
+
* @extends Box2dJoint
|
|
1637
|
+
* @memberof Box2D
|
|
1638
|
+
*/
|
|
1639
|
+
class Box2dWheelJoint extends Box2dJoint
|
|
1640
|
+
{
|
|
1641
|
+
/** Create a wheel joint
|
|
1642
|
+
* @param {Box2dObject} objectA
|
|
1643
|
+
* @param {Box2dObject} objectB
|
|
1644
|
+
* @param {Vector2} [anchor] - World position, objectB's position if not given
|
|
1645
|
+
* @param {Vector2} [worldAxis]
|
|
1646
|
+
* @param {boolean} [collide] */
|
|
1647
|
+
constructor(objectA, objectB, anchor, worldAxis=vec2(0,1), collide=false)
|
|
1648
|
+
{
|
|
1649
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1650
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
1651
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
1652
|
+
const localAxisA = objectA.worldToLocalVector(worldAxis).normalize(); // Box2D uses the wheel axis as given
|
|
1653
|
+
const jointDef = new box2d.instance.b2WheelJointDef();
|
|
1654
|
+
jointDef.set_bodyA(objectA.body);
|
|
1655
|
+
jointDef.set_bodyB(objectB.body);
|
|
1656
|
+
jointDef.set_localAnchorA(box2dTemp(localAnchorA));
|
|
1657
|
+
jointDef.set_localAnchorB(box2dTemp(localAnchorB));
|
|
1658
|
+
jointDef.set_localAxisA(box2dTemp(localAxisA));
|
|
1659
|
+
jointDef.set_collideConnected(collide);
|
|
1660
|
+
super(jointDef);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
/** Get the local anchor point relative to objectA's origin
|
|
1664
|
+
* @return {Vector2} */
|
|
1665
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
1666
|
+
|
|
1667
|
+
/** Get the local anchor point relative to objectB's origin
|
|
1668
|
+
* @return {Vector2} */
|
|
1669
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
1670
|
+
|
|
1671
|
+
/** Get the local joint axis relative to bodyA
|
|
1672
|
+
* @return {Vector2} */
|
|
1673
|
+
getLocalAxisA() { return box2d.vec2From(this.box2dJoint.GetLocalAxisA()); }
|
|
1674
|
+
|
|
1675
|
+
/** Get the current joint translation
|
|
1676
|
+
* @return {number} */
|
|
1677
|
+
getJointTranslation() { return this.box2dJoint.GetJointTranslation(); }
|
|
1678
|
+
|
|
1679
|
+
/** Get the current joint rotation speed in radians per second, clockwise like angle,
|
|
1680
|
+
* which is what this version of Box2D measures for a wheel joint
|
|
1681
|
+
* @return {number} */
|
|
1682
|
+
getJointSpeed() { return -this.box2dJoint.GetJointSpeed(); } // box2d uses reverse angle
|
|
1683
|
+
|
|
1684
|
+
/** Is the joint motor enabled?
|
|
1685
|
+
* @return {boolean} */
|
|
1686
|
+
isMotorEnabled() { return this.box2dJoint.IsMotorEnabled(); }
|
|
1687
|
+
|
|
1688
|
+
/** Enable/disable the joint motor
|
|
1689
|
+
* @param {boolean} [enable] */
|
|
1690
|
+
enableMotor(enable=true) { this.box2dJoint.EnableMotor(enable); }
|
|
1691
|
+
|
|
1692
|
+
/** Set the motor speed, the wheel's turn in radians per second, clockwise like angle
|
|
1693
|
+
* @param {number} speed */
|
|
1694
|
+
setMotorSpeed(speed) { this.box2dJoint.SetMotorSpeed(-speed); }
|
|
1695
|
+
|
|
1696
|
+
/** Get the motor speed, clockwise like angle
|
|
1697
|
+
* @return {number} */
|
|
1698
|
+
getMotorSpeed() { return -this.box2dJoint.GetMotorSpeed(); }
|
|
1699
|
+
|
|
1700
|
+
/** Set the maximum motor torque, a magnitude
|
|
1701
|
+
* @param {number} torque */
|
|
1702
|
+
setMaxMotorTorque(torque) { this.box2dJoint.SetMaxMotorTorque(torque); }
|
|
1703
|
+
|
|
1704
|
+
/** Get the max motor torque
|
|
1705
|
+
* @return {number} */
|
|
1706
|
+
getMaxMotorTorque() { return this.box2dJoint.GetMaxMotorTorque(); }
|
|
1707
|
+
|
|
1708
|
+
/** Get the motor torque over the last step, clockwise like angle
|
|
1709
|
+
* @param {number} [time] - The step length in seconds, the world steps by timeDelta
|
|
1710
|
+
* @return {number} */
|
|
1711
|
+
getMotorTorque(time=timeDelta) { return -this.box2dJoint.GetMotorTorque(1/time); }
|
|
1712
|
+
|
|
1713
|
+
/** Set the spring frequency in Hertz
|
|
1714
|
+
* @param {number} hz */
|
|
1715
|
+
setSpringFrequencyHz(hz) { this.box2dJoint.SetSpringFrequencyHz(hz); box2dWakeJoint(this.box2dJoint); }
|
|
1716
|
+
|
|
1717
|
+
/** Get the spring frequency in Hertz
|
|
1718
|
+
* @return {number} */
|
|
1719
|
+
getSpringFrequencyHz() { return this.box2dJoint.GetSpringFrequencyHz(); }
|
|
1720
|
+
|
|
1721
|
+
/** Set the spring damping ratio
|
|
1722
|
+
* @param {number} ratio */
|
|
1723
|
+
setSpringDampingRatio(ratio) { this.box2dJoint.SetSpringDampingRatio(ratio); box2dWakeJoint(this.box2dJoint); }
|
|
1724
|
+
|
|
1725
|
+
/** Get the spring damping ratio
|
|
1726
|
+
* @return {number} */
|
|
1727
|
+
getSpringDampingRatio() { return this.box2dJoint.GetSpringDampingRatio(); }
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1731
|
+
/**
|
|
1732
|
+
* Box2D Weld Joint
|
|
1733
|
+
* - Glues two objects together
|
|
1734
|
+
* @extends Box2dJoint
|
|
1735
|
+
* @memberof Box2D
|
|
1736
|
+
*/
|
|
1737
|
+
class Box2dWeldJoint extends Box2dJoint
|
|
1738
|
+
{
|
|
1739
|
+
/** Create a weld joint
|
|
1740
|
+
* @param {Box2dObject} objectA
|
|
1741
|
+
* @param {Box2dObject} objectB
|
|
1742
|
+
* @param {Vector2} [anchor] - World position, objectB's position if not given
|
|
1743
|
+
* @param {boolean} [collide] */
|
|
1744
|
+
constructor(objectA, objectB, anchor, collide=false)
|
|
1745
|
+
{
|
|
1746
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1747
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
1748
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
1749
|
+
const referenceAngle = objectB.body.GetAngle() - objectA.body.GetAngle();
|
|
1750
|
+
const jointDef = new box2d.instance.b2WeldJointDef();
|
|
1751
|
+
jointDef.set_bodyA(objectA.body);
|
|
1752
|
+
jointDef.set_bodyB(objectB.body);
|
|
1753
|
+
jointDef.set_localAnchorA(box2dTemp(localAnchorA));
|
|
1754
|
+
jointDef.set_localAnchorB(box2dTemp(localAnchorB));
|
|
1755
|
+
jointDef.set_referenceAngle(referenceAngle);
|
|
1756
|
+
jointDef.set_collideConnected(collide);
|
|
1757
|
+
super(jointDef);
|
|
1758
|
+
|
|
1759
|
+
// kept here, since the binding cannot read it back from a weld joint; box2d uses reverse angle
|
|
1760
|
+
this.referenceAngle = -referenceAngle;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
/** Get the local anchor point relative to objectA's origin
|
|
1764
|
+
* @return {Vector2} */
|
|
1765
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
1766
|
+
|
|
1767
|
+
/** Get the local anchor point relative to objectB's origin
|
|
1768
|
+
* @return {Vector2} */
|
|
1769
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
1770
|
+
|
|
1771
|
+
/** Get the reference angle, objectB angle minus objectA angle in the reference state
|
|
1772
|
+
* @return {number} */
|
|
1773
|
+
getReferenceAngle() { return this.referenceAngle; }
|
|
1774
|
+
|
|
1775
|
+
/** Set the frequency in Hertz
|
|
1776
|
+
* @param {number} hz */
|
|
1777
|
+
setFrequency(hz) { this.box2dJoint.SetFrequency(hz); box2dWakeJoint(this.box2dJoint); }
|
|
1778
|
+
|
|
1779
|
+
/** Get the frequency in Hertz
|
|
1780
|
+
* @return {number} */
|
|
1781
|
+
getFrequency() { return this.box2dJoint.GetFrequency(); }
|
|
1782
|
+
|
|
1783
|
+
/** Set the damping ratio
|
|
1784
|
+
* @param {number} ratio */
|
|
1785
|
+
setDampingRatio(ratio) { this.box2dJoint.SetDampingRatio(ratio); box2dWakeJoint(this.box2dJoint); }
|
|
1786
|
+
|
|
1787
|
+
/** Get the damping ratio
|
|
1788
|
+
* @return {number} */
|
|
1789
|
+
getDampingRatio() { return this.box2dJoint.GetDampingRatio(); }
|
|
1790
|
+
|
|
1791
|
+
/** @deprecated since 1.20, use setDampingRatio
|
|
1792
|
+
* @param {number} ratio */
|
|
1793
|
+
setSpringDampingRatio(ratio) { this.setDampingRatio(ratio); }
|
|
1794
|
+
|
|
1795
|
+
/** @deprecated since 1.20, use getDampingRatio
|
|
1796
|
+
* @return {number} */
|
|
1797
|
+
getSpringDampingRatio() { return this.getDampingRatio(); }
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1801
|
+
/**
|
|
1802
|
+
* Box2D Friction Joint
|
|
1803
|
+
* - Used to apply top-down friction
|
|
1804
|
+
* - Provides 2D translational friction and angular friction
|
|
1805
|
+
* @extends Box2dJoint
|
|
1806
|
+
* @memberof Box2D
|
|
1807
|
+
*/
|
|
1808
|
+
class Box2dFrictionJoint extends Box2dJoint
|
|
1809
|
+
{
|
|
1810
|
+
/** Create a friction joint
|
|
1811
|
+
* @param {Box2dObject} objectA
|
|
1812
|
+
* @param {Box2dObject} objectB
|
|
1813
|
+
* @param {Vector2} [anchor] - World position, objectB's position if not given
|
|
1814
|
+
* @param {boolean} [collide] */
|
|
1815
|
+
constructor(objectA, objectB, anchor, collide=false)
|
|
1816
|
+
{
|
|
1817
|
+
anchor ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1818
|
+
const localAnchorA = objectA.worldToLocal(anchor);
|
|
1819
|
+
const localAnchorB = objectB.worldToLocal(anchor);
|
|
1820
|
+
const jointDef = new box2d.instance.b2FrictionJointDef();
|
|
1821
|
+
jointDef.set_bodyA(objectA.body);
|
|
1822
|
+
jointDef.set_bodyB(objectB.body);
|
|
1823
|
+
jointDef.set_localAnchorA(box2dTemp(localAnchorA));
|
|
1824
|
+
jointDef.set_localAnchorB(box2dTemp(localAnchorB));
|
|
1825
|
+
jointDef.set_collideConnected(collide);
|
|
1826
|
+
super(jointDef);
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
/** Get the local anchor point relative to objectA's origin
|
|
1830
|
+
* @return {Vector2} */
|
|
1831
|
+
getLocalAnchorA() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorA()); }
|
|
1832
|
+
|
|
1833
|
+
/** Get the local anchor point relative to objectB's origin
|
|
1834
|
+
* @return {Vector2} */
|
|
1835
|
+
getLocalAnchorB() { return box2d.vec2From(this.box2dJoint.GetLocalAnchorB()); }
|
|
1836
|
+
|
|
1837
|
+
/** Set the maximum friction force
|
|
1838
|
+
* @param {number} force */
|
|
1839
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(max(force, 0)); } // Box2D stops on a negative one
|
|
1840
|
+
|
|
1841
|
+
/** Get the maximum friction force
|
|
1842
|
+
* @return {number} */
|
|
1843
|
+
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
|
|
1844
|
+
|
|
1845
|
+
/** Set the maximum friction torque
|
|
1846
|
+
* @param {number} torque */
|
|
1847
|
+
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(max(torque, 0)); } // Box2D stops on a negative one
|
|
1848
|
+
|
|
1849
|
+
/** Get the maximum friction torque
|
|
1850
|
+
* @return {number} */
|
|
1851
|
+
getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1855
|
+
/**
|
|
1856
|
+
* Box2D Pulley Joint
|
|
1857
|
+
* - Connects to two objects and two fixed ground points
|
|
1858
|
+
* - The pulley supports a ratio such that: length1 + ratio * length2 <= constant
|
|
1859
|
+
* - The force transmitted is scaled by the ratio
|
|
1860
|
+
* @extends Box2dJoint
|
|
1861
|
+
* @memberof Box2D
|
|
1862
|
+
*/
|
|
1863
|
+
class Box2dPulleyJoint extends Box2dJoint
|
|
1864
|
+
{
|
|
1865
|
+
/** Create a pulley joint
|
|
1866
|
+
* @param {Box2dObject} objectA
|
|
1867
|
+
* @param {Box2dObject} objectB
|
|
1868
|
+
* @param {Vector2} groundAnchorA
|
|
1869
|
+
* @param {Vector2} groundAnchorB
|
|
1870
|
+
* @param {Vector2} [anchorA] - World position, objectA's position if not given
|
|
1871
|
+
* @param {Vector2} [anchorB] - World position, objectB's position if not given
|
|
1872
|
+
* @param {number} [ratio]
|
|
1873
|
+
* @param {boolean} [collide] */
|
|
1874
|
+
constructor(objectA, objectB, groundAnchorA, groundAnchorB, anchorA, anchorB, ratio=1, collide=false)
|
|
1875
|
+
{
|
|
1876
|
+
anchorA ||= box2d.vec2From(objectA.body.GetPosition());
|
|
1877
|
+
anchorB ||= box2d.vec2From(objectB.body.GetPosition());
|
|
1878
|
+
const localAnchorA = objectA.worldToLocal(anchorA);
|
|
1879
|
+
const localAnchorB = objectB.worldToLocal(anchorB);
|
|
1880
|
+
const jointDef = new box2d.instance.b2PulleyJointDef();
|
|
1881
|
+
jointDef.set_bodyA(objectA.body);
|
|
1882
|
+
jointDef.set_bodyB(objectB.body);
|
|
1883
|
+
jointDef.set_groundAnchorA(box2dTemp(groundAnchorA));
|
|
1884
|
+
jointDef.set_groundAnchorB(box2dTemp(groundAnchorB));
|
|
1885
|
+
jointDef.set_localAnchorA(box2dTemp(localAnchorA));
|
|
1886
|
+
jointDef.set_localAnchorB(box2dTemp(localAnchorB));
|
|
1887
|
+
ASSERT(ratio, 'a pulley ratio can not be 0');
|
|
1888
|
+
jointDef.set_ratio(ratio);
|
|
1889
|
+
jointDef.set_lengthA(groundAnchorA.distance(anchorA));
|
|
1890
|
+
jointDef.set_lengthB(groundAnchorB.distance(anchorB));
|
|
1891
|
+
jointDef.set_collideConnected(collide);
|
|
1892
|
+
super(jointDef);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
/** Get the first ground anchor
|
|
1896
|
+
* @return {Vector2} */
|
|
1897
|
+
getGroundAnchorA() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorA()); }
|
|
1898
|
+
|
|
1899
|
+
/** Get the second ground anchor
|
|
1900
|
+
* @return {Vector2} */
|
|
1901
|
+
getGroundAnchorB() { return box2d.vec2From(this.box2dJoint.GetGroundAnchorB()); }
|
|
1902
|
+
|
|
1903
|
+
/** Get the rest length of the segment attached to objectA, set at creation
|
|
1904
|
+
* @return {number} */
|
|
1905
|
+
getLengthA() { return this.box2dJoint.GetLengthA(); }
|
|
1906
|
+
|
|
1907
|
+
/** Get the rest length of the segment attached to objectB, set at creation
|
|
1908
|
+
* @return {number} */
|
|
1909
|
+
getLengthB(){ return this.box2dJoint.GetLengthB(); }
|
|
1910
|
+
|
|
1911
|
+
/** Get the pulley ratio
|
|
1912
|
+
* @return {number} */
|
|
1913
|
+
getRatio() { return this.box2dJoint.GetRatio(); }
|
|
1914
|
+
|
|
1915
|
+
/** Get the current length of the segment attached to objectA
|
|
1916
|
+
* @return {number} */
|
|
1917
|
+
getCurrentLengthA() { return this.box2dJoint.GetCurrentLengthA(); }
|
|
1918
|
+
|
|
1919
|
+
/** Get the current length of the segment attached to objectB
|
|
1920
|
+
* @return {number} */
|
|
1921
|
+
getCurrentLengthB() { return this.box2dJoint.GetCurrentLengthB(); }
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1925
|
+
/**
|
|
1926
|
+
* Box2D Motor Joint
|
|
1927
|
+
* - Controls the relative motion between two objects
|
|
1928
|
+
* - Typical usage is to control the movement of a object with respect to the ground
|
|
1929
|
+
* @extends Box2dJoint
|
|
1930
|
+
* @memberof Box2D
|
|
1931
|
+
*/
|
|
1932
|
+
class Box2dMotorJoint extends Box2dJoint
|
|
1933
|
+
{
|
|
1934
|
+
/** Create a motor joint
|
|
1935
|
+
* @param {Box2dObject} objectA
|
|
1936
|
+
* @param {Box2dObject} objectB */
|
|
1937
|
+
constructor(objectA, objectB)
|
|
1938
|
+
{
|
|
1939
|
+
const linearOffset = objectA.worldToLocal(box2d.vec2From(objectB.body.GetPosition()));
|
|
1940
|
+
const angularOffset = objectB.body.GetAngle() - objectA.body.GetAngle();
|
|
1941
|
+
const jointDef = new box2d.instance.b2MotorJointDef();
|
|
1942
|
+
jointDef.set_bodyA(objectA.body);
|
|
1943
|
+
jointDef.set_bodyB(objectB.body);
|
|
1944
|
+
jointDef.set_linearOffset(box2dTemp(linearOffset));
|
|
1945
|
+
jointDef.set_angularOffset(angularOffset);
|
|
1946
|
+
super(jointDef);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
/** Set the target linear offset, in frame A, in meters.
|
|
1950
|
+
* @param {Vector2} offset */
|
|
1951
|
+
setLinearOffset(offset) { this.box2dJoint.SetLinearOffset(box2dTemp(offset)); }
|
|
1952
|
+
|
|
1953
|
+
/** Get the target linear offset, in frame A, in meters.
|
|
1954
|
+
* @return {Vector2} */
|
|
1955
|
+
getLinearOffset() { return box2d.vec2From(this.box2dJoint.GetLinearOffset()); }
|
|
1956
|
+
|
|
1957
|
+
/** Set the target angular offset, objectB angle minus objectA angle, clockwise like angle
|
|
1958
|
+
* @param {number} offset */
|
|
1959
|
+
setAngularOffset(offset) { this.box2dJoint.SetAngularOffset(-offset); } // box2d uses reverse angle
|
|
1960
|
+
|
|
1961
|
+
/** Get the target angular offset, objectB angle minus objectA angle, clockwise like angle
|
|
1962
|
+
* @return {number} */
|
|
1963
|
+
getAngularOffset() { return -this.box2dJoint.GetAngularOffset(); }
|
|
1964
|
+
|
|
1965
|
+
/** Set the maximum force
|
|
1966
|
+
* @param {number} force */
|
|
1967
|
+
setMaxForce(force) { this.box2dJoint.SetMaxForce(max(force, 0)); box2dWakeJoint(this.box2dJoint); } // Box2D stops on a negative one
|
|
1968
|
+
|
|
1969
|
+
/** Get the maximum force
|
|
1970
|
+
* @return {number} */
|
|
1971
|
+
getMaxForce() { return this.box2dJoint.GetMaxForce(); }
|
|
1972
|
+
|
|
1973
|
+
/** Set the maximum torque
|
|
1974
|
+
* @param {number} torque */
|
|
1975
|
+
setMaxTorque(torque) { this.box2dJoint.SetMaxTorque(max(torque, 0)); box2dWakeJoint(this.box2dJoint); } // Box2D stops on a negative one
|
|
1976
|
+
|
|
1977
|
+
/** Get the maximum torque
|
|
1978
|
+
* @return {number} */
|
|
1979
|
+
getMaxTorque() { return this.box2dJoint.GetMaxTorque(); }
|
|
1980
|
+
|
|
1981
|
+
/** Set the position correction factor in the range [0,1]
|
|
1982
|
+
* @param {number} factor */
|
|
1983
|
+
setCorrectionFactor(factor) { this.box2dJoint.SetCorrectionFactor(clamp(factor)); box2dWakeJoint(this.box2dJoint); }
|
|
1984
|
+
|
|
1985
|
+
/** Get the position correction factor in the range [0,1]
|
|
1986
|
+
* @return {number} */
|
|
1987
|
+
getCorrectionFactor() { return this.box2dJoint.GetCorrectionFactor(); }
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
1991
|
+
/**
|
|
1992
|
+
* Box2D Global Object
|
|
1993
|
+
* - Wraps Box2d world and provides global functions
|
|
1994
|
+
* @memberof Box2D
|
|
1995
|
+
*/
|
|
1996
|
+
class Box2dPlugin
|
|
1997
|
+
{
|
|
1998
|
+
/** Create the global Box2D plugin object, box2dInit does this
|
|
1999
|
+
* @param {Object} instance */
|
|
2000
|
+
constructor(instance)
|
|
2001
|
+
{
|
|
2002
|
+
ASSERT(!box2d, 'Box2D already initialized');
|
|
2003
|
+
box2d = this;
|
|
2004
|
+
|
|
2005
|
+
/** @property {Object} - The Box2d instance */
|
|
2006
|
+
this.instance = instance;
|
|
2007
|
+
/** @property {Object} - The Box2d world */
|
|
2008
|
+
this.world = new box2d.instance.b2World();
|
|
2009
|
+
/** @property {Array<Box2dObject>} - List of all Box2d objects, a destroyed one stays until the next step
|
|
2010
|
+
* with its body undefined
|
|
2011
|
+
* @type {Array<Box2dObject>} */
|
|
2012
|
+
this.objects = [];
|
|
2013
|
+
/** @property {number} - Velocity iterations per update*/
|
|
2014
|
+
this.velocityIterations = 8;
|
|
2015
|
+
/** @property {number} - Position iterations per update*/
|
|
2016
|
+
this.positionIterations = 3;
|
|
2017
|
+
/** @property {number} - Static, zero mass, zero velocity, may be manually moved
|
|
2018
|
+
* @type {number} */
|
|
2019
|
+
this.bodyTypeStatic = instance.b2_staticBody;
|
|
2020
|
+
/** @property {number} - Kinematic, zero mass, non-zero velocity set by user, moved by solver
|
|
2021
|
+
* @type {number} */
|
|
2022
|
+
this.bodyTypeKinematic = instance.b2_kinematicBody;
|
|
2023
|
+
/** @property {number} - Dynamic, positive mass, non-zero velocity determined by forces, moved by solver
|
|
2024
|
+
* @type {number} */
|
|
2025
|
+
this.bodyTypeDynamic = instance.b2_dynamicBody;
|
|
2026
|
+
|
|
2027
|
+
// a body that goes takes its joints with it, their wrappers let go of them, and a gear joint on one of them
|
|
2028
|
+
// goes too, since it would keep pointers to what was freed; that destroy waits in the queue, which DestroyBody
|
|
2029
|
+
// runs from, and is skipped if the gear hung off this same body and went with it
|
|
2030
|
+
const destructionListener = new box2d.instance.JSDestructionListener();
|
|
2031
|
+
destructionListener.SayGoodbyeJoint = function(jointPointer)
|
|
2032
|
+
{
|
|
2033
|
+
const joint = box2dJoints.get(jointPointer);
|
|
2034
|
+
if (joint)
|
|
2035
|
+
{
|
|
2036
|
+
box2dDestroyGears(joint);
|
|
2037
|
+
joint.box2dJoint = 0;
|
|
2038
|
+
}
|
|
2039
|
+
box2dJoints.delete(jointPointer);
|
|
2040
|
+
};
|
|
2041
|
+
destructionListener.SayGoodbyeFixture = function() {};
|
|
2042
|
+
box2d.world.SetDestructionListener(destructionListener);
|
|
2043
|
+
|
|
2044
|
+
// setup contact listener
|
|
2045
|
+
const listener = new box2d.instance.JSContactListener();
|
|
2046
|
+
listener.BeginContact = function(contactPtr)
|
|
2047
|
+
{
|
|
2048
|
+
const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
|
|
2049
|
+
const fixtureA = contact.GetFixtureA();
|
|
2050
|
+
const fixtureB = contact.GetFixtureB();
|
|
2051
|
+
const objectA = fixtureA.GetBody().object;
|
|
2052
|
+
const objectB = fixtureB.GetBody().object;
|
|
2053
|
+
// raw user-created b2Bodies may have no .object — skip those
|
|
2054
|
+
if (!objectA || !objectB) return;
|
|
2055
|
+
objectA.beginContact(objectB, fixtureA, fixtureB);
|
|
2056
|
+
objectB.beginContact(objectA, fixtureB, fixtureA);
|
|
2057
|
+
}
|
|
2058
|
+
listener.EndContact = function(contactPtr)
|
|
2059
|
+
{
|
|
2060
|
+
const contact = box2d.instance.wrapPointer(contactPtr, box2d.instance.b2Contact);
|
|
2061
|
+
const fixtureA = contact.GetFixtureA();
|
|
2062
|
+
const fixtureB = contact.GetFixtureB();
|
|
2063
|
+
const objectA = fixtureA.GetBody().object;
|
|
2064
|
+
const objectB = fixtureB.GetBody().object;
|
|
2065
|
+
if (!objectA || !objectB) return;
|
|
2066
|
+
objectA.endContact(objectB, fixtureA, fixtureB);
|
|
2067
|
+
objectB.endContact(objectA, fixtureB, fixtureA);
|
|
2068
|
+
};
|
|
2069
|
+
listener.PreSolve = function() {};
|
|
2070
|
+
listener.PostSolve = function() {};
|
|
2071
|
+
box2d.world.SetContactListener(listener);
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
/** Step the physics world simulation
|
|
2075
|
+
* @param {number} [frames] */
|
|
2076
|
+
step(frames=1)
|
|
2077
|
+
{
|
|
2078
|
+
// the engine's gravity, Box2D does not wake a sleeping body for a new one, so a change wakes them all
|
|
2079
|
+
if (gravity.x !== box2dGravity.x || gravity.y !== box2dGravity.y)
|
|
2080
|
+
{
|
|
2081
|
+
box2dGravity.x = gravity.x, box2dGravity.y = gravity.y;
|
|
2082
|
+
box2d.world.SetGravity(box2dTemp(gravity));
|
|
2083
|
+
for (let b = box2d.world.GetBodyList(); !box2d.isNull(b); b = b.GetNext())
|
|
2084
|
+
b.SetAwake(true);
|
|
2085
|
+
}
|
|
2086
|
+
for (let i=frames; i--;)
|
|
2087
|
+
{
|
|
2088
|
+
box2d.world.Step(timeDelta, this.velocityIterations, this.positionIterations);
|
|
2089
|
+
|
|
2090
|
+
// what a contact callback destroyed or changed, now the world can take it
|
|
2091
|
+
box2dRunPending();
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
// remove destroyed objects, once for all of them
|
|
2095
|
+
this.objects = this.objects.filter(o=>!o.destroyed);
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2099
|
+
// raycasting and querying
|
|
2100
|
+
|
|
2101
|
+
/** raycast and return a list of all the results, nearest first
|
|
2102
|
+
* @param {Vector2} start
|
|
2103
|
+
* @param {Vector2} end
|
|
2104
|
+
* @param {boolean} [includeSensors] - Also hit sensors, trigger zones are passed through by default
|
|
2105
|
+
* @return {Array<Box2dRaycastResult>} */
|
|
2106
|
+
raycastAll(start, end, includeSensors=false)
|
|
2107
|
+
{
|
|
2108
|
+
// a ray with no length fails an assert that stops Box2D for good, measured as Box2D does in 32 bit floats,
|
|
2109
|
+
// where two ends a float apart are one point; one that is not a number has no length either
|
|
2110
|
+
const f = Math.fround, dx = f(f(end.x) - f(start.x)), dy = f(f(end.y) - f(start.y));
|
|
2111
|
+
const lengthSquared = f(f(dx*dx) + f(dy*dy));
|
|
2112
|
+
if (!(lengthSquared > 0 && lengthSquared < Infinity))
|
|
2113
|
+
return [];
|
|
2114
|
+
|
|
2115
|
+
const raycastCallback = box2dQueryObject('rayCast', 'JSRayCastCallback');
|
|
2116
|
+
raycastCallback.ReportFixture = function(fixturePointer, point, normal, fraction)
|
|
2117
|
+
{
|
|
2118
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
2119
|
+
const o = fixture.GetBody().object;
|
|
2120
|
+
if (!o || o.destroyed)
|
|
2121
|
+
return 1; // a raw body with no Box2dObject or one destroyed this step, continue getting results
|
|
2122
|
+
if (!includeSensors && fixture.IsSensor())
|
|
2123
|
+
return -1; // skip it, Box2D goes on as if it were not there
|
|
2124
|
+
point = box2d.vec2FromPointer(point);
|
|
2125
|
+
normal = box2d.vec2FromPointer(normal);
|
|
2126
|
+
raycastResults.push(new Box2dRaycastResult(fixture, point, normal, fraction));
|
|
2127
|
+
return 1; // continue getting results
|
|
2128
|
+
};
|
|
2129
|
+
|
|
2130
|
+
const raycastResults = [];
|
|
2131
|
+
box2d.world.RayCast(raycastCallback, box2dTemp(start), box2dTemp(end, 1));
|
|
2132
|
+
raycastResults.sort((a,b)=> a.fraction - b.fraction); // Box2D reports them in its tree's order
|
|
2133
|
+
debugRaycast && debugLine(start, end, raycastResults.length ? '#f00' : '#00f', .02, 0, false);
|
|
2134
|
+
return raycastResults;
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
/** raycast and return the first result
|
|
2138
|
+
* @param {Vector2} start
|
|
2139
|
+
* @param {Vector2} end
|
|
2140
|
+
* @param {boolean} [includeSensors] - Also hit sensors, trigger zones are passed through by default
|
|
2141
|
+
* @return {Box2dRaycastResult|undefined} */
|
|
2142
|
+
raycast(start, end, includeSensors=false)
|
|
2143
|
+
{
|
|
2144
|
+
return box2d.raycastAll(start, end, includeSensors)[0];
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
/** box aabb cast and return all the objects
|
|
2148
|
+
* @param {Vector2} pos
|
|
2149
|
+
* @param {Vector2} size
|
|
2150
|
+
* @param {boolean} [includeSensors] - Also find sensors, trigger zones are passed through by default
|
|
2151
|
+
* @return {Array<Box2dObject>} */
|
|
2152
|
+
boxCastAll(pos, size, includeSensors=false)
|
|
2153
|
+
{
|
|
2154
|
+
const queryCallback = box2dQueryObject('query', 'JSQueryCallback');
|
|
2155
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
2156
|
+
{
|
|
2157
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
2158
|
+
if (!includeSensors && fixture.IsSensor())
|
|
2159
|
+
return true; // a trigger zone, continue getting results
|
|
2160
|
+
const o = fixture.GetBody().object;
|
|
2161
|
+
if (o && !o.destroyed && !queryObjects.includes(o) // skip raw bodies and ones destroyed this step
|
|
2162
|
+
&& box2dFixtureOverlaps(fixture, aabb))
|
|
2163
|
+
queryObjects.push(o); // add if not already in list
|
|
2164
|
+
return true; // continue getting results
|
|
2165
|
+
};
|
|
2166
|
+
|
|
2167
|
+
const aabb = box2dQueryObject('aabb', 'b2AABB');
|
|
2168
|
+
aabb.set_lowerBound(box2dTemp(pos.subtract(size.scale(.5))));
|
|
2169
|
+
aabb.set_upperBound(box2dTemp(pos.add(size.scale(.5))));
|
|
2170
|
+
|
|
2171
|
+
let queryObjects = [];
|
|
2172
|
+
box2d.world.QueryAABB(queryCallback, aabb);
|
|
2173
|
+
debugRaycast && debugRect(pos, size, queryObjects.length ? '#f00' : '#00f', 0, 0, false, false);
|
|
2174
|
+
return queryObjects;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/** box aabb cast and return the first object
|
|
2178
|
+
* @param {Vector2} pos
|
|
2179
|
+
* @param {Vector2} size
|
|
2180
|
+
* @param {boolean} [includeSensors] - Also find sensors, trigger zones are passed through by default
|
|
2181
|
+
* @return {Box2dObject|undefined} */
|
|
2182
|
+
boxCast(pos, size, includeSensors=false)
|
|
2183
|
+
{
|
|
2184
|
+
const queryCallback = box2dQueryObject('query', 'JSQueryCallback');
|
|
2185
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
2186
|
+
{
|
|
2187
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
2188
|
+
if (!includeSensors && fixture.IsSensor())
|
|
2189
|
+
return true; // a trigger zone, continue getting results
|
|
2190
|
+
const o = fixture.GetBody().object;
|
|
2191
|
+
if (!o || o.destroyed)
|
|
2192
|
+
return true; // a raw body with no Box2dObject or one destroyed this step, continue getting results
|
|
2193
|
+
if (!box2dFixtureOverlaps(fixture, aabb))
|
|
2194
|
+
return true; // only near the box, continue getting results
|
|
2195
|
+
queryObject = o;
|
|
2196
|
+
return false; // stop getting results
|
|
2197
|
+
};
|
|
2198
|
+
|
|
2199
|
+
const aabb = box2dQueryObject('aabb', 'b2AABB');
|
|
2200
|
+
aabb.set_lowerBound(box2dTemp(pos.subtract(size.scale(.5))));
|
|
2201
|
+
aabb.set_upperBound(box2dTemp(pos.add(size.scale(.5))));
|
|
2202
|
+
|
|
2203
|
+
let queryObject;
|
|
2204
|
+
box2d.world.QueryAABB(queryCallback, aabb);
|
|
2205
|
+
debugRaycast && debugRect(pos, size, queryObject ? '#f00' : '#00f', 0, 0, false, false);
|
|
2206
|
+
return queryObject;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
/** circle cast and return all the objects whose position is within the circle, wherever their shapes are
|
|
2210
|
+
* @param {Vector2} pos
|
|
2211
|
+
* @param {number} diameter
|
|
2212
|
+
* @param {boolean} [includeSensors] - Also find objects whose shapes are all sensors, like trigger zones
|
|
2213
|
+
* @return {Array<Box2dObject>} */
|
|
2214
|
+
circleCastAll(pos, diameter, includeSensors=false)
|
|
2215
|
+
{
|
|
2216
|
+
// by each object's position, so an object whose shapes are offset from it is still found
|
|
2217
|
+
const radius2 = (diameter/2)**2;
|
|
2218
|
+
const results = box2d.objects.filter(o=> !o.destroyed && o.body && o.pos.distanceSquared(pos) < radius2 &&
|
|
2219
|
+
(includeSensors || o.getFixtureList().some(fixture=> !fixture.IsSensor())));
|
|
2220
|
+
debugRaycast && debugCircle(pos, diameter, results.length ? '#f00' : '#00f', 0, false, false);
|
|
2221
|
+
return results;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
/** circle cast and return the object whose position is nearest, of those within the circle
|
|
2225
|
+
* @param {Vector2} pos
|
|
2226
|
+
* @param {number} diameter
|
|
2227
|
+
* @param {boolean} [includeSensors] - Also find objects whose shapes are all sensors, like trigger zones
|
|
2228
|
+
* @return {Box2dObject|undefined} */
|
|
2229
|
+
circleCast(pos, diameter, includeSensors=false)
|
|
2230
|
+
{
|
|
2231
|
+
let bestResult, bestDistance2;
|
|
2232
|
+
for (const result of box2d.circleCastAll(pos, diameter, includeSensors))
|
|
2233
|
+
{
|
|
2234
|
+
const distance2 = result.pos.distanceSquared(pos);
|
|
2235
|
+
if (!bestResult || distance2 < bestDistance2)
|
|
2236
|
+
{
|
|
2237
|
+
bestResult = result;
|
|
2238
|
+
bestDistance2 = distance2;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
return bestResult;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/** point cast and return the first object
|
|
2245
|
+
* @param {Vector2} pos
|
|
2246
|
+
* @param {boolean} [dynamicOnly]
|
|
2247
|
+
* @param {boolean} [includeSensors] - Also find sensors, so a pickup radius does not grab its object from afar
|
|
2248
|
+
* @return {Box2dObject|undefined} */
|
|
2249
|
+
pointCast(pos, dynamicOnly=true, includeSensors=false)
|
|
2250
|
+
{
|
|
2251
|
+
const queryCallback = box2dQueryObject('query', 'JSQueryCallback');
|
|
2252
|
+
queryCallback.ReportFixture = function(fixturePointer)
|
|
2253
|
+
{
|
|
2254
|
+
const fixture = box2d.instance.wrapPointer(fixturePointer, box2d.instance.b2Fixture);
|
|
2255
|
+
if (!includeSensors && fixture.IsSensor())
|
|
2256
|
+
return true; // a trigger zone, continue getting results
|
|
2257
|
+
if (dynamicOnly && fixture.GetBody().GetType() !== box2d.bodyTypeDynamic)
|
|
2258
|
+
return true; // continue getting results
|
|
2259
|
+
if (!fixture.TestPoint(box2dTemp(pos)))
|
|
2260
|
+
return true; // continue getting results
|
|
2261
|
+
const o = fixture.GetBody().object;
|
|
2262
|
+
if (!o || o.destroyed)
|
|
2263
|
+
return true; // a raw body with no Box2dObject or one destroyed this step, continue getting results
|
|
2264
|
+
queryObject = o;
|
|
2265
|
+
return false; // stop getting results
|
|
2266
|
+
};
|
|
2267
|
+
|
|
2268
|
+
const aabb = box2dQueryObject('aabb', 'b2AABB');
|
|
2269
|
+
aabb.set_lowerBound(box2dTemp(pos));
|
|
2270
|
+
aabb.set_upperBound(box2dTemp(pos));
|
|
2271
|
+
|
|
2272
|
+
let queryObject;
|
|
2273
|
+
box2d.world.QueryAABB(queryCallback, aabb);
|
|
2274
|
+
debugRaycast && debugRect(pos, vec2(), queryObject ? '#f00' : '#00f', 0, 0, false, false);
|
|
2275
|
+
return queryObject;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2279
|
+
// drawing
|
|
2280
|
+
|
|
2281
|
+
/** draws a fixture
|
|
2282
|
+
* @param {Object} fixture
|
|
2283
|
+
* @param {Vector2} pos
|
|
2284
|
+
* @param {number} angle
|
|
2285
|
+
* @param {Color} [color]
|
|
2286
|
+
* @param {Color} [lineColor]
|
|
2287
|
+
* @param {number} [lineWidth]
|
|
2288
|
+
* @param {boolean} [useWebGL=glEnable]
|
|
2289
|
+
* @param {CanvasRenderingContext2D} [context] */
|
|
2290
|
+
drawFixture(fixture, pos, angle, color=WHITE, lineColor=BLACK, lineWidth=.1, useWebGL, context)
|
|
2291
|
+
{
|
|
2292
|
+
const shape = box2d.castShapeObject(fixture.GetShape());
|
|
2293
|
+
switch (shape.GetType())
|
|
2294
|
+
{
|
|
2295
|
+
case box2d.instance.b2Shape.e_polygon:
|
|
2296
|
+
{
|
|
2297
|
+
let points = [];
|
|
2298
|
+
for (let i=shape.GetVertexCount(); i--;)
|
|
2299
|
+
points.push(box2d.vec2From(shape.GetVertex(i)));
|
|
2300
|
+
drawPoly(points, color, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
2301
|
+
break;
|
|
2302
|
+
}
|
|
2303
|
+
case box2d.instance.b2Shape.e_circle:
|
|
2304
|
+
{
|
|
2305
|
+
const radius = shape.get_m_radius(), offset = box2d.vec2From(shape.get_m_p());
|
|
2306
|
+
drawCircle(pos.add(offset.rotate(angle)), radius*2, color, lineWidth, lineColor, useWebGL, false, context);
|
|
2307
|
+
break;
|
|
2308
|
+
}
|
|
2309
|
+
case box2d.instance.b2Shape.e_edge:
|
|
2310
|
+
{
|
|
2311
|
+
const v1 = box2d.vec2From(shape.get_m_vertex1());
|
|
2312
|
+
const v2 = box2d.vec2From(shape.get_m_vertex2());
|
|
2313
|
+
drawLine(v1, v2, lineWidth, lineColor, pos, angle, useWebGL, false, context);
|
|
2314
|
+
break;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2320
|
+
// helper functions
|
|
2321
|
+
|
|
2322
|
+
/** converts a box2d vec2 to a Vector2
|
|
2323
|
+
* @param {Object} v */
|
|
2324
|
+
vec2From(v)
|
|
2325
|
+
{
|
|
2326
|
+
ASSERT(v instanceof box2d.instance.b2Vec2);
|
|
2327
|
+
return new Vector2(v.get_x(), v.get_y());
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
/** converts a box2d vec2 pointer to a Vector2
|
|
2331
|
+
* @param {Object} vp */
|
|
2332
|
+
vec2FromPointer(vp)
|
|
2333
|
+
{
|
|
2334
|
+
const v = box2d.instance.wrapPointer(vp, box2d.instance.b2Vec2);
|
|
2335
|
+
return box2d.vec2From(v);
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
/** converts a Vector2 to a new box2d vec2, which stays until destroyed with box2d.instance.destroy;
|
|
2339
|
+
* the plugin itself passes Box2D reused ones, since Box2D copies every vector it is given
|
|
2340
|
+
* @param {Vector2} v */
|
|
2341
|
+
vec2dTo(v)
|
|
2342
|
+
{
|
|
2343
|
+
ASSERT(isVector2(v));
|
|
2344
|
+
return new box2d.instance.b2Vec2(v.x, v.y);
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
/** checks if a box2d object is null
|
|
2348
|
+
* @param {Object} o */
|
|
2349
|
+
isNull(o) { return !box2d.instance.getPointer(o); }
|
|
2350
|
+
|
|
2351
|
+
/** casts a box2d object to a shape type
|
|
2352
|
+
* @param {Object} o */
|
|
2353
|
+
castShapeObject(o)
|
|
2354
|
+
{
|
|
2355
|
+
switch (o.GetType())
|
|
2356
|
+
{
|
|
2357
|
+
case box2d.instance.b2Shape.e_circle:
|
|
2358
|
+
return box2d.instance.castObject(o, box2d.instance.b2CircleShape);
|
|
2359
|
+
case box2d.instance.b2Shape.e_edge:
|
|
2360
|
+
return box2d.instance.castObject(o, box2d.instance.b2EdgeShape);
|
|
2361
|
+
case box2d.instance.b2Shape.e_polygon:
|
|
2362
|
+
return box2d.instance.castObject(o, box2d.instance.b2PolygonShape);
|
|
2363
|
+
case box2d.instance.b2Shape.e_chain:
|
|
2364
|
+
return box2d.instance.castObject(o, box2d.instance.b2ChainShape);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
ASSERT(false, 'Unknown box2d object type');
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
/** casts a box2d object to a joint type
|
|
2371
|
+
* @param {Object} o */
|
|
2372
|
+
castJointObject(o)
|
|
2373
|
+
{
|
|
2374
|
+
switch (o.GetType())
|
|
2375
|
+
{
|
|
2376
|
+
case box2d.instance.e_revoluteJoint:
|
|
2377
|
+
return box2d.instance.castObject(o, box2d.instance.b2RevoluteJoint);
|
|
2378
|
+
case box2d.instance.e_prismaticJoint:
|
|
2379
|
+
return box2d.instance.castObject(o, box2d.instance.b2PrismaticJoint);
|
|
2380
|
+
case box2d.instance.e_distanceJoint:
|
|
2381
|
+
return box2d.instance.castObject(o, box2d.instance.b2DistanceJoint);
|
|
2382
|
+
case box2d.instance.e_pulleyJoint:
|
|
2383
|
+
return box2d.instance.castObject(o, box2d.instance.b2PulleyJoint);
|
|
2384
|
+
case box2d.instance.e_mouseJoint:
|
|
2385
|
+
return box2d.instance.castObject(o, box2d.instance.b2MouseJoint);
|
|
2386
|
+
case box2d.instance.e_gearJoint:
|
|
2387
|
+
return box2d.instance.castObject(o, box2d.instance.b2GearJoint);
|
|
2388
|
+
case box2d.instance.e_wheelJoint:
|
|
2389
|
+
return box2d.instance.castObject(o, box2d.instance.b2WheelJoint);
|
|
2390
|
+
case box2d.instance.e_weldJoint:
|
|
2391
|
+
return box2d.instance.castObject(o, box2d.instance.b2WeldJoint);
|
|
2392
|
+
case box2d.instance.e_frictionJoint:
|
|
2393
|
+
return box2d.instance.castObject(o, box2d.instance.b2FrictionJoint);
|
|
2394
|
+
case box2d.instance.e_ropeJoint:
|
|
2395
|
+
return box2d.instance.castObject(o, box2d.instance.b2RopeJoint);
|
|
2396
|
+
case box2d.instance.e_motorJoint:
|
|
2397
|
+
return box2d.instance.castObject(o, box2d.instance.b2MotorJoint);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
ASSERT(false, 'Unknown box2d object type');
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2405
|
+
/** Box2d Init - Call with await to init box2d
|
|
2406
|
+
* @example
|
|
2407
|
+
* await box2dInit();
|
|
2408
|
+
* @return {Promise<Box2dPlugin>}
|
|
2409
|
+
* @memberof Box2D */
|
|
2410
|
+
async function box2dInit()
|
|
2411
|
+
{
|
|
2412
|
+
// load box2d
|
|
2413
|
+
// @ts-ignore - Box2D is the global that box2d.wasm.js defines
|
|
2414
|
+
new Box2dPlugin(await Box2D());
|
|
2415
|
+
setupDebugDraw();
|
|
2416
|
+
engineAddPlugin(box2dUpdate, box2dRender);
|
|
2417
|
+
return box2d;
|
|
2418
|
+
|
|
2419
|
+
// add the box2d plugin to the engine
|
|
2420
|
+
function box2dUpdate()
|
|
2421
|
+
{
|
|
2422
|
+
// frozen like the engine objects while paused or time is stopped
|
|
2423
|
+
if (paused || !timeScale)
|
|
2424
|
+
{
|
|
2425
|
+
// what was destroyed while frozen leaves the list, the step does it otherwise
|
|
2426
|
+
box2d.objects = box2d.objects.filter(o=>!o.destroyed);
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
box2d.step();
|
|
2431
|
+
|
|
2432
|
+
// copy box2d physics results to engine objects
|
|
2433
|
+
for (const o of box2d.objects)
|
|
2434
|
+
{
|
|
2435
|
+
if (o.body)
|
|
2436
|
+
{
|
|
2437
|
+
// box2d uses reverse angle
|
|
2438
|
+
o.pos = box2d.vec2From(o.body.GetPosition());
|
|
2439
|
+
o.angle = -o.body.GetAngle();
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
function box2dRender()
|
|
2444
|
+
{
|
|
2445
|
+
if (box2dDebug || debugPhysics)
|
|
2446
|
+
box2d.world.DrawDebugData();
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// box2d debug drawing
|
|
2450
|
+
function setupDebugDraw()
|
|
2451
|
+
{
|
|
2452
|
+
// setup debug draw
|
|
2453
|
+
const debugLineWidth = .1;
|
|
2454
|
+
const debugDraw = new box2d.instance.JSDraw();
|
|
2455
|
+
const box2dColor = (c)=> new Color(c.get_r(), c.get_g(), c.get_b());
|
|
2456
|
+
const box2dColorPointer = (c)=>
|
|
2457
|
+
box2dColor(box2d.instance.wrapPointer(c, box2d.instance.b2Color));
|
|
2458
|
+
const getDebugColor = (color)=>box2dColorPointer(color).scale(1,.8);
|
|
2459
|
+
const getPointsList = (vertices, vertexCount)=>
|
|
2460
|
+
{
|
|
2461
|
+
const points = [];
|
|
2462
|
+
for (let i=vertexCount; i--;)
|
|
2463
|
+
points.push(box2d.vec2FromPointer(vertices+i*8));
|
|
2464
|
+
return points;
|
|
2465
|
+
}
|
|
2466
|
+
debugDraw.DrawSegment = function(point1, point2, color)
|
|
2467
|
+
{
|
|
2468
|
+
color = getDebugColor(color);
|
|
2469
|
+
point1 = box2d.vec2FromPointer(point1);
|
|
2470
|
+
point2 = box2d.vec2FromPointer(point2);
|
|
2471
|
+
drawLine(point1, point2, debugLineWidth, color, vec2(), 0, false, false);
|
|
2472
|
+
};
|
|
2473
|
+
debugDraw.DrawPolygon = function(vertices, vertexCount, color)
|
|
2474
|
+
{
|
|
2475
|
+
color = getDebugColor(color);
|
|
2476
|
+
const points = getPointsList(vertices, vertexCount);
|
|
2477
|
+
drawPoly(points, CLEAR_WHITE, debugLineWidth, color, vec2(), 0, false, false);
|
|
2478
|
+
};
|
|
2479
|
+
debugDraw.DrawSolidPolygon = function(vertices, vertexCount, color)
|
|
2480
|
+
{
|
|
2481
|
+
color = getDebugColor(color);
|
|
2482
|
+
const points = getPointsList(vertices, vertexCount);
|
|
2483
|
+
drawPoly(points, color, 0, color, vec2(), 0, false, false);
|
|
2484
|
+
};
|
|
2485
|
+
debugDraw.DrawCircle = function(center, radius, color)
|
|
2486
|
+
{
|
|
2487
|
+
color = getDebugColor(color);
|
|
2488
|
+
center = box2d.vec2FromPointer(center);
|
|
2489
|
+
drawCircle(center, radius*2, CLEAR_WHITE, debugLineWidth, color, false, false);
|
|
2490
|
+
};
|
|
2491
|
+
debugDraw.DrawSolidCircle = function(center, radius, axis, color)
|
|
2492
|
+
{
|
|
2493
|
+
color = getDebugColor(color);
|
|
2494
|
+
center = box2d.vec2FromPointer(center);
|
|
2495
|
+
axis = box2d.vec2FromPointer(axis).scale(radius);
|
|
2496
|
+
drawCircle(center, radius*2, color, debugLineWidth, color, false, false);
|
|
2497
|
+
drawLine(vec2(), axis, debugLineWidth, color, center, 0, false, false);
|
|
2498
|
+
};
|
|
2499
|
+
debugDraw.DrawTransform = function(transform)
|
|
2500
|
+
{
|
|
2501
|
+
transform = box2d.instance.wrapPointer(transform, box2d.instance.b2Transform);
|
|
2502
|
+
const pos = box2d.vec2From(transform.get_p());
|
|
2503
|
+
const angle = -transform.get_q().GetAngle();
|
|
2504
|
+
const p1 = vec2(1,0), c1 = rgb(.75,0,0,.8);
|
|
2505
|
+
const p2 = vec2(0,1), c2 = rgb(0,.75,0,.8);
|
|
2506
|
+
drawLine(vec2(), p1, debugLineWidth, c1, pos, angle, false, false);
|
|
2507
|
+
drawLine(vec2(), p2, debugLineWidth, c2, pos, angle, false, false);
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
debugDraw.AppendFlags(box2d.instance.b2Draw.e_shapeBit);
|
|
2511
|
+
debugDraw.AppendFlags(box2d.instance.b2Draw.e_jointBit);
|
|
2512
|
+
//debugDraw.AppendFlags(box2d.instance.b2Draw.e_aabbBit);
|
|
2513
|
+
//debugDraw.AppendFlags(box2d.instance.b2Draw.e_pairBit);
|
|
2514
|
+
//debugDraw.AppendFlags(box2d.instance.b2Draw.e_centerOfMassBit);
|
|
2515
|
+
box2d.world.SetDebugDraw(debugDraw);
|
|
2516
|
+
}
|
|
2074
2517
|
}
|