reze-engine 0.16.3 → 0.17.1
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/README.md +252 -250
- package/dist/engine.d.ts +3 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +160 -21
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/model.d.ts +1 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/physics/body.d.ts +3 -1
- package/dist/physics/body.d.ts.map +1 -1
- package/dist/physics/body.js +84 -20
- package/dist/physics/constraint.d.ts +14 -1
- package/dist/physics/constraint.d.ts.map +1 -1
- package/dist/physics/constraint.js +43 -1
- package/dist/physics/solver.d.ts.map +1 -1
- package/dist/physics/solver.js +278 -110
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +1 -0
- package/dist/shaders/materials/body.d.ts +1 -1
- package/dist/shaders/materials/body.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_rough.d.ts +1 -1
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/materials/common.d.ts +1 -1
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +137 -122
- package/dist/shaders/materials/default.d.ts +1 -1
- package/dist/shaders/materials/default.d.ts.map +1 -1
- package/dist/shaders/materials/eye.d.ts +1 -1
- package/dist/shaders/materials/eye.d.ts.map +1 -1
- package/dist/shaders/materials/eye.js +14 -0
- package/dist/shaders/materials/face.d.ts +1 -1
- package/dist/shaders/materials/face.d.ts.map +1 -1
- package/dist/shaders/materials/hair.d.ts +1 -1
- package/dist/shaders/materials/hair.d.ts.map +1 -1
- package/dist/shaders/materials/metal.d.ts +1 -1
- package/dist/shaders/materials/metal.d.ts.map +1 -1
- package/dist/shaders/materials/mmd_classic.d.ts +2 -0
- package/dist/shaders/materials/mmd_classic.d.ts.map +1 -0
- package/dist/shaders/materials/mmd_classic.js +66 -0
- package/dist/shaders/materials/stockings.d.ts +1 -1
- package/dist/shaders/materials/stockings.d.ts.map +1 -1
- package/package.json +42 -42
- package/src/engine.ts +4204 -4045
- package/src/index.ts +29 -28
- package/src/model.ts +3 -0
- package/src/physics/body.ts +82 -24
- package/src/physics/constraint.ts +74 -5
- package/src/physics/solver.ts +915 -752
- package/src/pmx-loader.ts +1 -0
- package/src/shaders/materials/common.ts +204 -189
- package/src/shaders/materials/eye.ts +14 -0
- package/src/shaders/materials/mmd_classic.ts +68 -0
package/src/physics/solver.ts
CHANGED
|
@@ -1,752 +1,915 @@
|
|
|
1
|
-
// 6DOF spring + contact constraint solver. Sequential-impulse projected
|
|
2
|
-
// Gauss-Seidel: per axis, target a relative velocity (limit correction +
|
|
3
|
-
// spring), apply the impulse needed to reach it. Friction is two Coulomb
|
|
4
|
-
// rows per contact, normal is push-only.
|
|
5
|
-
//
|
|
6
|
-
// Two passes per substep:
|
|
7
|
-
// 1. SETUP — for each constraint and contact, compute every quantity that
|
|
8
|
-
// doesn't depend on lv/av (world axes, lever arms, Jacobian denominators,
|
|
9
|
-
// target velocities, friction tangent bases, restitution reference).
|
|
10
|
-
// These are constant during solve since pos/ori/inertia don't change.
|
|
11
|
-
// 2. ITERATE — `iterations` passes that read the cache and apply impulses
|
|
12
|
-
// based on the current lv/av. ~2× faster than recomputing per iter.
|
|
13
|
-
|
|
14
|
-
import { Mat4 } from "../math"
|
|
15
|
-
import type { RigidBodyStore } from "./body"
|
|
16
|
-
import type { SixDofSpringConstraint } from "./constraint"
|
|
17
|
-
import { STOP_ERP } from "./constraint"
|
|
18
|
-
import type { Contact, ContactPool } from "./contact"
|
|
19
|
-
|
|
20
|
-
const BOUNCE_THRESHOLD = 2.0
|
|
21
|
-
|
|
22
|
-
// Ceilings on limit-correction velocity. In normal operation limit errors are
|
|
23
|
-
// tiny; a large error only appears after a discontinuity (teleport, stall,
|
|
24
|
-
// deep penetration), and feeding err·ERP/dt to the solver unclamped then
|
|
25
|
-
// injects explosion-scale impulses into the chain.
|
|
26
|
-
const MAX_LINEAR_CORRECTION_VEL = 120 // units/s
|
|
27
|
-
const MAX_ANGULAR_CORRECTION_VEL = 30 // rad/s
|
|
28
|
-
|
|
29
|
-
// Bullet's limit-motor softness defaults (0.7 translational, 0.5 rotational):
|
|
30
|
-
// scale each iteration's limit impulse so the stop engages progressively
|
|
31
|
-
// instead of as a hard velocity snap.
|
|
32
|
-
const LIMIT_SOFTNESS_LINEAR = 0.7
|
|
33
|
-
const LIMIT_SOFTNESS_ANGULAR = 0.5
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
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
|
-
dt
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
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
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
const
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const
|
|
523
|
-
const
|
|
524
|
-
const
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
const
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
const
|
|
620
|
-
const
|
|
621
|
-
if (imA > 0) {
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
av[
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const
|
|
646
|
-
const
|
|
647
|
-
const
|
|
648
|
-
const
|
|
649
|
-
const
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
const
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
lv
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const
|
|
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
|
-
|
|
1
|
+
// 6DOF spring + contact constraint solver. Sequential-impulse projected
|
|
2
|
+
// Gauss-Seidel: per axis, target a relative velocity (limit correction +
|
|
3
|
+
// spring), apply the impulse needed to reach it. Friction is two Coulomb
|
|
4
|
+
// rows per contact, normal is push-only.
|
|
5
|
+
//
|
|
6
|
+
// Two passes per substep:
|
|
7
|
+
// 1. SETUP — for each constraint and contact, compute every quantity that
|
|
8
|
+
// doesn't depend on lv/av (world axes, lever arms, Jacobian denominators,
|
|
9
|
+
// target velocities, friction tangent bases, restitution reference).
|
|
10
|
+
// These are constant during solve since pos/ori/inertia don't change.
|
|
11
|
+
// 2. ITERATE — `iterations` passes that read the cache and apply impulses
|
|
12
|
+
// based on the current lv/av. ~2× faster than recomputing per iter.
|
|
13
|
+
|
|
14
|
+
import { Mat4 } from "../math"
|
|
15
|
+
import type { RigidBodyStore } from "./body"
|
|
16
|
+
import type { SixDofSpringConstraint } from "./constraint"
|
|
17
|
+
import { STOP_ERP } from "./constraint"
|
|
18
|
+
import type { Contact, ContactPool } from "./contact"
|
|
19
|
+
|
|
20
|
+
const BOUNCE_THRESHOLD = 2.0
|
|
21
|
+
|
|
22
|
+
// Ceilings on limit-correction velocity. In normal operation limit errors are
|
|
23
|
+
// tiny; a large error only appears after a discontinuity (teleport, stall,
|
|
24
|
+
// deep penetration), and feeding err·ERP/dt to the solver unclamped then
|
|
25
|
+
// injects explosion-scale impulses into the chain.
|
|
26
|
+
const MAX_LINEAR_CORRECTION_VEL = 120 // units/s
|
|
27
|
+
const MAX_ANGULAR_CORRECTION_VEL = 30 // rad/s
|
|
28
|
+
|
|
29
|
+
// Bullet's limit-motor softness defaults (0.7 translational, 0.5 rotational):
|
|
30
|
+
// scale each iteration's limit impulse so the stop engages progressively
|
|
31
|
+
// instead of as a hard velocity snap.
|
|
32
|
+
const LIMIT_SOFTNESS_LINEAR = 0.7
|
|
33
|
+
const LIMIT_SOFTNESS_ANGULAR = 0.5
|
|
34
|
+
|
|
35
|
+
// Bullet spring-motor velocity factor: target velocity = (fps/numIterations)
|
|
36
|
+
// × spring force, with the applied impulse clamped to the real spring force
|
|
37
|
+
// k·|err|·dt. The big target makes the row deliver its full physical force
|
|
38
|
+
// every substep (stiff, MMD-authored hold); the clamp keeps it from ever
|
|
39
|
+
// exceeding it (no energy pumping). Matches btGeneric6DofSpringConstraint
|
|
40
|
+
// with springDamping=1 and the default 10 solver iterations.
|
|
41
|
+
const SPRING_SOLVER_ITERATIONS = 10
|
|
42
|
+
// Slack on the spring impulse clamp: MMD rigs are authored against Bullet's
|
|
43
|
+
// spring motors, which deliver several times the naive k·err·dt per substep
|
|
44
|
+
// through their solver-row impulse bounds. Bounded slack keeps the hold
|
|
45
|
+
// stiff (breasts/hair rest near the authored pose) while still capping the
|
|
46
|
+
// energy any spring can inject per substep (the slow-boil guard).
|
|
47
|
+
const SPRING_FORCE_SLACK = 4
|
|
48
|
+
|
|
49
|
+
// ERP scale for loop-closing constraints (see buildConstraints: joints that
|
|
50
|
+
// close a cycle in the joint graph, e.g. the horizontal ring welds of
|
|
51
|
+
// cross-linked skirt lattices). A loop over-determines positions — when
|
|
52
|
+
// contacts push the lattice, the ring's errors cannot all reach zero, and
|
|
53
|
+
// full-rate corrections chase each other around the cycle as violent
|
|
54
|
+
// chatter. Loop edges keep shape at a fraction of the correction rate while
|
|
55
|
+
// the spanning-tree chains stay stiff.
|
|
56
|
+
const LOOP_ERP_SCALE = 1.0
|
|
57
|
+
// Loop-edge LOCKED axes are converted to force-clamped springs instead of
|
|
58
|
+
// weld rows: an equality row on a cycle fights the other cycle edges at any
|
|
59
|
+
// ERP (the velocity system is over-determined too). A spring bounded by its
|
|
60
|
+
// real force k·|err|·dt holds the ring's shape elastically without fighting.
|
|
61
|
+
const LOOP_SPRING_K = 900
|
|
62
|
+
// Angular limit violations below this switch to per-axis euler rows; above
|
|
63
|
+
// it, the single geodesic row takes over (see setupConstraint).
|
|
64
|
+
const GEODESIC_THRESHOLD = 0.5 // rad
|
|
65
|
+
|
|
66
|
+
// Module-level scratch (no per-iter allocations).
|
|
67
|
+
const _TA = new Float32Array(16)
|
|
68
|
+
const _TB = new Float32Array(16)
|
|
69
|
+
const _bodyMatA = new Float32Array(16)
|
|
70
|
+
const _bodyMatB = new Float32Array(16)
|
|
71
|
+
const _angDiffScratch = new Float32Array(3)
|
|
72
|
+
const _quatScratchA = new Float32Array(4)
|
|
73
|
+
const _quatScratchB = new Float32Array(4)
|
|
74
|
+
|
|
75
|
+
export function solveConstraints(
|
|
76
|
+
store: RigidBodyStore,
|
|
77
|
+
constraints: SixDofSpringConstraint[],
|
|
78
|
+
contacts: ContactPool,
|
|
79
|
+
dt: number,
|
|
80
|
+
iterations: number,
|
|
81
|
+
): void {
|
|
82
|
+
if (dt <= 0) return
|
|
83
|
+
if (constraints.length === 0 && contacts.count === 0) return
|
|
84
|
+
|
|
85
|
+
const invDt = 1 / dt
|
|
86
|
+
const lv = store.linearVelocities
|
|
87
|
+
const av = store.angularVelocities
|
|
88
|
+
const invMass = store.invMass
|
|
89
|
+
|
|
90
|
+
// World-space inverse inertia tensors for this substep's poses.
|
|
91
|
+
store.updateInvInertiaWorld()
|
|
92
|
+
const W = store.invInertiaWorld
|
|
93
|
+
|
|
94
|
+
for (let c = 0; c < constraints.length; c++) {
|
|
95
|
+
setupConstraint(constraints[c], store, dt, invDt)
|
|
96
|
+
}
|
|
97
|
+
for (let ci = 0; ci < contacts.count; ci++) {
|
|
98
|
+
setupContactRow(contacts.get(ci), lv, av, invMass, W)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
102
|
+
for (let c = 0; c < constraints.length; c++) {
|
|
103
|
+
iterateConstraint(constraints[c], lv, av, invMass)
|
|
104
|
+
}
|
|
105
|
+
for (let ci = 0; ci < contacts.count; ci++) {
|
|
106
|
+
iterateContactRow(contacts.get(ci), lv, av, invMass)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// SETUP: compute everything that doesn't depend on velocities. Caller
|
|
112
|
+
// guarantees pos/ori don't change between this and the iter loop.
|
|
113
|
+
function setupConstraint(
|
|
114
|
+
con: SixDofSpringConstraint,
|
|
115
|
+
store: RigidBodyStore,
|
|
116
|
+
dt: number,
|
|
117
|
+
invDt: number,
|
|
118
|
+
): void {
|
|
119
|
+
const a = con.bodyA
|
|
120
|
+
const b = con.bodyB
|
|
121
|
+
const imA = store.invMass[a]
|
|
122
|
+
const imB = store.invMass[b]
|
|
123
|
+
const W = store.invInertiaWorld
|
|
124
|
+
const a9 = a * 9
|
|
125
|
+
const b9 = b * 9
|
|
126
|
+
|
|
127
|
+
con.cacheSkip = imA === 0 && imB === 0
|
|
128
|
+
if (con.cacheSkip) return
|
|
129
|
+
|
|
130
|
+
const erpScale = con.isLoop ? LOOP_ERP_SCALE : 1.0
|
|
131
|
+
|
|
132
|
+
buildBodyMat(store, a, _bodyMatA)
|
|
133
|
+
buildBodyMat(store, b, _bodyMatB)
|
|
134
|
+
Mat4.multiplyArrays(_bodyMatA, 0, con.frameA, 0, _TA, 0)
|
|
135
|
+
Mat4.multiplyArrays(_bodyMatB, 0, con.frameB, 0, _TB, 0)
|
|
136
|
+
|
|
137
|
+
// Per-body pivots at each body's own joint-frame origin (Spring2-style).
|
|
138
|
+
// Bullet 2.7x's shared mass-weighted anchor (m_AnchorPos) degenerates when
|
|
139
|
+
// the joint is violated by a large distance: the midpoint sits far from
|
|
140
|
+
// both bodies, the lever arms grow with the separation, the Jacobian
|
|
141
|
+
// denominator blows up as err²·invInertia, and the row applies torque
|
|
142
|
+
// instead of closing velocity — the joint "breaks" and the error runs
|
|
143
|
+
// away. Per-body pivots keep the levers bounded by the frame offsets, so
|
|
144
|
+
// the row stays effective no matter how large the violation is.
|
|
145
|
+
const pos = store.positions
|
|
146
|
+
const ai = a * 3
|
|
147
|
+
const bi = b * 3
|
|
148
|
+
const rAx = _TA[12] - pos[ai + 0]
|
|
149
|
+
const rAy = _TA[13] - pos[ai + 1]
|
|
150
|
+
const rAz = _TA[14] - pos[ai + 2]
|
|
151
|
+
const rBx = _TB[12] - pos[bi + 0]
|
|
152
|
+
const rBy = _TB[13] - pos[bi + 1]
|
|
153
|
+
const rBz = _TB[14] - pos[bi + 2]
|
|
154
|
+
const lA = con.cacheLeverA
|
|
155
|
+
const lB = con.cacheLeverB
|
|
156
|
+
lA[0] = rAx; lA[1] = rAy; lA[2] = rAz
|
|
157
|
+
lB[0] = rBx; lB[1] = rBy; lB[2] = rBz
|
|
158
|
+
|
|
159
|
+
// linearDiff = TA.basis^T · (TB.origin − TA.origin); axes = TA columns 0/1/2.
|
|
160
|
+
const dxw = _TB[12] - _TA[12]
|
|
161
|
+
const dyw = _TB[13] - _TA[13]
|
|
162
|
+
const dzw = _TB[14] - _TA[14]
|
|
163
|
+
const linDiff0 = _TA[0] * dxw + _TA[1] * dyw + _TA[2] * dzw
|
|
164
|
+
const linDiff1 = _TA[4] * dxw + _TA[5] * dyw + _TA[6] * dzw
|
|
165
|
+
const linDiff2 = _TA[8] * dxw + _TA[9] * dyw + _TA[10] * dzw
|
|
166
|
+
|
|
167
|
+
const axes = con.cacheLinAxes
|
|
168
|
+
const cA = con.cacheLinCrossA
|
|
169
|
+
const cB = con.cacheLinCrossB
|
|
170
|
+
const jac = con.cacheLinJacInv
|
|
171
|
+
const tgt = con.cacheLinTargetVel
|
|
172
|
+
const act = con.cacheLinActive
|
|
173
|
+
|
|
174
|
+
for (let i = 0; i < 3; i++) {
|
|
175
|
+
const o = i * 3
|
|
176
|
+
const axx = i === 0 ? _TA[0] : i === 1 ? _TA[4] : _TA[8]
|
|
177
|
+
const axy = i === 0 ? _TA[1] : i === 1 ? _TA[5] : _TA[9]
|
|
178
|
+
const axz = i === 0 ? _TA[2] : i === 1 ? _TA[6] : _TA[10]
|
|
179
|
+
axes[o + 0] = axx
|
|
180
|
+
axes[o + 1] = axy
|
|
181
|
+
axes[o + 2] = axz
|
|
182
|
+
|
|
183
|
+
const cAx = rAy * axz - rAz * axy
|
|
184
|
+
const cAy = rAz * axx - rAx * axz
|
|
185
|
+
const cAz = rAx * axy - rAy * axx
|
|
186
|
+
const cBx = rBy * axz - rBz * axy
|
|
187
|
+
const cBy = rBz * axx - rBx * axz
|
|
188
|
+
const cBz = rBx * axy - rBy * axx
|
|
189
|
+
// Tensor-multiplied lever crosses: cache I⁻¹·(r×ax) for application;
|
|
190
|
+
// denominator = (r×ax)ᵀ·I⁻¹·(r×ax).
|
|
191
|
+
const wAx = W[a9 + 0] * cAx + W[a9 + 1] * cAy + W[a9 + 2] * cAz
|
|
192
|
+
const wAy = W[a9 + 3] * cAx + W[a9 + 4] * cAy + W[a9 + 5] * cAz
|
|
193
|
+
const wAz = W[a9 + 6] * cAx + W[a9 + 7] * cAy + W[a9 + 8] * cAz
|
|
194
|
+
const wBx = W[b9 + 0] * cBx + W[b9 + 1] * cBy + W[b9 + 2] * cBz
|
|
195
|
+
const wBy = W[b9 + 3] * cBx + W[b9 + 4] * cBy + W[b9 + 5] * cBz
|
|
196
|
+
const wBz = W[b9 + 6] * cBx + W[b9 + 7] * cBy + W[b9 + 8] * cBz
|
|
197
|
+
cA[o + 0] = wAx; cA[o + 1] = wAy; cA[o + 2] = wAz
|
|
198
|
+
cB[o + 0] = wBx; cB[o + 1] = wBy; cB[o + 2] = wBz
|
|
199
|
+
|
|
200
|
+
const denom = imA + imB +
|
|
201
|
+
(cAx * wAx + cAy * wAy + cAz * wAz) +
|
|
202
|
+
(cBx * wBx + cBy * wBy + cBz * wBz)
|
|
203
|
+
jac[i] = denom > 0 ? 1 / denom : 0
|
|
204
|
+
|
|
205
|
+
const lo = con.linearMin[i]
|
|
206
|
+
const hi = con.linearMax[i]
|
|
207
|
+
const curr = i === 0 ? linDiff0 : i === 1 ? linDiff1 : linDiff2
|
|
208
|
+
// active: 1 = bilateral equality (locked axis — a joint, always on),
|
|
209
|
+
// 2 = unilateral stop (ranged axis in violation).
|
|
210
|
+
let target = 0
|
|
211
|
+
let active = 0
|
|
212
|
+
if (lo <= hi) {
|
|
213
|
+
let err = 0
|
|
214
|
+
if (curr < lo) err = curr - lo
|
|
215
|
+
else if (curr > hi) err = curr - hi
|
|
216
|
+
if (lo === hi) active = 1
|
|
217
|
+
else if (err !== 0) active = 2
|
|
218
|
+
if (err !== 0) {
|
|
219
|
+
target = -err * STOP_ERP * erpScale * invDt
|
|
220
|
+
if (target > MAX_LINEAR_CORRECTION_VEL) target = MAX_LINEAR_CORRECTION_VEL
|
|
221
|
+
else if (target < -MAX_LINEAR_CORRECTION_VEL) target = -MAX_LINEAR_CORRECTION_VEL
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
tgt[i] = target
|
|
225
|
+
act[i] = denom > 0 ? active : 0
|
|
226
|
+
con.cacheLinLimitImp[i] = 0
|
|
227
|
+
// A spring on a locked axis is redundant — the bilateral limit row
|
|
228
|
+
// already welds the DOF, and driving it twice overshoots every
|
|
229
|
+
// iteration (PMX rigs routinely put k=100000 springs on locked axes,
|
|
230
|
+
// which turned welded weight-bodies into energy pumps).
|
|
231
|
+
if (con.springEnabled[i] && denom > 0 && lo !== hi) {
|
|
232
|
+
// Clamp k to the deadbeat limit: an explicit spring with k·dt² > 1
|
|
233
|
+
// overshoots equilibrium every step and pumps energy — the classic
|
|
234
|
+
// pre-Spring2 Bullet 6dof instability this port inherited.
|
|
235
|
+
const k = Math.min(con.springStiffness[i], invDt * invDt)
|
|
236
|
+
const serr = curr - con.equilibriumPoint[i]
|
|
237
|
+
con.cacheLinSpringTarget[i] = -k * serr * dt
|
|
238
|
+
// Boil guard: per-substep spring impulse bounded by (a slack over) the
|
|
239
|
+
// physical spring force — unbounded spring drives pump energy through
|
|
240
|
+
// the euler-axis mis-scale and slowly boil resting cloth.
|
|
241
|
+
con.cacheLinSpringMaxImp[i] = SPRING_FORCE_SLACK * Math.abs(k * serr) * dt
|
|
242
|
+
con.cacheLinSpringImp[i] = 0
|
|
243
|
+
con.cacheLinSpringActive[i] = 1
|
|
244
|
+
} else if (!(lo === hi && con.isLoop)) {
|
|
245
|
+
con.cacheLinSpringActive[i] = 0
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Angular: TA^T · TB → Euler XYZ; axes from TA.col2 × TB.col0.
|
|
250
|
+
const r00 = _TA[0]*_TB[0] + _TA[1]*_TB[1] + _TA[2]*_TB[2]
|
|
251
|
+
const r01 = _TA[0]*_TB[4] + _TA[1]*_TB[5] + _TA[2]*_TB[6]
|
|
252
|
+
const r10 = _TA[4]*_TB[0] + _TA[5]*_TB[1] + _TA[6]*_TB[2]
|
|
253
|
+
const r11 = _TA[4]*_TB[4] + _TA[5]*_TB[5] + _TA[6]*_TB[6]
|
|
254
|
+
const r20 = _TA[8]*_TB[0] + _TA[9]*_TB[1] + _TA[10]*_TB[2]
|
|
255
|
+
const r21 = _TA[8]*_TB[4] + _TA[9]*_TB[5] + _TA[10]*_TB[6]
|
|
256
|
+
const r22 = _TA[8]*_TB[8] + _TA[9]*_TB[9] + _TA[10]*_TB[10]
|
|
257
|
+
matrixToEulerXYZ(r00, r01, r10, r11, r20, r21, r22, _angDiffScratch)
|
|
258
|
+
|
|
259
|
+
const a2x = _TA[8], a2y = _TA[9], a2z = _TA[10]
|
|
260
|
+
const b0x = _TB[0], b0y = _TB[1], b0z = _TB[2]
|
|
261
|
+
let yx = a2y * b0z - a2z * b0y
|
|
262
|
+
let yy = a2z * b0x - a2x * b0z
|
|
263
|
+
let yz = a2x * b0y - a2y * b0x
|
|
264
|
+
let l = Math.hypot(yx, yy, yz)
|
|
265
|
+
if (l > 1e-8) { const inv = 1/l; yx*=inv; yy*=inv; yz*=inv }
|
|
266
|
+
let xx = yy * a2z - yz * a2y
|
|
267
|
+
let xy = yz * a2x - yx * a2z
|
|
268
|
+
let xz = yx * a2y - yy * a2x
|
|
269
|
+
l = Math.hypot(xx, xy, xz)
|
|
270
|
+
if (l > 1e-8) { const inv = 1/l; xx*=inv; xy*=inv; xz*=inv }
|
|
271
|
+
let zx = b0y * yz - b0z * yy
|
|
272
|
+
let zy = b0z * yx - b0x * yz
|
|
273
|
+
let zz = b0x * yy - b0y * yx
|
|
274
|
+
l = Math.hypot(zx, zy, zz)
|
|
275
|
+
if (l > 1e-8) { const inv = 1/l; zx*=inv; zy*=inv; zz*=inv }
|
|
276
|
+
|
|
277
|
+
const angAxes = con.cacheAngAxes
|
|
278
|
+
angAxes[0] = xx; angAxes[1] = xy; angAxes[2] = xz
|
|
279
|
+
angAxes[3] = yx; angAxes[4] = yy; angAxes[5] = yz
|
|
280
|
+
angAxes[6] = zx; angAxes[7] = zy; angAxes[8] = zz
|
|
281
|
+
|
|
282
|
+
// Per-axis angular Jacobians with the full tensors: cache I⁻¹·axis per
|
|
283
|
+
// body plus 1/(axᵀ(I⁻¹A+I⁻¹B)ax) per axis.
|
|
284
|
+
const angJac = con.cacheAngJacInv
|
|
285
|
+
const angWAs = con.cacheAngWA
|
|
286
|
+
const angWBs = con.cacheAngWB
|
|
287
|
+
for (let i = 0; i < 3; i++) {
|
|
288
|
+
const o = i * 3
|
|
289
|
+
const axx = angAxes[o + 0], axy = angAxes[o + 1], axz = angAxes[o + 2]
|
|
290
|
+
const wAx = W[a9 + 0] * axx + W[a9 + 1] * axy + W[a9 + 2] * axz
|
|
291
|
+
const wAy = W[a9 + 3] * axx + W[a9 + 4] * axy + W[a9 + 5] * axz
|
|
292
|
+
const wAz = W[a9 + 6] * axx + W[a9 + 7] * axy + W[a9 + 8] * axz
|
|
293
|
+
const wBx = W[b9 + 0] * axx + W[b9 + 1] * axy + W[b9 + 2] * axz
|
|
294
|
+
const wBy = W[b9 + 3] * axx + W[b9 + 4] * axy + W[b9 + 5] * axz
|
|
295
|
+
const wBz = W[b9 + 6] * axx + W[b9 + 7] * axy + W[b9 + 8] * axz
|
|
296
|
+
angWAs[o + 0] = wAx; angWAs[o + 1] = wAy; angWAs[o + 2] = wAz
|
|
297
|
+
angWBs[o + 0] = wBx; angWBs[o + 1] = wBy; angWBs[o + 2] = wBz
|
|
298
|
+
const denom = axx * (wAx + wBx) + axy * (wAy + wBy) + axz * (wAz + wBz)
|
|
299
|
+
angJac[i] = denom > 0 ? 1 / denom : 0
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Per-axis rows carry only the springs. Sign flip vs linear:
|
|
303
|
+
// d(angDiff)/dt = −(ω_B − ω_A)·ax.
|
|
304
|
+
const angTgt = con.cacheAngTargetVel
|
|
305
|
+
const angAct = con.cacheAngActive
|
|
306
|
+
for (let i = 0; i < 3; i++) {
|
|
307
|
+
const idx = i + 3
|
|
308
|
+
// Springs on locked axes are skipped — the limit row welds those, and
|
|
309
|
+
// double-driving a DOF overshoots every iteration (see the linear loop).
|
|
310
|
+
if (con.springEnabled[idx] && angJac[i] > 0 && con.angularMin[i] !== con.angularMax[i]) {
|
|
311
|
+
const k = Math.min(con.springStiffness[idx], invDt * invDt)
|
|
312
|
+
const serr = _angDiffScratch[i] - con.equilibriumPoint[idx]
|
|
313
|
+
angTgt[i] = k * serr * dt
|
|
314
|
+
con.cacheAngSpringMaxImp[i] = SPRING_FORCE_SLACK * Math.abs(k * serr) * dt
|
|
315
|
+
angAct[i] = 1
|
|
316
|
+
} else {
|
|
317
|
+
angTgt[i] = 0
|
|
318
|
+
angAct[i] = 0
|
|
319
|
+
}
|
|
320
|
+
con.cacheAngSpringImp[i] = 0
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Angular limit handling is hybrid. Small violations (the resting-cloth
|
|
324
|
+
// regime) use per-axis euler rows — they converge cleanly and keep resting
|
|
325
|
+
// cloth dead still. Large violations switch to a single geodesic row toward
|
|
326
|
+
// the euler-clamped target: per-axis euler rows (the Bullet-2.7x approach
|
|
327
|
+
// this port used) become geometrically inconsistent for large errors — near
|
|
328
|
+
// the asin singularity they chase phantom errors and pump angular velocity
|
|
329
|
+
// into the chain instead of converging.
|
|
330
|
+
con.cacheAngLimActive = 0
|
|
331
|
+
con.cacheAngPAActive[0] = 0
|
|
332
|
+
con.cacheAngPAActive[1] = 0
|
|
333
|
+
con.cacheAngPAActive[2] = 0
|
|
334
|
+
if (imA > 0 || imB > 0) {
|
|
335
|
+
const ex = _angDiffScratch[0], ey = _angDiffScratch[1], ez = _angDiffScratch[2]
|
|
336
|
+
// Free axes (min > max) follow the current angle, i.e. no correction.
|
|
337
|
+
let tx = ex, ty = ey, tz = ez
|
|
338
|
+
if (con.angularMin[0] <= con.angularMax[0]) tx = ex < con.angularMin[0] ? con.angularMin[0] : ex > con.angularMax[0] ? con.angularMax[0] : ex
|
|
339
|
+
if (con.angularMin[1] <= con.angularMax[1]) ty = ey < con.angularMin[1] ? con.angularMin[1] : ey > con.angularMax[1] ? con.angularMax[1] : ey
|
|
340
|
+
if (con.angularMin[2] <= con.angularMax[2]) tz = ez < con.angularMin[2] ? con.angularMin[2] : ez > con.angularMax[2] ? con.angularMax[2] : ez
|
|
341
|
+
const errX = ex - tx, errY = ey - ty, errZ = ez - tz
|
|
342
|
+
const maxErr = Math.max(Math.abs(errX), Math.abs(errY), Math.abs(errZ))
|
|
343
|
+
if (maxErr > 0 && maxErr < GEODESIC_THRESHOLD) {
|
|
344
|
+
// Per-axis euler limit rows. Locked axes are bilateral joints; ranged
|
|
345
|
+
// axes are unilateral stops (sign-clamped accumulation) — a bilateral
|
|
346
|
+
// row on a ranged axis brakes natural recovery every substep and
|
|
347
|
+
// pumps energy into swinging cloth, and the pump grows WITH solver
|
|
348
|
+
// convergence (more iterations enforce the brake harder).
|
|
349
|
+
for (let i = 0; i < 3; i++) {
|
|
350
|
+
const err = i === 0 ? errX : i === 1 ? errY : errZ
|
|
351
|
+
con.cacheAngPAImp[i] = 0
|
|
352
|
+
if (err === 0) {
|
|
353
|
+
con.cacheAngPAActive[i] = 0
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
let target = err * STOP_ERP * erpScale * invDt
|
|
357
|
+
if (target > MAX_ANGULAR_CORRECTION_VEL) target = MAX_ANGULAR_CORRECTION_VEL
|
|
358
|
+
else if (target < -MAX_ANGULAR_CORRECTION_VEL) target = -MAX_ANGULAR_CORRECTION_VEL
|
|
359
|
+
con.cacheAngPATarget[i] = target
|
|
360
|
+
con.cacheAngPAActive[i] = con.angularMin[i] === con.angularMax[i] ? 1 : 2
|
|
361
|
+
}
|
|
362
|
+
} else if (maxErr > 0) {
|
|
363
|
+
// Bilateral (equality) if any violated axis is locked — a locked axis
|
|
364
|
+
// is a joint, not a stop. Unilateral otherwise.
|
|
365
|
+
const bilateral =
|
|
366
|
+
(tx !== ex && con.angularMin[0] === con.angularMax[0]) ||
|
|
367
|
+
(ty !== ey && con.angularMin[1] === con.angularMax[1]) ||
|
|
368
|
+
(tz !== ez && con.angularMin[2] === con.angularMax[2])
|
|
369
|
+
// The decomposition above satisfies R_rel^T = Rx(x)·Ry(y)·Rz(z), so
|
|
370
|
+
// u = qx·qy·qz is conj(q_rel) and the error rotation (current →
|
|
371
|
+
// clamped target, expressed in TA's frame) is q_E = conj(u_t) ⊗ u.
|
|
372
|
+
eulerXYZQuatInto(ex, ey, ez, _quatScratchA)
|
|
373
|
+
eulerXYZQuatInto(tx, ty, tz, _quatScratchB)
|
|
374
|
+
const ux = _quatScratchA[0], uy = _quatScratchA[1], uz = _quatScratchA[2], uw = _quatScratchA[3]
|
|
375
|
+
const vx = _quatScratchB[0], vy = _quatScratchB[1], vz = _quatScratchB[2], vw = _quatScratchB[3]
|
|
376
|
+
// q_E = conj(v) ⊗ u
|
|
377
|
+
let qex = vw * ux - vx * uw - vy * uz + vz * uy
|
|
378
|
+
let qey = vw * uy + vx * uz - vy * uw - vz * ux
|
|
379
|
+
let qez = vw * uz - vx * uy + vy * ux - vz * uw
|
|
380
|
+
let qew = vw * uw + vx * ux + vy * uy + vz * uz
|
|
381
|
+
if (qew < 0) { qex = -qex; qey = -qey; qez = -qez; qew = -qew }
|
|
382
|
+
const sinHalf = Math.sqrt(qex * qex + qey * qey + qez * qez)
|
|
383
|
+
if (sinHalf > 1e-6) {
|
|
384
|
+
const angle = 2 * Math.atan2(sinHalf, qew)
|
|
385
|
+
const invS = 1 / sinHalf
|
|
386
|
+
const axx = qex * invS, axy = qey * invS, axz = qez * invS
|
|
387
|
+
// Axis lives in TA's frame; TA's basis columns map it to world.
|
|
388
|
+
const lim = con.cacheAngLimAxis
|
|
389
|
+
lim[0] = _TA[0] * axx + _TA[4] * axy + _TA[8] * axz
|
|
390
|
+
lim[1] = _TA[1] * axx + _TA[5] * axy + _TA[9] * axz
|
|
391
|
+
lim[2] = _TA[2] * axx + _TA[6] * axy + _TA[10] * axz
|
|
392
|
+
const gWA = con.cacheAngLimWA
|
|
393
|
+
const gWB = con.cacheAngLimWB
|
|
394
|
+
gWA[0] = W[a9 + 0] * lim[0] + W[a9 + 1] * lim[1] + W[a9 + 2] * lim[2]
|
|
395
|
+
gWA[1] = W[a9 + 3] * lim[0] + W[a9 + 4] * lim[1] + W[a9 + 5] * lim[2]
|
|
396
|
+
gWA[2] = W[a9 + 6] * lim[0] + W[a9 + 7] * lim[1] + W[a9 + 8] * lim[2]
|
|
397
|
+
gWB[0] = W[b9 + 0] * lim[0] + W[b9 + 1] * lim[1] + W[b9 + 2] * lim[2]
|
|
398
|
+
gWB[1] = W[b9 + 3] * lim[0] + W[b9 + 4] * lim[1] + W[b9 + 5] * lim[2]
|
|
399
|
+
gWB[2] = W[b9 + 6] * lim[0] + W[b9 + 7] * lim[1] + W[b9 + 8] * lim[2]
|
|
400
|
+
const gDenom = lim[0] * (gWA[0] + gWB[0]) + lim[1] * (gWA[1] + gWB[1]) + lim[2] * (gWA[2] + gWB[2])
|
|
401
|
+
con.cacheAngLimJacInv = gDenom > 0 ? 1 / gDenom : 0
|
|
402
|
+
let target = angle * STOP_ERP * erpScale * invDt
|
|
403
|
+
if (target > MAX_ANGULAR_CORRECTION_VEL) target = MAX_ANGULAR_CORRECTION_VEL
|
|
404
|
+
con.cacheAngLimTarget = target
|
|
405
|
+
con.cacheAngLimActive = bilateral ? 1 : 2
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
con.cacheAngLimImp = 0
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ITER: read cache, compute relVel from current lv/av, apply impulse.
|
|
413
|
+
function iterateConstraint(
|
|
414
|
+
con: SixDofSpringConstraint,
|
|
415
|
+
lv: Float32Array,
|
|
416
|
+
av: Float32Array,
|
|
417
|
+
invMass: Float32Array,
|
|
418
|
+
): void {
|
|
419
|
+
if (con.cacheSkip) return
|
|
420
|
+
const a = con.bodyA
|
|
421
|
+
const b = con.bodyB
|
|
422
|
+
const ai = a * 3
|
|
423
|
+
const bi = b * 3
|
|
424
|
+
const imA = invMass[a]
|
|
425
|
+
const imB = invMass[b]
|
|
426
|
+
|
|
427
|
+
// Linear axes — relVel at the offset point: v_pivot = v_CG + ω × r.
|
|
428
|
+
const lA = con.cacheLeverA
|
|
429
|
+
const lB = con.cacheLeverB
|
|
430
|
+
const rAx = lA[0], rAy = lA[1], rAz = lA[2]
|
|
431
|
+
const rBx = lB[0], rBy = lB[1], rBz = lB[2]
|
|
432
|
+
const axes = con.cacheLinAxes
|
|
433
|
+
const cA = con.cacheLinCrossA
|
|
434
|
+
const cB = con.cacheLinCrossB
|
|
435
|
+
const jac = con.cacheLinJacInv
|
|
436
|
+
const tgt = con.cacheLinTargetVel
|
|
437
|
+
const act = con.cacheLinActive
|
|
438
|
+
|
|
439
|
+
const vAx = lv[ai + 0] + av[ai + 1] * rAz - av[ai + 2] * rAy
|
|
440
|
+
const vAy = lv[ai + 1] + av[ai + 2] * rAx - av[ai + 0] * rAz
|
|
441
|
+
const vAz = lv[ai + 2] + av[ai + 0] * rAy - av[ai + 1] * rAx
|
|
442
|
+
const vBx = lv[bi + 0] + av[bi + 1] * rBz - av[bi + 2] * rBy
|
|
443
|
+
const vBy = lv[bi + 1] + av[bi + 2] * rBx - av[bi + 0] * rBz
|
|
444
|
+
const vBz = lv[bi + 2] + av[bi + 0] * rBy - av[bi + 1] * rBx
|
|
445
|
+
const dvx = vBx - vAx
|
|
446
|
+
const dvy = vBy - vAy
|
|
447
|
+
const dvz = vBz - vAz
|
|
448
|
+
|
|
449
|
+
const sprAct = con.cacheLinSpringActive
|
|
450
|
+
const sprTgt = con.cacheLinSpringTarget
|
|
451
|
+
const sprMax = con.cacheLinSpringMaxImp
|
|
452
|
+
const sprImp = con.cacheLinSpringImp
|
|
453
|
+
const limImp = con.cacheLinLimitImp
|
|
454
|
+
for (let i = 0; i < 3; i++) {
|
|
455
|
+
if (!act[i] && !sprAct[i]) continue
|
|
456
|
+
const o = i * 3
|
|
457
|
+
const axx = axes[o + 0], axy = axes[o + 1], axz = axes[o + 2]
|
|
458
|
+
const relVel = dvx * axx + dvy * axy + dvz * axz
|
|
459
|
+
let j = 0
|
|
460
|
+
|
|
461
|
+
// Limit row. Locked axes (act 1) are bilateral equality joints; ranged
|
|
462
|
+
// axes in violation (act 2) are unilateral stops — accumulated impulse
|
|
463
|
+
// clamped to the corrective sign, so the stop pushes back into range but
|
|
464
|
+
// never pulls deeper or brakes natural recovery (a bilateral stop acts
|
|
465
|
+
// as a motor and pumps energy into swinging cloth).
|
|
466
|
+
if (act[i]) {
|
|
467
|
+
const target = tgt[i]
|
|
468
|
+
let dImp = LIMIT_SOFTNESS_LINEAR * (target - relVel) * jac[i]
|
|
469
|
+
if (act[i] === 2) {
|
|
470
|
+
const old = limImp[i]
|
|
471
|
+
let next = old + dImp
|
|
472
|
+
if (target > 0 ? next < 0 : next > 0) next = 0
|
|
473
|
+
dImp = next - old
|
|
474
|
+
limImp[i] = next
|
|
475
|
+
}
|
|
476
|
+
j += dImp
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Spring row: velocity-target drive; the deadbeat k-clamp at setup
|
|
480
|
+
// bounds its aggression, and maxImp bounds loop-edge springs by their
|
|
481
|
+
// real force. relVel is refreshed with the limit impulse applied just
|
|
482
|
+
// above (j·denom = j / jac) — driving the spring off the stale value
|
|
483
|
+
// double-corrects the DOF and overshoots every iteration.
|
|
484
|
+
if (sprAct[i]) {
|
|
485
|
+
const relVelNow = j !== 0 ? relVel + j / jac[i] : relVel
|
|
486
|
+
let dImp = (sprTgt[i] - relVelNow) * jac[i]
|
|
487
|
+
const max = sprMax[i]
|
|
488
|
+
if (max !== Infinity) {
|
|
489
|
+
const old = sprImp[i]
|
|
490
|
+
let next = old + dImp
|
|
491
|
+
if (next > max) next = max
|
|
492
|
+
else if (next < -max) next = -max
|
|
493
|
+
dImp = next - old
|
|
494
|
+
sprImp[i] = next
|
|
495
|
+
}
|
|
496
|
+
j += dImp
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (j === 0) continue
|
|
500
|
+
if (imA > 0) {
|
|
501
|
+
lv[ai + 0] -= j * imA * axx
|
|
502
|
+
lv[ai + 1] -= j * imA * axy
|
|
503
|
+
lv[ai + 2] -= j * imA * axz
|
|
504
|
+
av[ai + 0] -= j * cA[o + 0]
|
|
505
|
+
av[ai + 1] -= j * cA[o + 1]
|
|
506
|
+
av[ai + 2] -= j * cA[o + 2]
|
|
507
|
+
}
|
|
508
|
+
if (imB > 0) {
|
|
509
|
+
lv[bi + 0] += j * imB * axx
|
|
510
|
+
lv[bi + 1] += j * imB * axy
|
|
511
|
+
lv[bi + 2] += j * imB * axz
|
|
512
|
+
av[bi + 0] += j * cB[o + 0]
|
|
513
|
+
av[bi + 1] += j * cB[o + 1]
|
|
514
|
+
av[bi + 2] += j * cB[o + 2]
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Angular axes — relAv = ω_B − ω_A.
|
|
519
|
+
const angAxes = con.cacheAngAxes
|
|
520
|
+
const angJac = con.cacheAngJacInv
|
|
521
|
+
const angWAs = con.cacheAngWA
|
|
522
|
+
const angWBs = con.cacheAngWB
|
|
523
|
+
const angTgt = con.cacheAngTargetVel
|
|
524
|
+
const angAct = con.cacheAngActive
|
|
525
|
+
const dax = av[bi + 0] - av[ai + 0]
|
|
526
|
+
const day = av[bi + 1] - av[ai + 1]
|
|
527
|
+
const daz = av[bi + 2] - av[ai + 2]
|
|
528
|
+
const angSprMax = con.cacheAngSpringMaxImp
|
|
529
|
+
const angSprImp = con.cacheAngSpringImp
|
|
530
|
+
for (let i = 0; i < 3; i++) {
|
|
531
|
+
if (!angAct[i]) continue
|
|
532
|
+
const o = i * 3
|
|
533
|
+
const axx = angAxes[o + 0], axy = angAxes[o + 1], axz = angAxes[o + 2]
|
|
534
|
+
const relAv = dax * axx + day * axy + daz * axz
|
|
535
|
+
let j = (angTgt[i] - relAv) * angJac[i]
|
|
536
|
+
{
|
|
537
|
+
const max = angSprMax[i]
|
|
538
|
+
const old = angSprImp[i]
|
|
539
|
+
let next = old + j
|
|
540
|
+
if (next > max) next = max
|
|
541
|
+
else if (next < -max) next = -max
|
|
542
|
+
j = next - old
|
|
543
|
+
angSprImp[i] = next
|
|
544
|
+
}
|
|
545
|
+
if (j === 0) continue
|
|
546
|
+
if (imA > 0) {
|
|
547
|
+
av[ai + 0] -= j * angWAs[o + 0]
|
|
548
|
+
av[ai + 1] -= j * angWAs[o + 1]
|
|
549
|
+
av[ai + 2] -= j * angWAs[o + 2]
|
|
550
|
+
}
|
|
551
|
+
if (imB > 0) {
|
|
552
|
+
av[bi + 0] += j * angWBs[o + 0]
|
|
553
|
+
av[bi + 1] += j * angWBs[o + 1]
|
|
554
|
+
av[bi + 2] += j * angWBs[o + 2]
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Per-axis angular limit rows (small-violation regime), on the derived
|
|
559
|
+
// euler axes. Sign convention matches the springs: positive target reduces
|
|
560
|
+
// positive error via d(angDiff)/dt = −(ω_B − ω_A)·ax.
|
|
561
|
+
const paAct = con.cacheAngPAActive
|
|
562
|
+
if (paAct[0] || paAct[1] || paAct[2]) {
|
|
563
|
+
const paTgt = con.cacheAngPATarget
|
|
564
|
+
const paImp = con.cacheAngPAImp
|
|
565
|
+
for (let i = 0; i < 3; i++) {
|
|
566
|
+
if (!paAct[i]) continue
|
|
567
|
+
const o = i * 3
|
|
568
|
+
const axx = angAxes[o + 0], axy = angAxes[o + 1], axz = angAxes[o + 2]
|
|
569
|
+
const relAv =
|
|
570
|
+
(av[bi + 0] - av[ai + 0]) * axx +
|
|
571
|
+
(av[bi + 1] - av[ai + 1]) * axy +
|
|
572
|
+
(av[bi + 2] - av[ai + 2]) * axz
|
|
573
|
+
const target = paTgt[i]
|
|
574
|
+
// Locked axes (act 1) are welds — full gain, like the 0.16.3 fold;
|
|
575
|
+
// softness only tempers the unilateral stops.
|
|
576
|
+
const soft = paAct[i] === 2 ? LIMIT_SOFTNESS_ANGULAR : 1.0
|
|
577
|
+
let j = soft * (target - relAv) * angJac[i]
|
|
578
|
+
if (paAct[i] === 2) {
|
|
579
|
+
const old = paImp[i]
|
|
580
|
+
let next = old + j
|
|
581
|
+
if (target > 0 ? next < 0 : next > 0) next = 0
|
|
582
|
+
j = next - old
|
|
583
|
+
paImp[i] = next
|
|
584
|
+
}
|
|
585
|
+
if (j === 0) continue
|
|
586
|
+
if (imA > 0) {
|
|
587
|
+
av[ai + 0] -= j * angWAs[o + 0]
|
|
588
|
+
av[ai + 1] -= j * angWAs[o + 1]
|
|
589
|
+
av[ai + 2] -= j * angWAs[o + 2]
|
|
590
|
+
}
|
|
591
|
+
if (imB > 0) {
|
|
592
|
+
av[bi + 0] += j * angWBs[o + 0]
|
|
593
|
+
av[bi + 1] += j * angWBs[o + 1]
|
|
594
|
+
av[bi + 2] += j * angWBs[o + 2]
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Geodesic limit row: drive (ω_B − ω_A)·axis toward the correction target.
|
|
600
|
+
// Unilateral — the accumulated impulse can only push toward the legal
|
|
601
|
+
// region (target is always ≥ 0 along the corrective axis).
|
|
602
|
+
if (con.cacheAngLimActive) {
|
|
603
|
+
const lim = con.cacheAngLimAxis
|
|
604
|
+
const nx = lim[0], ny = lim[1], nz = lim[2]
|
|
605
|
+
// Re-read relAv — the spring rows above may have changed av.
|
|
606
|
+
const relAv =
|
|
607
|
+
(av[bi + 0] - av[ai + 0]) * nx +
|
|
608
|
+
(av[bi + 1] - av[ai + 1]) * ny +
|
|
609
|
+
(av[bi + 2] - av[ai + 2]) * nz
|
|
610
|
+
let j = LIMIT_SOFTNESS_ANGULAR * (con.cacheAngLimTarget - relAv) * con.cacheAngLimJacInv
|
|
611
|
+
if (con.cacheAngLimActive === 2) {
|
|
612
|
+
const old = con.cacheAngLimImp
|
|
613
|
+
let next = old + j
|
|
614
|
+
if (next < 0) next = 0
|
|
615
|
+
j = next - old
|
|
616
|
+
con.cacheAngLimImp = next
|
|
617
|
+
}
|
|
618
|
+
if (j !== 0) {
|
|
619
|
+
const gWA = con.cacheAngLimWA
|
|
620
|
+
const gWB = con.cacheAngLimWB
|
|
621
|
+
if (imA > 0) {
|
|
622
|
+
av[ai + 0] -= j * gWA[0]
|
|
623
|
+
av[ai + 1] -= j * gWA[1]
|
|
624
|
+
av[ai + 2] -= j * gWA[2]
|
|
625
|
+
}
|
|
626
|
+
if (imB > 0) {
|
|
627
|
+
av[bi + 0] += j * gWB[0]
|
|
628
|
+
av[bi + 1] += j * gWB[1]
|
|
629
|
+
av[bi + 2] += j * gWB[2]
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// SETUP: pre-compute Jacobians, friction basis, and the bounce reference
|
|
636
|
+
// from the *initial* closing velocity (Bullet's pattern — captures restitution
|
|
637
|
+
// before iter 1 zeroes out the approach).
|
|
638
|
+
function setupContactRow(
|
|
639
|
+
c: Contact,
|
|
640
|
+
lv: Float32Array,
|
|
641
|
+
av: Float32Array,
|
|
642
|
+
invMass: Float32Array,
|
|
643
|
+
W: Float32Array,
|
|
644
|
+
): void {
|
|
645
|
+
const ai = c.bodyA * 3
|
|
646
|
+
const bi = c.bodyB * 3
|
|
647
|
+
const a9 = c.bodyA * 9
|
|
648
|
+
const b9 = c.bodyB * 9
|
|
649
|
+
const imA = invMass[c.bodyA]
|
|
650
|
+
const imB = invMass[c.bodyB]
|
|
651
|
+
const rAx = c.rAx, rAy = c.rAy, rAz = c.rAz
|
|
652
|
+
const rBx = c.rBx, rBy = c.rBy, rBz = c.rBz
|
|
653
|
+
const nx = c.nx, ny = c.ny, nz = c.nz
|
|
654
|
+
|
|
655
|
+
// Normal Jacobian. Cached vectors are tensor-multiplied I⁻¹·(r×n).
|
|
656
|
+
const cAxN = rAy * nz - rAz * ny
|
|
657
|
+
const cAyN = rAz * nx - rAx * nz
|
|
658
|
+
const cAzN = rAx * ny - rAy * nx
|
|
659
|
+
const cBxN = rBy * nz - rBz * ny
|
|
660
|
+
const cByN = rBz * nx - rBx * nz
|
|
661
|
+
const cBzN = rBx * ny - rBy * nx
|
|
662
|
+
const wAxN = W[a9 + 0] * cAxN + W[a9 + 1] * cAyN + W[a9 + 2] * cAzN
|
|
663
|
+
const wAyN = W[a9 + 3] * cAxN + W[a9 + 4] * cAyN + W[a9 + 5] * cAzN
|
|
664
|
+
const wAzN = W[a9 + 6] * cAxN + W[a9 + 7] * cAyN + W[a9 + 8] * cAzN
|
|
665
|
+
const wBxN = W[b9 + 0] * cBxN + W[b9 + 1] * cByN + W[b9 + 2] * cBzN
|
|
666
|
+
const wByN = W[b9 + 3] * cBxN + W[b9 + 4] * cByN + W[b9 + 5] * cBzN
|
|
667
|
+
const wBzN = W[b9 + 6] * cBxN + W[b9 + 7] * cByN + W[b9 + 8] * cBzN
|
|
668
|
+
const denomN = imA + imB +
|
|
669
|
+
(cAxN * wAxN + cAyN * wAyN + cAzN * wAzN) +
|
|
670
|
+
(cBxN * wBxN + cByN * wByN + cBzN * wBzN)
|
|
671
|
+
c.cAxN = wAxN; c.cAyN = wAyN; c.cAzN = wAzN
|
|
672
|
+
c.cBxN = wBxN; c.cByN = wByN; c.cBzN = wBzN
|
|
673
|
+
c.jacInvN = denomN > 0 ? 1 / denomN : 0
|
|
674
|
+
|
|
675
|
+
// Restitution reference, captured from initial relVelN.
|
|
676
|
+
const vAx = lv[ai + 0] + av[ai + 1] * rAz - av[ai + 2] * rAy
|
|
677
|
+
const vAy = lv[ai + 1] + av[ai + 2] * rAx - av[ai + 0] * rAz
|
|
678
|
+
const vAz = lv[ai + 2] + av[ai + 0] * rAy - av[ai + 1] * rAx
|
|
679
|
+
const vBx = lv[bi + 0] + av[bi + 1] * rBz - av[bi + 2] * rBy
|
|
680
|
+
const vBy = lv[bi + 1] + av[bi + 2] * rBx - av[bi + 0] * rBz
|
|
681
|
+
const vBz = lv[bi + 2] + av[bi + 0] * rBy - av[bi + 1] * rBx
|
|
682
|
+
const relVelN0 = (vBx - vAx) * nx + (vBy - vAy) * ny + (vBz - vAz) * nz
|
|
683
|
+
c.bounceVel = c.restitution > 0 && relVelN0 < -BOUNCE_THRESHOLD
|
|
684
|
+
? -c.restitution * relVelN0
|
|
685
|
+
: 0
|
|
686
|
+
|
|
687
|
+
// Friction tangent basis. Pick the axis least aligned with n.
|
|
688
|
+
let t1x: number, t1y: number, t1z: number
|
|
689
|
+
if (Math.abs(nx) < 0.7071) { t1x = 0; t1y = -nz; t1z = ny }
|
|
690
|
+
else { t1x = nz; t1y = 0; t1z = -nx }
|
|
691
|
+
const tl = Math.hypot(t1x, t1y, t1z)
|
|
692
|
+
if (tl > 1e-8) {
|
|
693
|
+
const tInv = 1 / tl
|
|
694
|
+
t1x *= tInv; t1y *= tInv; t1z *= tInv
|
|
695
|
+
} else {
|
|
696
|
+
c.jacInvT1 = 0; c.jacInvT2 = 0
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
const t2x = ny * t1z - nz * t1y
|
|
700
|
+
const t2y = nz * t1x - nx * t1z
|
|
701
|
+
const t2z = nx * t1y - ny * t1x
|
|
702
|
+
c.t1x = t1x; c.t1y = t1y; c.t1z = t1z
|
|
703
|
+
c.t2x = t2x; c.t2y = t2y; c.t2z = t2z
|
|
704
|
+
|
|
705
|
+
// Friction Jacobians.
|
|
706
|
+
const cAxT1 = rAy * t1z - rAz * t1y
|
|
707
|
+
const cAyT1 = rAz * t1x - rAx * t1z
|
|
708
|
+
const cAzT1 = rAx * t1y - rAy * t1x
|
|
709
|
+
const cBxT1 = rBy * t1z - rBz * t1y
|
|
710
|
+
const cByT1 = rBz * t1x - rBx * t1z
|
|
711
|
+
const cBzT1 = rBx * t1y - rBy * t1x
|
|
712
|
+
const wAxT1 = W[a9 + 0] * cAxT1 + W[a9 + 1] * cAyT1 + W[a9 + 2] * cAzT1
|
|
713
|
+
const wAyT1 = W[a9 + 3] * cAxT1 + W[a9 + 4] * cAyT1 + W[a9 + 5] * cAzT1
|
|
714
|
+
const wAzT1 = W[a9 + 6] * cAxT1 + W[a9 + 7] * cAyT1 + W[a9 + 8] * cAzT1
|
|
715
|
+
const wBxT1 = W[b9 + 0] * cBxT1 + W[b9 + 1] * cByT1 + W[b9 + 2] * cBzT1
|
|
716
|
+
const wByT1 = W[b9 + 3] * cBxT1 + W[b9 + 4] * cByT1 + W[b9 + 5] * cBzT1
|
|
717
|
+
const wBzT1 = W[b9 + 6] * cBxT1 + W[b9 + 7] * cByT1 + W[b9 + 8] * cBzT1
|
|
718
|
+
const denomT1 = imA + imB +
|
|
719
|
+
(cAxT1 * wAxT1 + cAyT1 * wAyT1 + cAzT1 * wAzT1) +
|
|
720
|
+
(cBxT1 * wBxT1 + cByT1 * wByT1 + cBzT1 * wBzT1)
|
|
721
|
+
c.cAxT1 = wAxT1; c.cAyT1 = wAyT1; c.cAzT1 = wAzT1
|
|
722
|
+
c.cBxT1 = wBxT1; c.cByT1 = wByT1; c.cBzT1 = wBzT1
|
|
723
|
+
c.jacInvT1 = denomT1 > 0 ? 1 / denomT1 : 0
|
|
724
|
+
|
|
725
|
+
const cAxT2 = rAy * t2z - rAz * t2y
|
|
726
|
+
const cAyT2 = rAz * t2x - rAx * t2z
|
|
727
|
+
const cAzT2 = rAx * t2y - rAy * t2x
|
|
728
|
+
const cBxT2 = rBy * t2z - rBz * t2y
|
|
729
|
+
const cByT2 = rBz * t2x - rBx * t2z
|
|
730
|
+
const cBzT2 = rBx * t2y - rBy * t2x
|
|
731
|
+
const wAxT2 = W[a9 + 0] * cAxT2 + W[a9 + 1] * cAyT2 + W[a9 + 2] * cAzT2
|
|
732
|
+
const wAyT2 = W[a9 + 3] * cAxT2 + W[a9 + 4] * cAyT2 + W[a9 + 5] * cAzT2
|
|
733
|
+
const wAzT2 = W[a9 + 6] * cAxT2 + W[a9 + 7] * cAyT2 + W[a9 + 8] * cAzT2
|
|
734
|
+
const wBxT2 = W[b9 + 0] * cBxT2 + W[b9 + 1] * cByT2 + W[b9 + 2] * cBzT2
|
|
735
|
+
const wByT2 = W[b9 + 3] * cBxT2 + W[b9 + 4] * cByT2 + W[b9 + 5] * cBzT2
|
|
736
|
+
const wBzT2 = W[b9 + 6] * cBxT2 + W[b9 + 7] * cByT2 + W[b9 + 8] * cBzT2
|
|
737
|
+
const denomT2 = imA + imB +
|
|
738
|
+
(cAxT2 * wAxT2 + cAyT2 * wAyT2 + cAzT2 * wAzT2) +
|
|
739
|
+
(cBxT2 * wBxT2 + cByT2 * wByT2 + cBzT2 * wBzT2)
|
|
740
|
+
c.cAxT2 = wAxT2; c.cAyT2 = wAyT2; c.cAzT2 = wAzT2
|
|
741
|
+
c.cBxT2 = wBxT2; c.cByT2 = wByT2; c.cBzT2 = wBzT2
|
|
742
|
+
c.jacInvT2 = denomT2 > 0 ? 1 / denomT2 : 0
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// ITER: one push-only normal row + two Coulomb friction rows. Friction
|
|
746
|
+
// bound depends on the *current* applied normal impulse, so it tightens
|
|
747
|
+
// as the normal row converges.
|
|
748
|
+
function iterateContactRow(
|
|
749
|
+
c: Contact,
|
|
750
|
+
lv: Float32Array,
|
|
751
|
+
av: Float32Array,
|
|
752
|
+
invMass: Float32Array,
|
|
753
|
+
): void {
|
|
754
|
+
const imA = invMass[c.bodyA]
|
|
755
|
+
const imB = invMass[c.bodyB]
|
|
756
|
+
if (imA === 0 && imB === 0) return
|
|
757
|
+
const ai = c.bodyA * 3, bi = c.bodyB * 3
|
|
758
|
+
const rAx = c.rAx, rAy = c.rAy, rAz = c.rAz
|
|
759
|
+
const rBx = c.rBx, rBy = c.rBy, rBz = c.rBz
|
|
760
|
+
|
|
761
|
+
const vAx = lv[ai + 0] + av[ai + 1] * rAz - av[ai + 2] * rAy
|
|
762
|
+
const vAy = lv[ai + 1] + av[ai + 2] * rAx - av[ai + 0] * rAz
|
|
763
|
+
const vAz = lv[ai + 2] + av[ai + 0] * rAy - av[ai + 1] * rAx
|
|
764
|
+
const vBx = lv[bi + 0] + av[bi + 1] * rBz - av[bi + 2] * rBy
|
|
765
|
+
const vBy = lv[bi + 1] + av[bi + 2] * rBx - av[bi + 0] * rBz
|
|
766
|
+
const vBz = lv[bi + 2] + av[bi + 0] * rBy - av[bi + 1] * rBx
|
|
767
|
+
const dvx = vBx - vAx
|
|
768
|
+
const dvy = vBy - vAy
|
|
769
|
+
const dvz = vBz - vAz
|
|
770
|
+
|
|
771
|
+
// Normal row.
|
|
772
|
+
const jacInvN = c.jacInvN
|
|
773
|
+
if (jacInvN > 0) {
|
|
774
|
+
const nx = c.nx, ny = c.ny, nz = c.nz
|
|
775
|
+
const relVelN = dvx * nx + dvy * ny + dvz * nz
|
|
776
|
+
let dImpN = (c.bounceVel - relVelN) * jacInvN
|
|
777
|
+
const oldN = c.appliedNormalImpulse
|
|
778
|
+
let newN = oldN + dImpN
|
|
779
|
+
if (newN < 0) { newN = 0; dImpN = -oldN }
|
|
780
|
+
c.appliedNormalImpulse = newN
|
|
781
|
+
if (dImpN !== 0) {
|
|
782
|
+
const cAxN = c.cAxN, cAyN = c.cAyN, cAzN = c.cAzN
|
|
783
|
+
const cBxN = c.cBxN, cByN = c.cByN, cBzN = c.cBzN
|
|
784
|
+
if (imA > 0) {
|
|
785
|
+
lv[ai + 0] -= dImpN * imA * nx
|
|
786
|
+
lv[ai + 1] -= dImpN * imA * ny
|
|
787
|
+
lv[ai + 2] -= dImpN * imA * nz
|
|
788
|
+
av[ai + 0] -= dImpN * cAxN
|
|
789
|
+
av[ai + 1] -= dImpN * cAyN
|
|
790
|
+
av[ai + 2] -= dImpN * cAzN
|
|
791
|
+
}
|
|
792
|
+
if (imB > 0) {
|
|
793
|
+
lv[bi + 0] += dImpN * imB * nx
|
|
794
|
+
lv[bi + 1] += dImpN * imB * ny
|
|
795
|
+
lv[bi + 2] += dImpN * imB * nz
|
|
796
|
+
av[bi + 0] += dImpN * cBxN
|
|
797
|
+
av[bi + 1] += dImpN * cByN
|
|
798
|
+
av[bi + 2] += dImpN * cBzN
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Friction. Bound = ±μ · current normal impulse.
|
|
804
|
+
const muNormal = c.friction * c.appliedNormalImpulse
|
|
805
|
+
if (muNormal <= 0) return
|
|
806
|
+
|
|
807
|
+
// Re-read dv after the normal impulse possibly changed lv/av.
|
|
808
|
+
const vAx2 = lv[ai + 0] + av[ai + 1] * rAz - av[ai + 2] * rAy
|
|
809
|
+
const vAy2 = lv[ai + 1] + av[ai + 2] * rAx - av[ai + 0] * rAz
|
|
810
|
+
const vAz2 = lv[ai + 2] + av[ai + 0] * rAy - av[ai + 1] * rAx
|
|
811
|
+
const vBx2 = lv[bi + 0] + av[bi + 1] * rBz - av[bi + 2] * rBy
|
|
812
|
+
const vBy2 = lv[bi + 1] + av[bi + 2] * rBx - av[bi + 0] * rBz
|
|
813
|
+
const vBz2 = lv[bi + 2] + av[bi + 0] * rBy - av[bi + 1] * rBx
|
|
814
|
+
const dvx2 = vBx2 - vAx2
|
|
815
|
+
const dvy2 = vBy2 - vAy2
|
|
816
|
+
const dvz2 = vBz2 - vAz2
|
|
817
|
+
|
|
818
|
+
applyFrictionTangent(
|
|
819
|
+
c, ai, bi, dvx2, dvy2, dvz2,
|
|
820
|
+
c.t1x, c.t1y, c.t1z,
|
|
821
|
+
c.cAxT1, c.cAyT1, c.cAzT1, c.cBxT1, c.cByT1, c.cBzT1,
|
|
822
|
+
c.jacInvT1, muNormal, imA, imB, lv, av, 1,
|
|
823
|
+
)
|
|
824
|
+
applyFrictionTangent(
|
|
825
|
+
c, ai, bi, dvx2, dvy2, dvz2,
|
|
826
|
+
c.t2x, c.t2y, c.t2z,
|
|
827
|
+
c.cAxT2, c.cAyT2, c.cAzT2, c.cBxT2, c.cByT2, c.cBzT2,
|
|
828
|
+
c.jacInvT2, muNormal, imA, imB, lv, av, 2,
|
|
829
|
+
)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function applyFrictionTangent(
|
|
833
|
+
c: Contact,
|
|
834
|
+
ai: number, bi: number,
|
|
835
|
+
dvx: number, dvy: number, dvz: number,
|
|
836
|
+
tx: number, ty: number, tz: number,
|
|
837
|
+
cAx: number, cAy: number, cAz: number,
|
|
838
|
+
cBx: number, cBy: number, cBz: number,
|
|
839
|
+
jacInv: number, muNormal: number,
|
|
840
|
+
imA: number, imB: number,
|
|
841
|
+
lv: Float32Array, av: Float32Array,
|
|
842
|
+
slot: 1 | 2,
|
|
843
|
+
): void {
|
|
844
|
+
if (jacInv <= 0) return
|
|
845
|
+
const relVel = dvx * tx + dvy * ty + dvz * tz
|
|
846
|
+
let dImp = -relVel * jacInv
|
|
847
|
+
const old = slot === 1 ? c.appliedFrictionImpulse1 : c.appliedFrictionImpulse2
|
|
848
|
+
let next = old + dImp
|
|
849
|
+
if (next < -muNormal) { next = -muNormal; dImp = next - old }
|
|
850
|
+
else if (next > muNormal) { next = muNormal; dImp = next - old }
|
|
851
|
+
if (slot === 1) c.appliedFrictionImpulse1 = next
|
|
852
|
+
else c.appliedFrictionImpulse2 = next
|
|
853
|
+
|
|
854
|
+
if (dImp === 0) return
|
|
855
|
+
if (imA > 0) {
|
|
856
|
+
lv[ai + 0] -= dImp * imA * tx
|
|
857
|
+
lv[ai + 1] -= dImp * imA * ty
|
|
858
|
+
lv[ai + 2] -= dImp * imA * tz
|
|
859
|
+
av[ai + 0] -= dImp * cAx
|
|
860
|
+
av[ai + 1] -= dImp * cAy
|
|
861
|
+
av[ai + 2] -= dImp * cAz
|
|
862
|
+
}
|
|
863
|
+
if (imB > 0) {
|
|
864
|
+
lv[bi + 0] += dImp * imB * tx
|
|
865
|
+
lv[bi + 1] += dImp * imB * ty
|
|
866
|
+
lv[bi + 2] += dImp * imB * tz
|
|
867
|
+
av[bi + 0] += dImp * cBx
|
|
868
|
+
av[bi + 1] += dImp * cBy
|
|
869
|
+
av[bi + 2] += dImp * cBz
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function buildBodyMat(store: RigidBodyStore, i: number, out: Float32Array): void {
|
|
874
|
+
const i3 = i * 3, i4 = i * 4
|
|
875
|
+
Mat4.fromPositionRotationInto(
|
|
876
|
+
store.positions[i3 + 0], store.positions[i3 + 1], store.positions[i3 + 2],
|
|
877
|
+
store.orientations[i4 + 0], store.orientations[i4 + 1], store.orientations[i4 + 2], store.orientations[i4 + 3],
|
|
878
|
+
out,
|
|
879
|
+
)
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Quaternion of qx(x) ⊗ qy(y) ⊗ qz(z) (three.js 'XYZ' order).
|
|
883
|
+
function eulerXYZQuatInto(x: number, y: number, z: number, out: Float32Array): void {
|
|
884
|
+
const sx = Math.sin(x * 0.5), cx = Math.cos(x * 0.5)
|
|
885
|
+
const sy = Math.sin(y * 0.5), cy = Math.cos(y * 0.5)
|
|
886
|
+
const sz = Math.sin(z * 0.5), cz = Math.cos(z * 0.5)
|
|
887
|
+
out[0] = sx * cy * cz + cx * sy * sz
|
|
888
|
+
out[1] = cx * sy * cz - sx * cy * sz
|
|
889
|
+
out[2] = cx * cy * sz + sx * sy * cz
|
|
890
|
+
out[3] = cx * cy * cz - sx * sy * sz
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Euler XYZ from a 3×3 rotation matrix (row-major elements).
|
|
894
|
+
function matrixToEulerXYZ(
|
|
895
|
+
r00: number, r01: number,
|
|
896
|
+
r10: number, r11: number,
|
|
897
|
+
r20: number, r21: number, r22: number,
|
|
898
|
+
out: Float32Array,
|
|
899
|
+
): void {
|
|
900
|
+
if (r20 < 1) {
|
|
901
|
+
if (r20 > -1) {
|
|
902
|
+
out[0] = Math.atan2(-r21, r22)
|
|
903
|
+
out[1] = Math.asin(r20)
|
|
904
|
+
out[2] = Math.atan2(-r10, r00)
|
|
905
|
+
} else {
|
|
906
|
+
out[0] = -Math.atan2(r01, r11)
|
|
907
|
+
out[1] = -Math.PI * 0.5
|
|
908
|
+
out[2] = 0
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
out[0] = Math.atan2(r01, r11)
|
|
912
|
+
out[1] = Math.PI * 0.5
|
|
913
|
+
out[2] = 0
|
|
914
|
+
}
|
|
915
|
+
}
|