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