@woosh/meep-engine 2.142.0 → 2.144.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/package.json +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.d.ts +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.js +1 -1
- package/src/core/geom/3d/shape/PointShape3D.d.ts +1 -0
- package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/PointShape3D.js +11 -0
- package/src/core/geom/3d/shape/SphereShape3D.d.ts +48 -0
- package/src/core/geom/3d/shape/SphereShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/SphereShape3D.js +131 -0
- package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts +30 -18
- package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -92
- package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/shape_to_type.js +4 -2
- package/src/core/geom/3d/shape/json/type_adapters.d.ts +12 -3
- package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/type_adapters.js +16 -4
- package/src/core/geom/3d/shape/util/shape_to_visual_entity.js +2 -2
- package/src/engine/control/first-person/DESIGN_COLLISION.md +302 -0
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +91 -58
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1814 -1789
- package/src/engine/control/first-person/TODO.md +17 -32
- package/src/engine/control/first-person/collision/KinematicMover.d.ts +176 -0
- package/src/engine/control/first-person/collision/KinematicMover.d.ts.map +1 -0
- package/src/engine/control/first-person/collision/KinematicMover.js +424 -0
- package/src/engine/control/first-person/prototype_first_person_controller.js +65 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +3 -1
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +30 -16
- package/src/engine/physics/PLAN.md +94 -32
- package/src/engine/physics/contact/ManifoldStore.d.ts +28 -2
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +37 -3
- package/src/engine/physics/contact/combine_material.d.ts +30 -0
- package/src/engine/physics/contact/combine_material.d.ts.map +1 -0
- package/src/engine/physics/contact/combine_material.js +35 -0
- package/src/engine/physics/ecs/Collider.d.ts +15 -0
- package/src/engine/physics/ecs/Collider.d.ts.map +1 -1
- package/src/engine/physics/ecs/Collider.js +34 -0
- package/src/engine/physics/ecs/Joint.d.ts +18 -0
- package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
- package/src/engine/physics/ecs/Joint.js +70 -0
- package/src/engine/physics/ecs/JointSerializationAdapter.d.ts +29 -0
- package/src/engine/physics/ecs/JointSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -0
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +9 -4
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +9 -4
- package/src/engine/physics/ecs/RigidBody.d.ts +15 -0
- package/src/engine/physics/ecs/RigidBody.d.ts.map +1 -1
- package/src/engine/physics/ecs/RigidBody.js +46 -0
- package/src/engine/physics/narrowphase/compute_penetration.d.ts +41 -41
- package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/compute_penetration.js +96 -169
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts +52 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +150 -16
- package/src/engine/physics/narrowphase/refine_ray_hit.js +2 -2
- package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts +8 -7
- package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/sphere_sphere_contact.js +8 -7
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +10 -21
|
@@ -81,6 +81,7 @@ const COLOR_WALLJUMP = 0xaa55ff;
|
|
|
81
81
|
const COLOR_LEDGE = 0xffaa44;
|
|
82
82
|
const COLOR_SLIDE = 0x44ddee;
|
|
83
83
|
const COLOR_GAP = 0xff66cc;
|
|
84
|
+
const COLOR_STAIRS = 0xeedd44;
|
|
84
85
|
const COLOR_NEUTRAL = 0xaaaaaa;
|
|
85
86
|
|
|
86
87
|
const eh = new EngineHarness();
|
|
@@ -129,6 +130,11 @@ async function main(engine) {
|
|
|
129
130
|
// player from BVH raycasts). Leaving the flat baseline on would
|
|
130
131
|
// pin the player to y=0 even when standing on a 2 m platform.
|
|
131
132
|
fpsSystem.useBuiltInFlatGround = false;
|
|
133
|
+
// Collision is resolved by the KinematicMover (recover + unified 3D
|
|
134
|
+
// sweep-and-slide + ground categorize/stick/slope/stairs) whenever a
|
|
135
|
+
// PhysicsSystem is present — which it is here. The mover probes the
|
|
136
|
+
// physics world for ground itself, so no `groundResolver` is wired.
|
|
137
|
+
// See DESIGN_COLLISION.md.
|
|
132
138
|
await em.addSystem(fpsSystem);
|
|
133
139
|
|
|
134
140
|
// Sensors system — populates wall/obstacle/ledge probes via
|
|
@@ -224,6 +230,7 @@ function buildGym(ecd) {
|
|
|
224
230
|
buildLedgeGrabStation(ecd);
|
|
225
231
|
buildSlideTunnel(ecd);
|
|
226
232
|
buildGapJumpStation(ecd);
|
|
233
|
+
buildStairsStation(ecd);
|
|
227
234
|
buildMeshShapeShowcase(ecd);
|
|
228
235
|
}
|
|
229
236
|
|
|
@@ -537,6 +544,64 @@ function buildGapJumpStation(ecd) {
|
|
|
537
544
|
});
|
|
538
545
|
}
|
|
539
546
|
|
|
547
|
+
/**
|
|
548
|
+
* Stairs station — a walkable staircase up to a landing, SW of spawn.
|
|
549
|
+
* Exercises the KinematicMover's stair handling (DESIGN_COLLISION.md
|
|
550
|
+
* Phase 3): the player walks up the steps staying grounded (no launch
|
|
551
|
+
* off each lip) and walks back down staying grounded (stick-to-ground
|
|
552
|
+
* snaps onto each lower step rather than going airborne).
|
|
553
|
+
*
|
|
554
|
+
* Each step rises `RISE` (0.2 m) — comfortably under the controller's
|
|
555
|
+
* `stepHeight` (0.3 m), so the mover climbs it. A blocking reference
|
|
556
|
+
* sits beside the stairs: a single 0.5 m riser (> stepHeight) the
|
|
557
|
+
* player canNOT walk up, for contrast.
|
|
558
|
+
*
|
|
559
|
+
* Steps are solid pillars from the ground to each tread height, placed
|
|
560
|
+
* edge-to-edge so they form a clean staircase profile (no floating
|
|
561
|
+
* treads, no overlap). The player approaches from the +X (spawn) side
|
|
562
|
+
* and walks −X up the flight.
|
|
563
|
+
*/
|
|
564
|
+
function buildStairsStation(ecd) {
|
|
565
|
+
const baseX = SPAWN_X - 11;
|
|
566
|
+
const baseZ = SPAWN_Z - 11;
|
|
567
|
+
spawnPad(ecd, baseX, baseZ, COLOR_STAIRS);
|
|
568
|
+
|
|
569
|
+
const RISE = 0.2; // per-step rise — under stepHeight (0.3) → climbable
|
|
570
|
+
const DEPTH = 0.45; // tread depth
|
|
571
|
+
const WIDTH = 4; // generous width so aiming isn't required
|
|
572
|
+
const STEPS = 7; // → 1.4 m top
|
|
573
|
+
const firstFrontX = baseX - 2; // near (east) edge of the first step
|
|
574
|
+
|
|
575
|
+
for (let i = 1; i <= STEPS; i++) {
|
|
576
|
+
const topY = i * RISE;
|
|
577
|
+
spawnBox(ecd, {
|
|
578
|
+
center: new Vector3(firstFrontX - (i - 0.5) * DEPTH, topY / 2, baseZ),
|
|
579
|
+
size: new Vector3(DEPTH, topY, WIDTH),
|
|
580
|
+
color: COLOR_STAIRS,
|
|
581
|
+
roughness: 0.6,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Landing platform at the top, flush with the last tread.
|
|
586
|
+
const topY = STEPS * RISE;
|
|
587
|
+
spawnBox(ecd, {
|
|
588
|
+
center: new Vector3(firstFrontX - STEPS * DEPTH - 1.5, topY / 2, baseZ),
|
|
589
|
+
size: new Vector3(3, topY, WIDTH),
|
|
590
|
+
color: COLOR_STAIRS,
|
|
591
|
+
roughness: 0.6,
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Blocking reference — a single 0.5 m riser (> stepHeight) beside the
|
|
595
|
+
// flight (offset +Z). Walk into it: the mover blocks rather than
|
|
596
|
+
// climbing, the contrast to the stairs.
|
|
597
|
+
spawnBox(ecd, {
|
|
598
|
+
center: new Vector3(firstFrontX - 1, 0.25, baseZ + WIDTH / 2 + 2),
|
|
599
|
+
size: new Vector3(3, 0.5, 3),
|
|
600
|
+
color: COLOR_NEUTRAL,
|
|
601
|
+
roughness: 0.8,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
540
605
|
// =====================================================================
|
|
541
606
|
// Spawn helpers
|
|
542
607
|
// =====================================================================
|
package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AmbientOcclusionPostProcessEffect.d.ts","sourceRoot":"","sources":["../../../../../../../../src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js"],"names":[],"mappings":"AAoBA;IAIQ,WAA0C;IAI1C,2BAQE;IAyBF;;;;OAIG;IACH,sBAA0B;IAE1B;;;;OAIG;IACH,2BAA6B;IAG7B;;;;OAIG;IACH,yBAA4B;IAE5B;;;;OAIG;IACH,wBAA2B;IAE3B,8CAUE;IAEF;;;;OAIG;IACH,uBAAwB;IAExB;;;;;OAKG;IACH,2BASE;IAKF;;;;OAIG;IACH,0BAAwD;IAK5D;;;OAGG;IACH,2BAEC;IAED;;;OAGG;IACH,wBAEC;IAED;;;;OAIG;IACH,oBAeC;IAED,iBAEC;IAED,
|
|
1
|
+
{"version":3,"file":"AmbientOcclusionPostProcessEffect.d.ts","sourceRoot":"","sources":["../../../../../../../../src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js"],"names":[],"mappings":"AAoBA;IAIQ,WAA0C;IAI1C,2BAQE;IAyBF;;;;OAIG;IACH,sBAA0B;IAE1B;;;;OAIG;IACH,2BAA6B;IAG7B;;;;OAIG;IACH,yBAA4B;IAE5B;;;;OAIG;IACH,wBAA2B;IAE3B,8CAUE;IAEF;;;;OAIG;IACH,uBAAwB;IAExB;;;;;OAKG;IACH,2BASE;IAKF;;;;OAIG;IACH,0BAAwD;IAK5D;;;OAGG;IACH,2BAEC;IAED;;;OAGG;IACH,wBAEC;IAED;;;;OAIG;IACH,oBAeC;IAED,iBAEC;IAED,2BAoCC;IAED,uBASC;IAED;;;OAGG;IACH,iBAwBC;IAED;;;OAGG;IACH,4BAFW,OAAO,QAiCjB;IAED;;;;OAIG;IACH,gCASC;IAED;;;;OAIG;IACH,mCAUC;IAGD,oCAcC;IAED,wBA2CC;IAED,yBAiBC;CAEJ;6BAjZ4B,uCAAuC;+BAD7D,OAAO;6CAAP,OAAO"}
|
|
@@ -187,7 +187,9 @@ export class AmbientOcclusionPostProcessEffect extends EnginePlugin {
|
|
|
187
187
|
|
|
188
188
|
const camera = this.__render_camera;
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
// AO renders at half-res but samples the full-res depth buffer, so `size` is the full (depth)
|
|
191
|
+
// resolution -- all texel math then lands on depth texel centres rather than texel boundaries
|
|
192
|
+
uniforms.size.value.set(this.__composit_layer.renderTarget.width, this.__composit_layer.renderTarget.height);
|
|
191
193
|
|
|
192
194
|
// setup camera
|
|
193
195
|
const near = camera.near;
|
|
@@ -199,8 +199,6 @@ const SAOShader = {
|
|
|
199
199
|
// Roberts' R3 low-discrepancy additive recurrence: ( 1/g, 1/g^2, 1/g^3 ), g^4 = g + 1
|
|
200
200
|
const vec3 R3 = vec3( 0.8191725134, 0.6710436067, 0.5497004779 );
|
|
201
201
|
|
|
202
|
-
const float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES );
|
|
203
|
-
|
|
204
202
|
// normal-bias as a fraction of kernelRadius: lifts the sample origin off the surface to avoid
|
|
205
203
|
// self-occlusion. Larger removes acne but lets near-surface contact occlusion leak.
|
|
206
204
|
const float SURFACE_BIAS = 0.05;
|
|
@@ -217,8 +215,8 @@ const SAOShader = {
|
|
|
217
215
|
// radius over which an occluder's contribution fades to zero at the edge. Must be in [0, 1]. (GTAO)
|
|
218
216
|
const float GTAO_EFFECT_FALLOFF_RANGE = 0.615;
|
|
219
217
|
|
|
220
|
-
float getOccludedFraction( const in vec3 centerViewPosition ) {
|
|
221
|
-
vec3 centerViewNormal = getViewNormal( centerViewPosition,
|
|
218
|
+
float getOccludedFraction( const in vec3 centerViewPosition, const in vec2 centerUv ) {
|
|
219
|
+
vec3 centerViewNormal = getViewNormal( centerViewPosition, centerUv );
|
|
222
220
|
|
|
223
221
|
// sample origin lifted slightly off the surface along the normal (see SURFACE_BIAS)
|
|
224
222
|
vec3 sampleOrigin = centerViewPosition + centerViewNormal * ( kernelRadius * SURFACE_BIAS );
|
|
@@ -246,6 +244,7 @@ const SAOShader = {
|
|
|
246
244
|
float falloffAdd = falloffFrom / falloffRange + 1.0;
|
|
247
245
|
|
|
248
246
|
float occlusion = 0.0;
|
|
247
|
+
float weightSum = 0.0;
|
|
249
248
|
|
|
250
249
|
for ( int i = 0; i < NUM_SAMPLES; i ++ ) {
|
|
251
250
|
// fixed R3 low-discrepancy point; per-pixel variation comes from the rotated frame
|
|
@@ -260,6 +259,10 @@ const SAOShader = {
|
|
|
260
259
|
// sampleDistributionPower) so the near occluders that dominate AO are favoured
|
|
261
260
|
float radius = pow( fract( q.z + noise.y ), sampleDistributionPower );
|
|
262
261
|
|
|
262
|
+
// falloff weight from the probe's own distance -- defined for every sample, so the
|
|
263
|
+
// weighted normaliser is well posed; distant probes are less reliable (GTAO)
|
|
264
|
+
float weight = saturate( radius * kernelRadius * falloffMul + falloffAdd );
|
|
265
|
+
|
|
263
266
|
vec3 samplePositionVS = sampleOrigin + dir * ( radius * kernelRadius );
|
|
264
267
|
|
|
265
268
|
// project the sample into screen space
|
|
@@ -267,7 +270,7 @@ const SAOShader = {
|
|
|
267
270
|
vec2 sampleUv = ( clip.xy / clip.w ) * 0.5 + 0.5;
|
|
268
271
|
|
|
269
272
|
// screen-space offset from the centre, in pixels
|
|
270
|
-
vec2 offsetPixels = ( sampleUv -
|
|
273
|
+
vec2 offsetPixels = ( sampleUv - centerUv ) * size;
|
|
271
274
|
|
|
272
275
|
// push forward along the screen direction by the self-occlusion margin (extends the
|
|
273
276
|
// screen-space radius), then snap to the nearest pixel centre so the depth fetch is
|
|
@@ -276,13 +279,16 @@ const SAOShader = {
|
|
|
276
279
|
offsetPixels += offsetPixels / max( offsetLength, EPSILON ) * SELF_OCCLUSION_MARGIN_PIXELS;
|
|
277
280
|
offsetPixels = round( offsetPixels );
|
|
278
281
|
|
|
279
|
-
sampleUv =
|
|
282
|
+
sampleUv = centerUv + offsetPixels / size;
|
|
280
283
|
|
|
281
284
|
// samples outside the screen have no depth to test against
|
|
282
285
|
if ( sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0 ) {
|
|
283
286
|
continue;
|
|
284
287
|
}
|
|
285
288
|
|
|
289
|
+
// a valid (on-screen) sample contributes its weight to the normaliser, occluded or not
|
|
290
|
+
weightSum += weight;
|
|
291
|
+
|
|
286
292
|
float sampleDepth = getDepth( sampleUv );
|
|
287
293
|
|
|
288
294
|
// skip the sky / far plane
|
|
@@ -302,29 +308,37 @@ const SAOShader = {
|
|
|
302
308
|
// ...and the tapped surface must actually be within the sampling radius, otherwise we
|
|
303
309
|
// have hit distant geometry through the depth buffer that does not occlude this point
|
|
304
310
|
vec3 occluderVS = getViewPosition( sampleUv, sampleDepth, sceneViewZ );
|
|
305
|
-
|
|
306
|
-
if ( sampleDist > kernelRadius ) {
|
|
311
|
+
if ( distance( centerViewPosition, occluderVS ) > kernelRadius ) {
|
|
307
312
|
continue;
|
|
308
313
|
}
|
|
309
314
|
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
occlusion += saturate( sampleDist * falloffMul + falloffAdd );
|
|
315
|
+
// occluded: add this sample's weight to the numerator
|
|
316
|
+
occlusion += weight;
|
|
313
317
|
}
|
|
314
318
|
|
|
315
|
-
|
|
319
|
+
// weighted occlusion fraction; off-screen samples were excluded so screen borders are unbiased
|
|
320
|
+
return weightSum > 0.0 ? occlusion / weightSum : 0.0;
|
|
316
321
|
}
|
|
317
322
|
|
|
318
323
|
void main() {
|
|
319
|
-
|
|
324
|
+
// snap to the centre of the full-res depth texel we sample. The half-res pass otherwise
|
|
325
|
+
// lands on texel boundaries, where NEAREST biases consistently to one side and the
|
|
326
|
+
// reconstructed position disagrees with the sampled depth -> asymmetric edge self-occlusion.
|
|
327
|
+
vec2 centerUv = ( floor( vUv * size ) + 0.5 ) / size;
|
|
328
|
+
|
|
329
|
+
float centerDepth = getDepth( centerUv );
|
|
320
330
|
if( centerDepth >= ( 1.0 - EPSILON ) ) {
|
|
321
|
-
|
|
331
|
+
// background / far plane is fully visible. Write white rather than discarding, so the
|
|
332
|
+
// half-res buffer never leaves the clear value for the upscale to read back as occlusion
|
|
333
|
+
// (that is the foreground-edge halo over background).
|
|
334
|
+
out_occlusion = 1.0;
|
|
335
|
+
return;
|
|
322
336
|
}
|
|
323
337
|
|
|
324
338
|
float centerViewZ = getViewZ( centerDepth );
|
|
325
|
-
vec3 viewPosition = getViewPosition(
|
|
339
|
+
vec3 viewPosition = getViewPosition( centerUv, centerDepth, centerViewZ );
|
|
326
340
|
|
|
327
|
-
float occlusion = getOccludedFraction( viewPosition );
|
|
341
|
+
float occlusion = getOccludedFraction( viewPosition, centerUv );
|
|
328
342
|
|
|
329
343
|
// intensity is an artistic power: 1.0 leaves the physical estimate untouched, higher
|
|
330
344
|
// darkens occluded areas while leaving fully-lit areas at 1.0
|
|
@@ -169,21 +169,35 @@ Architectural references for design choices:
|
|
|
169
169
|
kinematic bodies (character controllers, AOE selection).
|
|
170
170
|
|
|
171
171
|
### Standalone narrowphase utilities
|
|
172
|
+
- `deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB, posB,
|
|
173
|
+
rotB)` (exported from `narrowphase_step.js`) — runs the **same**
|
|
174
|
+
`dispatch_pair` the contact solver consumes for one posed shape pair and
|
|
175
|
+
returns the DEEPEST contact's depth + world normal (B → A). The single
|
|
176
|
+
source of truth for "minimum-translation between two posed shapes", reused by
|
|
177
|
+
`compute_penetration` (and available to any other query).
|
|
172
178
|
- `compute_penetration(out_direction, shape_a, pos_a, rot_a, shape_b,
|
|
173
179
|
pos_b, rot_b)` — non-system geometry primitive: positive penetration
|
|
174
180
|
depth + outward direction (B → A convention) on overlap, 0 otherwise.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
181
|
+
**Hardened** — delegates to `deepest_pair_penetration`, so it is correct
|
|
182
|
+
(not "correct sometimes") for every shape pair the engine can build:
|
|
183
|
+
- sphere / box / capsule pairs → exact closed-form (box-box via SAT, so a
|
|
184
|
+
small body resting on a large floor reports the few-cm near-face overlap,
|
|
185
|
+
NOT the metres-deep "exit through the far side" that MPR's centroid-seeded
|
|
186
|
+
portal used to return);
|
|
187
|
+
- general convex pairs → GJK + EPA (exact for polytopes; curved shapes never
|
|
188
|
+
reach it — they have closed forms);
|
|
189
|
+
- convex × concave → triangle decomposition + the closed-form per-triangle
|
|
190
|
+
solvers, bounded to each triangle's true 2-D extent (the old closed-mesh
|
|
191
|
+
side-face over-report is gone).
|
|
192
|
+
The previous per-triangle half-space test is retained ONLY as a recovery
|
|
193
|
+
fallback for the one case the one-sided closed forms can't resolve: a convex
|
|
194
|
+
shape that has fully tunnelled to the *inner* side of a concave surface (a
|
|
195
|
+
depenetration query must still push it back out — exact for heightmap terrain,
|
|
196
|
+
a valid outward push for closed meshes). Concave × concave throws (M×N
|
|
197
|
+
triangle pairs out of scope). The spec asserts an "applying out_direction ×
|
|
198
|
+
depth separates the shapes" invariant across every convex+convex pair type and
|
|
199
|
+
convex+concave, plus exact per-type depths and the small-box-on-huge-floor
|
|
200
|
+
regression (3 m → 0.05 m).
|
|
187
201
|
|
|
188
202
|
### Determinism
|
|
189
203
|
- Direct typed-array writes on hot paths (bypassing `Vector3#set`'s observer
|
|
@@ -224,11 +238,14 @@ Architectural references for design choices:
|
|
|
224
238
|
|
|
225
239
|
## Limitations / Known caveats
|
|
226
240
|
|
|
227
|
-
- **Multi-collider material precision
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
241
|
+
- **Multi-collider material precision** — *resolved for contact materials.* The
|
|
242
|
+
narrowphase now combines the specific source-collider pair's friction /
|
|
243
|
+
restitution per contact and stores them in the manifold (CONTACT_STRIDE
|
|
244
|
+
offsets 14/15); the solver reads them per contact, so mixed-material compound
|
|
245
|
+
bodies are accurate (regression test: an asymmetric-friction body yaws when
|
|
246
|
+
shoved). Still primary-collider only: the contact-filter callback's
|
|
247
|
+
`colliderA/B` arguments and the body-level sensor / concave-dispatch flags —
|
|
248
|
+
a smaller follow-up.
|
|
232
249
|
- **EPA on smooth shapes**: degenerates (no flat face to converge on).
|
|
233
250
|
Mitigated by closed-form paths for sphere/cube/capsule pairs and by the
|
|
234
251
|
**MPR fallback** on EPA non-convergence; exotic convex shapes vs spheres can
|
|
@@ -239,12 +256,20 @@ Architectural references for design choices:
|
|
|
239
256
|
for those primitives, so a sphere/box/capsule on a heightmap or mesh decelerates
|
|
240
257
|
and settles correctly; the `narrowphase_concave.spec.js` "drop and settle"
|
|
241
258
|
cases and the mesh torus-knot settle test are **un-skipped**. Per-triangle
|
|
242
|
-
GJK+EPA remains only as the fallback for *other* convex shapes vs triangles
|
|
243
|
-
(
|
|
259
|
+
GJK+EPA remains only as the fallback for *other* convex shapes vs triangles.
|
|
260
|
+
(`compute_penetration` now routes through that same dispatch via
|
|
261
|
+
`deepest_pair_penetration` — see *Standalone narrowphase utilities* — instead
|
|
262
|
+
of its old half-space pre-test; the half-space test survives only as a
|
|
263
|
+
tunnel-recovery fallback.)
|
|
244
264
|
- **Box-box edge-edge contact**: a single point at the true closest-pair of the
|
|
245
265
|
two edges (P3.2), not the old body-centre midpoint. This is geometrically
|
|
246
|
-
correct
|
|
247
|
-
|
|
266
|
+
correct — and an empirical SAT-source sweep confirms the edge-cross branch
|
|
267
|
+
*only* fires for **transverse** edge crossings (inter-edge angle ≈ 83-90°),
|
|
268
|
+
where two skew lines meet at a unique point. Near-parallel edge contacts
|
|
269
|
+
cannot reach this branch (a near-parallel `edgeA × edgeB` never wins the SAT
|
|
270
|
+
minimum) — they resolve through the multi-point face-clipping path. So the
|
|
271
|
+
once-planned "multi-point edge contact for near-parallel edges" refinement is
|
|
272
|
+
**moot**; see the resolved Stability backlog entry.
|
|
248
273
|
- **CCD floor only**: speculative margin via the fattened AABB prevents
|
|
249
274
|
most tunnelling. No per-body swept shape-cast for very fast objects.
|
|
250
275
|
- **Cross-runtime determinism is not guaranteed**: `Math.sin/cos/exp/log`
|
|
@@ -530,15 +555,49 @@ scaffolding is in place.
|
|
|
530
555
|
those primitives. Un-skipped the `narrowphase_concave.spec.js` ball-on-
|
|
531
556
|
heightmap / mesh-cube settle tests and the `PhysicsSystem.spec.js`
|
|
532
557
|
torus-knot test. Per-triangle GJK+EPA remains only as the fallback for
|
|
533
|
-
*other* convex shapes vs triangles
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
558
|
+
*other* convex shapes vs triangles. `compute_penetration` now routes
|
|
559
|
+
through the shared narrowphase dispatch (`deepest_pair_penetration`), so it
|
|
560
|
+
uses the closed-form per-triangle solvers too — the old closed-mesh
|
|
561
|
+
over-report is gone; the half-space test is retained only as a
|
|
562
|
+
tunnel-recovery fallback.
|
|
563
|
+
- [x] **Edge-edge multi-point manifold** — *resolved by design (no code
|
|
564
|
+
change needed).* An empirical SAT-source sweep over a wide range of
|
|
565
|
+
box-box orientations shows the single-point edge-cross branch only ever
|
|
566
|
+
wins for **transverse** edge crossings (inter-edge angle ≈ 83-90°), where
|
|
567
|
+
a single closest-pair point is geometrically exact. A near-parallel edge
|
|
568
|
+
pair gives a near-degenerate `edgeA × edgeB` that never becomes the SAT
|
|
569
|
+
minimum, so near-parallel ("line") edge contacts resolve through the
|
|
570
|
+
multi-point **face-clipping** path instead — confirmed by regression
|
|
571
|
+
tests in `box_box_manifold.spec.js` (near-parallel tilted boxes → ≥ 2
|
|
572
|
+
points; transverse crossing → exactly 1 exact point). The originally
|
|
573
|
+
planned refinement targeted a case the geometry can't produce, so it is
|
|
574
|
+
closed rather than implemented.
|
|
575
|
+
- [x] **Per-contact source-collider tracking (materials)** — multi-material
|
|
576
|
+
compound bodies now get accurate per-contact friction / restitution. The
|
|
577
|
+
narrowphase combines the specific (colliderA, colliderB) pair's
|
|
578
|
+
coefficients at dispatch time (the only place that knows the source
|
|
579
|
+
collider on each side — `contact/combine_material.js`) and stamps them
|
|
580
|
+
into the manifold (CONTACT_STRIDE grown 14 → 16, offsets 14/15); the
|
|
581
|
+
solver reads them per contact instead of from the body's primary collider.
|
|
582
|
+
Regression test: an asymmetric-friction compound body yaws when shoved
|
|
583
|
+
(the grippy collider drags), and a symmetric control does not. Still
|
|
584
|
+
primary-collider-only: the contact-filter callback's collider args and the
|
|
585
|
+
body-level sensor / concave flags (smaller follow-up).
|
|
586
|
+
- [ ] **Joint-aware island sleep (ragdoll settle quality).** A draped,
|
|
587
|
+
self-colliding 10-joint ragdoll does not fully sleep in 10 s — surfaced by
|
|
588
|
+
a 1000-seed Monte-Carlo sweep (`PhysicsSystem.ragdoll.spec.js`, `.skip`):
|
|
589
|
+
for unlucky seeds a distal limb sustains a settled limit cycle (settled
|
|
590
|
+
finite-difference accel up to ~1094 m/s² / ~1479 rad/s² at a limb end vs a
|
|
591
|
+
~55 m/s² median — bounded, non-growing, penetration-free, so a quality gap
|
|
592
|
+
not a divergence). The sleep test today is per-body `|v|²+|ω|²`; an island
|
|
593
|
+
over-constrained by cone-twist limits + self-contacts keeps small residual
|
|
594
|
+
jiggle above the per-body threshold so it never crosses into sleep.
|
|
595
|
+
Candidate fixes: sleep a jointed/contacting island on its AGGREGATE motion
|
|
596
|
+
rather than the per-body minimum, and/or a settled-regime relaxation (zero
|
|
597
|
+
restitution + extra position iterations) once an island's energy is low.
|
|
598
|
+
The sweep flags the worst seeds for replay. (Test infra also adds
|
|
599
|
+
per-point kinematics tracking — joint anchors + limb ends, with
|
|
600
|
+
displacement→velocity→acceleration and the angular equivalents.)
|
|
542
601
|
|
|
543
602
|
### Performance / Scale
|
|
544
603
|
- [ ] **Per-body linear CCD shape-cast**: optional opt-in for fast-moving
|
|
@@ -598,8 +657,11 @@ scaffolding is in place.
|
|
|
598
657
|
- [x] **`compute_penetration(out_direction, shape_a, pos_a, rot_a,
|
|
599
658
|
shape_b, pos_b, rot_b)`** — standalone geometry primitive (no
|
|
600
659
|
PhysicsSystem) for resolving overlap between two shapes at given
|
|
601
|
-
poses. Returns depth + outward direction.
|
|
602
|
-
|
|
660
|
+
poses. Returns depth + outward direction. **Hardened** to route through
|
|
661
|
+
the shared narrowphase dispatch (`deepest_pair_penetration`): exact
|
|
662
|
+
closed-form for sphere/box/capsule pairs (box-box via SAT), GJK+EPA for
|
|
663
|
+
general convex, closed-form per-triangle for convex × concave; the
|
|
664
|
+
half-space test is retained only for tunnel recovery.
|
|
603
665
|
|
|
604
666
|
### Raycast narrowphase (done)
|
|
605
667
|
|
|
@@ -19,11 +19,19 @@ export const MAX_CONTACTS_PER_MANIFOLD: number;
|
|
|
19
19
|
* 13 : feature_id (uint32 packed as f64 — stable cross-frame ID of the
|
|
20
20
|
* geometric feature pair that produced this contact;
|
|
21
21
|
* 0 means "no info, fall back to position matching")
|
|
22
|
+
* 14 : friction (already-combined coefficient for THIS contact's
|
|
23
|
+
* specific source-collider pair — see combine_material)
|
|
24
|
+
* 15 : restitution(already-combined coefficient for this contact)
|
|
22
25
|
*
|
|
23
26
|
* Solver uses `(world_a + world_b) * 0.5` as the application point; storing
|
|
24
27
|
* both surface points enables the per-frame warm-start matcher in the
|
|
25
28
|
* narrowphase to compare contact-point positions when feature_id is 0.
|
|
26
29
|
*
|
|
30
|
+
* Friction / restitution are stored **per contact** (not per body pair) so a
|
|
31
|
+
* compound body whose colliders have different materials gets the right
|
|
32
|
+
* coefficient at each contact: the narrowphase knows the exact source collider
|
|
33
|
+
* on each side of every contact and combines there.
|
|
34
|
+
*
|
|
27
35
|
* @type {number}
|
|
28
36
|
*/
|
|
29
37
|
export const CONTACT_STRIDE: number;
|
|
@@ -166,7 +174,8 @@ export class ManifoldStore {
|
|
|
166
174
|
* Write a contact point into a slot. Increments contact_count if `idx`
|
|
167
175
|
* exceeds the current count.
|
|
168
176
|
*
|
|
169
|
-
* Writes geometry (offsets 0..9)
|
|
177
|
+
* Writes geometry (offsets 0..9), the feature_id at offset 13, and the
|
|
178
|
+
* per-contact combined friction / restitution at offsets 14 / 15.
|
|
170
179
|
* Deliberately does NOT touch the warm-start impulse fields (offsets
|
|
171
180
|
* 10..12) — the narrowphase match-and-merge pass uses this to refill
|
|
172
181
|
* a slot while preserving the cached impulses that the solver's
|
|
@@ -187,8 +196,25 @@ export class ManifoldStore {
|
|
|
187
196
|
* @param {number} feature_id stable cross-frame identifier of the
|
|
188
197
|
* geometric feature pair that produced this contact; 0 means
|
|
189
198
|
* "no info, fall back to position matching"
|
|
199
|
+
* @param {number} friction combined friction for this contact's specific
|
|
200
|
+
* source-collider pair
|
|
201
|
+
* @param {number} restitution combined restitution for this contact
|
|
202
|
+
*/
|
|
203
|
+
set_contact(slot: number, idx: number, lax: number, lay: number, laz: number, lbx: number, lby: number, lbz: number, nx: number, ny: number, nz: number, depth: number, feature_id?: number, friction?: number, restitution?: number): void;
|
|
204
|
+
/**
|
|
205
|
+
* Read the per-contact combined friction stored at one contact.
|
|
206
|
+
* @param {number} slot
|
|
207
|
+
* @param {number} idx
|
|
208
|
+
* @returns {number}
|
|
209
|
+
*/
|
|
210
|
+
friction_of(slot: number, idx: number): number;
|
|
211
|
+
/**
|
|
212
|
+
* Read the per-contact combined restitution stored at one contact.
|
|
213
|
+
* @param {number} slot
|
|
214
|
+
* @param {number} idx
|
|
215
|
+
* @returns {number}
|
|
190
216
|
*/
|
|
191
|
-
|
|
217
|
+
restitution_of(slot: number, idx: number): number;
|
|
192
218
|
/**
|
|
193
219
|
* Zero the warm-start impulse fields (j_n, j_t1, j_t2) for one contact
|
|
194
220
|
* index inside a slot. Called by the narrowphase match-and-merge pass
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ManifoldStore.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/ManifoldStore.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wCAFU,MAAM,CAE2B;AAE3C
|
|
1
|
+
{"version":3,"file":"ManifoldStore.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/ManifoldStore.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wCAFU,MAAM,CAE2B;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,6BAFU,MAAM,CAEiB;AAEjC;;;;GAIG;AACH,wCAFU,MAAM,CAE4B;AAE5C;;;GAGG;AACH,+BAFU,MAAM,CAE2D;AAyB3E;;;GAGG;AACH,mCAFU,MAAM,CAEuB;AAIvC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;IAEI;;OAEG;IACH,+BAFW,MAAM,EA6BhB;IAzBG,mBAA+C;IAC/C,qBAAqB;IAErB,qBAAkE;IAClE,oBAAiE;IAQjE,0BAAwD;IAExD,4BAA0D;IAG1D,0BAAoD;IACpD,uBAAiD;IAEjD,qBAAqB;IAGrB,yBAAmD;IACnD,qBAAqB;IAGzB;;OAEG;IACH,oBAEC;IAED;;OAEG;IACH,uBAEC;IAED;;;;;;;OAOG;IACH,UAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAKlB;IAED;;;;;;;;OAQG;IACH,aAJW,MAAM,OACN,MAAM,GACJ,MAAM,CA+BlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,YAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,oBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,iBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;OAGG;IACH,uBAHW,MAAM,GACJ,OAAO,CAInB;IAED;;;;;OAKG;IACH,WAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,qBAFW,MAAM,QAQhB;IAED;;;;;;;;;;;;;OAaG;IACH,mBAFW,MAAM,QAKhB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,kBAnBW,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,SACN,MAAM,eACN,MAAM,aAGN,MAAM,gBAEN,MAAM,QAwBhB;IAED;;;;;OAKG;IACH,kBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;OAKG;IACH,qBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;;;;;;;;OAeG;IACH,qBAHW,MAAM,OACN,MAAM,QAOhB;IAED;;;;;OAKG;IACH,oBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,kBALW,MAAM,OACN,MAAM,OACN,MAAM,EAAE,GAAC,YAAY,cACrB,MAAM,QAOhB;IAED;;;;;;OAMG;IACH,uBAJW,MAAM,OACN,MAAM,GACJ,MAAM,CAIlB;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,uBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED,iEAAiE;IACjE,oBADY,MAAM,OAAe,MAAM,GAAgB,MAAM,CAG5D;IAED;;;;;OAKG;IACH,gCAEC;IAED;;;;;;OAMG;IACH,qCAEC;IAED;;;;;OAKG;IACH,uBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,uBAHW,MAAM,GACJ,MAAM,CAIlB;IAED;;;OAGG;IACH,sBA6BC;IAED;;;OAGG;IACH,wBAcC;IAED;;;OAGG;IACH,uBAkBC;IAED;;OAEG;IACH,eA+BC;IAED;;;OAGG;IACH,oBAaC;IAED;;;OAGG;IACH,mBAqBC;CACJ;8BAhoBqD,2CAA2C"}
|
|
@@ -23,14 +23,22 @@ export const MAX_CONTACTS_PER_MANIFOLD = 4;
|
|
|
23
23
|
* 13 : feature_id (uint32 packed as f64 — stable cross-frame ID of the
|
|
24
24
|
* geometric feature pair that produced this contact;
|
|
25
25
|
* 0 means "no info, fall back to position matching")
|
|
26
|
+
* 14 : friction (already-combined coefficient for THIS contact's
|
|
27
|
+
* specific source-collider pair — see combine_material)
|
|
28
|
+
* 15 : restitution(already-combined coefficient for this contact)
|
|
26
29
|
*
|
|
27
30
|
* Solver uses `(world_a + world_b) * 0.5` as the application point; storing
|
|
28
31
|
* both surface points enables the per-frame warm-start matcher in the
|
|
29
32
|
* narrowphase to compare contact-point positions when feature_id is 0.
|
|
30
33
|
*
|
|
34
|
+
* Friction / restitution are stored **per contact** (not per body pair) so a
|
|
35
|
+
* compound body whose colliders have different materials gets the right
|
|
36
|
+
* coefficient at each contact: the narrowphase knows the exact source collider
|
|
37
|
+
* on each side of every contact and combines there.
|
|
38
|
+
*
|
|
31
39
|
* @type {number}
|
|
32
40
|
*/
|
|
33
|
-
export const CONTACT_STRIDE =
|
|
41
|
+
export const CONTACT_STRIDE = 16;
|
|
34
42
|
|
|
35
43
|
/**
|
|
36
44
|
* Per-contact feature_id offset within {@link CONTACT_STRIDE}.
|
|
@@ -286,7 +294,8 @@ export class ManifoldStore {
|
|
|
286
294
|
* Write a contact point into a slot. Increments contact_count if `idx`
|
|
287
295
|
* exceeds the current count.
|
|
288
296
|
*
|
|
289
|
-
* Writes geometry (offsets 0..9)
|
|
297
|
+
* Writes geometry (offsets 0..9), the feature_id at offset 13, and the
|
|
298
|
+
* per-contact combined friction / restitution at offsets 14 / 15.
|
|
290
299
|
* Deliberately does NOT touch the warm-start impulse fields (offsets
|
|
291
300
|
* 10..12) — the narrowphase match-and-merge pass uses this to refill
|
|
292
301
|
* a slot while preserving the cached impulses that the solver's
|
|
@@ -307,8 +316,11 @@ export class ManifoldStore {
|
|
|
307
316
|
* @param {number} feature_id stable cross-frame identifier of the
|
|
308
317
|
* geometric feature pair that produced this contact; 0 means
|
|
309
318
|
* "no info, fall back to position matching"
|
|
319
|
+
* @param {number} friction combined friction for this contact's specific
|
|
320
|
+
* source-collider pair
|
|
321
|
+
* @param {number} restitution combined restitution for this contact
|
|
310
322
|
*/
|
|
311
|
-
set_contact(slot, idx, lax, lay, laz, lbx, lby, lbz, nx, ny, nz, depth, feature_id = 0) {
|
|
323
|
+
set_contact(slot, idx, lax, lay, laz, lbx, lby, lbz, nx, ny, nz, depth, feature_id = 0, friction = 0, restitution = 0) {
|
|
312
324
|
const off = slot * SLOT_DATA_STRIDE + idx * CONTACT_STRIDE;
|
|
313
325
|
this.__data[off] = lax;
|
|
314
326
|
this.__data[off + 1] = lay;
|
|
@@ -322,6 +334,8 @@ export class ManifoldStore {
|
|
|
322
334
|
this.__data[off + 9] = depth;
|
|
323
335
|
// j_n, j_t1, j_t2 (offsets 10..12) are warm-start; preserved across calls.
|
|
324
336
|
this.__data[off + 13] = feature_id;
|
|
337
|
+
this.__data[off + 14] = friction;
|
|
338
|
+
this.__data[off + 15] = restitution;
|
|
325
339
|
|
|
326
340
|
const meta_off = slot * SLOT_META_STRIDE;
|
|
327
341
|
const count_now = this.__meta[meta_off + 2] & COUNT_MASK;
|
|
@@ -330,6 +344,26 @@ export class ManifoldStore {
|
|
|
330
344
|
}
|
|
331
345
|
}
|
|
332
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Read the per-contact combined friction stored at one contact.
|
|
349
|
+
* @param {number} slot
|
|
350
|
+
* @param {number} idx
|
|
351
|
+
* @returns {number}
|
|
352
|
+
*/
|
|
353
|
+
friction_of(slot, idx) {
|
|
354
|
+
return this.__data[slot * SLOT_DATA_STRIDE + idx * CONTACT_STRIDE + 14];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Read the per-contact combined restitution stored at one contact.
|
|
359
|
+
* @param {number} slot
|
|
360
|
+
* @param {number} idx
|
|
361
|
+
* @returns {number}
|
|
362
|
+
*/
|
|
363
|
+
restitution_of(slot, idx) {
|
|
364
|
+
return this.__data[slot * SLOT_DATA_STRIDE + idx * CONTACT_STRIDE + 15];
|
|
365
|
+
}
|
|
366
|
+
|
|
333
367
|
/**
|
|
334
368
|
* Zero the warm-start impulse fields (j_n, j_t1, j_t2) for one contact
|
|
335
369
|
* index inside a slot. Called by the narrowphase match-and-merge pass
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface-material combine rules — how the friction / restitution of the two
|
|
3
|
+
* colliders in contact are merged into the single coefficient the solver uses
|
|
4
|
+
* for that contact.
|
|
5
|
+
*
|
|
6
|
+
* These live in a shared leaf module because the combine happens in the
|
|
7
|
+
* **narrowphase** (which knows the specific source collider on each side of
|
|
8
|
+
* every contact, so a compound body's per-collider materials are honoured
|
|
9
|
+
* per-contact) and the result is stored in the manifold and read by the
|
|
10
|
+
* **solver**. Both sides import from here, keeping one source of truth for the
|
|
11
|
+
* policy.
|
|
12
|
+
*
|
|
13
|
+
* @author Alex Goldring
|
|
14
|
+
* @copyright Company Named Limited (c) 2026
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Combine two friction coefficients — geometric mean (Bullet / PhysX default).
|
|
18
|
+
* @param {number} a
|
|
19
|
+
* @param {number} b
|
|
20
|
+
* @returns {number}
|
|
21
|
+
*/
|
|
22
|
+
export function combine_friction(a: number, b: number): number;
|
|
23
|
+
/**
|
|
24
|
+
* Combine two restitution coefficients — maximum (Unity / common default).
|
|
25
|
+
* @param {number} a
|
|
26
|
+
* @param {number} b
|
|
27
|
+
* @returns {number}
|
|
28
|
+
*/
|
|
29
|
+
export function combine_restitution(a: number, b: number): number;
|
|
30
|
+
//# sourceMappingURL=combine_material.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"combine_material.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/contact/combine_material.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;GAKG;AACH,oCAJW,MAAM,KACN,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,uCAJW,MAAM,KACN,MAAM,GACJ,MAAM,CAIlB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface-material combine rules — how the friction / restitution of the two
|
|
3
|
+
* colliders in contact are merged into the single coefficient the solver uses
|
|
4
|
+
* for that contact.
|
|
5
|
+
*
|
|
6
|
+
* These live in a shared leaf module because the combine happens in the
|
|
7
|
+
* **narrowphase** (which knows the specific source collider on each side of
|
|
8
|
+
* every contact, so a compound body's per-collider materials are honoured
|
|
9
|
+
* per-contact) and the result is stored in the manifold and read by the
|
|
10
|
+
* **solver**. Both sides import from here, keeping one source of truth for the
|
|
11
|
+
* policy.
|
|
12
|
+
*
|
|
13
|
+
* @author Alex Goldring
|
|
14
|
+
* @copyright Company Named Limited (c) 2026
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Combine two friction coefficients — geometric mean (Bullet / PhysX default).
|
|
19
|
+
* @param {number} a
|
|
20
|
+
* @param {number} b
|
|
21
|
+
* @returns {number}
|
|
22
|
+
*/
|
|
23
|
+
export function combine_friction(a, b) {
|
|
24
|
+
return Math.sqrt(a * b);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Combine two restitution coefficients — maximum (Unity / common default).
|
|
29
|
+
* @param {number} a
|
|
30
|
+
* @param {number} b
|
|
31
|
+
* @returns {number}
|
|
32
|
+
*/
|
|
33
|
+
export function combine_restitution(a, b) {
|
|
34
|
+
return a > b ? a : b;
|
|
35
|
+
}
|
|
@@ -84,6 +84,21 @@ export class Collider {
|
|
|
84
84
|
flags: number;
|
|
85
85
|
};
|
|
86
86
|
fromJSON(json: any): void;
|
|
87
|
+
/**
|
|
88
|
+
* Value equality over the shape and the surface material. System-private
|
|
89
|
+
* fields (`_bvhNode`, `_bodyId`) are excluded. Shapes are compared by value
|
|
90
|
+
* (`shape.equals`), so two colliders sharing one shape instance or holding
|
|
91
|
+
* equal shapes both compare equal.
|
|
92
|
+
*
|
|
93
|
+
* @param {Collider} other
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
equals(other: Collider): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Hash over the same state as {@link equals}. Equal colliders hash equal.
|
|
99
|
+
* @returns {number}
|
|
100
|
+
*/
|
|
101
|
+
hash(): number;
|
|
87
102
|
/**
|
|
88
103
|
* @readonly
|
|
89
104
|
* @type {boolean}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collider.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/ecs/Collider.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Collider.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/ecs/Collider.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,+BAFU,MAAM,CAEmB;AAEnC;;;;;;;;;;GAUG;AACH;IAEI;;;OAGG;IACH,OAFU,eAAe,CAEU;IAEnC;;;;OAIG;IACH,UAFU,MAAM,CAED;IAEf;;;;OAIG;IACH,aAFU,MAAM,CAEA;IAEhB;;;;OAIG;IACH,SAFU,MAAM,CAEJ;IAEZ;;OAEG;IACH,OAFU,aAAa,GAAC,MAAM,CAEH;IAE3B;;;OAGG;IACH,UAFU,MAAM,CAEY;IAE5B;;;;OAIG;IACH,SAFU,MAAM,CAEH;IAEb;;;OAGG;IACH,cAFW,MAAM,GAAC,aAAa,QAI9B;IAED;;;OAGG;IACH,gBAFW,MAAM,GAAC,aAAa,QAI9B;IAED;;;;OAIG;IACH,cAHW,MAAM,GAAC,aAAa,GAClB,OAAO,CAInB;IAED;;;;OAIG;IACH,gBAHW,MAAM,GAAC,aAAa,SACpB,OAAO,QAQjB;IAED;;;;;MAOC;IAED,0BAKC;IAED;;;;;;;;OAQG;IACH,cAHW,QAAQ,GACN,OAAO,CAWnB;IAED;;;OAGG;IACH,QAFa,MAAM,CASlB;IASL;;;OAGG;IACH,qBAFU,OAAO,CAEY;CAZ5B;;kBAIS,MAAM;;gCAhKgB,gDAAgD;8BAElD,oBAAoB"}
|