@woosh/meep-engine 2.139.0 → 2.140.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/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts +3 -3
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js +4 -4
- package/src/{engine/physics/broadphase/aabb_transform_oriented.d.ts → core/geom/3d/aabb/aabb3_transform_oriented.d.ts} +2 -2
- package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -0
- package/src/{engine/physics/broadphase/aabb_transform_oriented.js → core/geom/3d/aabb/aabb3_transform_oriented.js} +1 -1
- package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts +54 -0
- package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
- package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
- package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
- package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
- package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
- package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
- package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
- package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
- package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
- package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/Triangle3D.js +318 -0
- package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
- package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
- package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
- package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
- package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +9 -11
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +40 -18
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +9 -5
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +38 -10
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +14 -5
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +47 -5
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +19 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +75 -13
- package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
- package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
- package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
- package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
- package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
- package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
- package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
- package/src/core/geom/vec3/v3_negate_array.js +2 -2
- package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
- package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
- package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
- package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
- package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
- package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
- package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +16 -3
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -211
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +72 -8
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +37 -5
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +101 -3
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -1416
- package/src/engine/control/first-person/TODO.md +173 -127
- package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/Slide.js +9 -1
- package/src/engine/control/first-person/prototype_first_person_controller.js +88 -2
- package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -1
- package/src/engine/control/first-person/test/buildTestPlayer.js +9 -1
- package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
- package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
- package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
- package/src/engine/physics/BULLET_REVIEW.md +945 -0
- package/src/engine/physics/CANNON_REVIEW.md +1300 -0
- package/src/engine/physics/JOLT_REVIEW.md +913 -0
- package/src/engine/physics/PLAN.md +461 -236
- package/src/engine/physics/RAPIER_REVIEW.md +934 -0
- package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
- package/src/engine/physics/broadphase/compute_fat_world_aabb.js +2 -2
- package/src/engine/physics/contact/ManifoldStore.d.ts +83 -10
- package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
- package/src/engine/physics/contact/ManifoldStore.js +608 -499
- package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
- package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +128 -20
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +1301 -1159
- package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
- package/src/engine/physics/fluid/FluidSimulator.js +2 -1
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +28 -6
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
- package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +39 -17
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
- package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
- package/src/engine/physics/gjk/expanding_polytope_algorithm.js +68 -22
- package/src/engine/physics/gjk/gjk.d.ts +28 -2
- package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
- package/src/engine/physics/gjk/gjk.js +421 -378
- package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
- package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
- package/src/engine/physics/gjk/minkowski_support.js +75 -0
- package/src/engine/physics/gjk/mpr.d.ts +56 -0
- package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
- package/src/engine/physics/gjk/mpr.js +344 -0
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts +20 -5
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
- package/src/engine/physics/inertia/world_inverse_inertia.js +36 -38
- package/src/engine/physics/integration/integrate_position.d.ts +25 -7
- package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
- package/src/engine/physics/integration/integrate_position.js +43 -12
- package/src/engine/physics/integration/integrate_velocity.d.ts +30 -0
- package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
- package/src/engine/physics/integration/integrate_velocity.js +82 -1
- package/src/engine/physics/narrowphase/PosedShape.d.ts +0 -8
- package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/PosedShape.js +28 -30
- package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_box_manifold.js +113 -17
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
- package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/capsule_contacts.js +10 -56
- package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
- package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
- package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
- package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
- package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
- package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
- package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
- package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
- package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
- package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
- package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
- package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts +8 -2
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +1422 -382
- package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/sphere_box_contact.js +16 -23
- package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
- package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
- package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
- package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
- package/src/engine/physics/queries/overlap_shape.js +183 -0
- package/src/engine/physics/queries/shape_cast.d.ts +56 -0
- package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
- package/src/engine/physics/queries/shape_cast.js +387 -0
- package/src/engine/physics/solver/solve_contacts.d.ts +116 -30
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +641 -223
- package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +0 -1
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +0 -20
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +0 -1
- package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.js +0 -83
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capsule_contacts.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/capsule_contacts.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"capsule_contacts.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/capsule_contacts.js"],"names":[],"mappings":"AA4BA;;;;;;;;;;;;;;;GAeG;AACH,2CAVW,MAAM,EAAE,GAAC,YAAY,MACrB,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,eACN,MAAM,QAOhB;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,4CAhBW,MAAM,EAAE,GAAC,YAAY,QACrB,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,YACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,GACJ,OAAO,CA6CnB;AAMD;;;;;GAKG;AACH,6CAHW,MAAM,EAAE,GAAC,YAAY,yNACnB,OAAO,CAuEnB;AAOD;;;;;;;;;;;;;GAaG;AACH,yCAHW,MAAM,EAAE,GAAC,YAAY,4NACnB,OAAO,CA8FnB;AAyCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,gDAtBW,YAAY,QACZ,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,YACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,GACJ,MAAM,CAuElB;AApJD;;;;;;;;;;GAUG;AACH,yCAFU,MAAM,CAE6B;AAE7C;;;;;GAKG;AACH,uCAFU,MAAM,CAE0B"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { line3_closest_points_segment_segment } from "../../../core/geom/3d/line/line3_closest_points_segment_segment.js";
|
|
2
2
|
import { line3_compute_segment_nearest_point_to_point_t } from "../../../core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js";
|
|
3
|
+
import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
|
|
4
|
+
import { v3_quat3_apply_inverse } from "../../../core/geom/vec3/v3_quat3_apply_inverse.js";
|
|
3
5
|
import { sphere_box_contact } from "./sphere_box_contact.js";
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -22,54 +24,6 @@ import { sphere_box_contact } from "./sphere_box_contact.js";
|
|
|
22
24
|
|
|
23
25
|
const scratch_st = new Float64Array(2);
|
|
24
26
|
|
|
25
|
-
/**
|
|
26
|
-
* Rotate a body-local vector into world space via the quaternion identity
|
|
27
|
-
* `v' = q · v · q*`. Writes (rx, ry, rz) into the destination at offset.
|
|
28
|
-
*
|
|
29
|
-
* @param {number[]|Float64Array} out
|
|
30
|
-
* @param {number} off
|
|
31
|
-
* @param {number} vx
|
|
32
|
-
* @param {number} vy
|
|
33
|
-
* @param {number} vz
|
|
34
|
-
* @param {number} qx
|
|
35
|
-
* @param {number} qy
|
|
36
|
-
* @param {number} qz
|
|
37
|
-
* @param {number} qw
|
|
38
|
-
*/
|
|
39
|
-
function rotate_local_to_world(out, off, vx, vy, vz, qx, qy, qz, qw) {
|
|
40
|
-
const ix = qw * vx + qy * vz - qz * vy;
|
|
41
|
-
const iy = qw * vy + qz * vx - qx * vz;
|
|
42
|
-
const iz = qw * vz + qx * vy - qy * vx;
|
|
43
|
-
const iw = -qx * vx - qy * vy - qz * vz;
|
|
44
|
-
out[off] = ix * qw + iw * (-qx) + iy * (-qz) - iz * (-qy);
|
|
45
|
-
out[off + 1] = iy * qw + iw * (-qy) + iz * (-qx) - ix * (-qz);
|
|
46
|
-
out[off + 2] = iz * qw + iw * (-qz) + ix * (-qy) - iy * (-qx);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Inverse-rotate a world vector into body space via the conjugate quaternion.
|
|
51
|
-
*
|
|
52
|
-
* @param {number[]|Float64Array} out
|
|
53
|
-
* @param {number} off
|
|
54
|
-
* @param {number} vx
|
|
55
|
-
* @param {number} vy
|
|
56
|
-
* @param {number} vz
|
|
57
|
-
* @param {number} qx
|
|
58
|
-
* @param {number} qy
|
|
59
|
-
* @param {number} qz
|
|
60
|
-
* @param {number} qw
|
|
61
|
-
*/
|
|
62
|
-
function rotate_world_to_local(out, off, vx, vy, vz, qx, qy, qz, qw) {
|
|
63
|
-
const cx = -qx, cy = -qy, cz = -qz;
|
|
64
|
-
const tx = qw * vx + cy * vz - cz * vy;
|
|
65
|
-
const ty = qw * vy + cz * vx - cx * vz;
|
|
66
|
-
const tz = qw * vz + cx * vy - cy * vx;
|
|
67
|
-
const tw = -cx * vx - cy * vy - cz * vz;
|
|
68
|
-
out[off] = tx * qw + tw * qx + ty * qz - tz * qy;
|
|
69
|
-
out[off + 1] = ty * qw + tw * qy + tz * qx - tx * qz;
|
|
70
|
-
out[off + 2] = tz * qw + tw * qz + tx * qy - ty * qx;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
27
|
const scratch_seg = new Float64Array(6);
|
|
74
28
|
|
|
75
29
|
/**
|
|
@@ -89,8 +43,8 @@ const scratch_seg = new Float64Array(6);
|
|
|
89
43
|
* @param {number} half_height
|
|
90
44
|
*/
|
|
91
45
|
export function capsule_world_segment(out, cx, cy, cz, qx, qy, qz, qw, half_height) {
|
|
92
|
-
|
|
93
|
-
|
|
46
|
+
v3_quat3_apply(out, 0, 0, -half_height, 0, qx, qy, qz, qw);
|
|
47
|
+
v3_quat3_apply(out, 3, 0, half_height, 0, qx, qy, qz, qw);
|
|
94
48
|
out[0] += cx; out[1] += cy; out[2] += cz;
|
|
95
49
|
out[3] += cx; out[4] += cy; out[5] += cz;
|
|
96
50
|
}
|
|
@@ -267,7 +221,7 @@ export function capsule_box_contact(
|
|
|
267
221
|
b_cx, b_cy, b_cz, b_qx, b_qy, b_qz, b_qw, b_hx, b_hy, b_hz
|
|
268
222
|
) {
|
|
269
223
|
// Bring the capsule's segment endpoints into box-local space.
|
|
270
|
-
|
|
224
|
+
v3_quat3_apply_inverse(scratch_a0_local, 0, -0, -a_half_h, 0, a_qx, a_qy, a_qz, a_qw);
|
|
271
225
|
// Note: we constructed the segment ends in body frame as (0, ±h, 0). We
|
|
272
226
|
// need them in world first, then box-local. Build world here directly
|
|
273
227
|
// for clarity.
|
|
@@ -275,8 +229,8 @@ export function capsule_box_contact(
|
|
|
275
229
|
// World → box-local: subtract box centre, then rotate by conjugate of box quat.
|
|
276
230
|
const w_a0x = scratch_seg[0] - b_cx, w_a0y = scratch_seg[1] - b_cy, w_a0z = scratch_seg[2] - b_cz;
|
|
277
231
|
const w_a1x = scratch_seg[3] - b_cx, w_a1y = scratch_seg[4] - b_cy, w_a1z = scratch_seg[5] - b_cz;
|
|
278
|
-
|
|
279
|
-
|
|
232
|
+
v3_quat3_apply_inverse(scratch_a0_local, 0, w_a0x, w_a0y, w_a0z, b_qx, b_qy, b_qz, b_qw);
|
|
233
|
+
v3_quat3_apply_inverse(scratch_a1_local, 0, w_a1x, w_a1y, w_a1z, b_qx, b_qy, b_qz, b_qw);
|
|
280
234
|
|
|
281
235
|
const a0lx = scratch_a0_local[0], a0ly = scratch_a0_local[1], a0lz = scratch_a0_local[2];
|
|
282
236
|
const a1lx = scratch_a1_local[0], a1ly = scratch_a1_local[1], a1lz = scratch_a1_local[2];
|
|
@@ -336,18 +290,18 @@ export function capsule_box_contact(
|
|
|
336
290
|
}
|
|
337
291
|
|
|
338
292
|
// Normal (box → capsule axis point) into world.
|
|
339
|
-
|
|
293
|
+
v3_quat3_apply(out, 0, nlx, nly, nlz, b_qx, b_qy, b_qz, b_qw);
|
|
340
294
|
const nx = out[0], ny = out[1], nz = out[2];
|
|
341
295
|
out[3] = a_radius - dist;
|
|
342
296
|
|
|
343
297
|
// Capsule-side contact: world segment point pushed back by radius.
|
|
344
|
-
|
|
298
|
+
v3_quat3_apply(out, 4, plx, ply, plz, b_qx, b_qy, b_qz, b_qw);
|
|
345
299
|
out[4] += b_cx - nx * a_radius;
|
|
346
300
|
out[5] += b_cy - ny * a_radius;
|
|
347
301
|
out[6] += b_cz - nz * a_radius;
|
|
348
302
|
|
|
349
303
|
// Box-side contact: world projection of the box-surface point.
|
|
350
|
-
|
|
304
|
+
v3_quat3_apply(out, 7, qlx, qly, qlz, b_qx, b_qy, b_qz, b_qw);
|
|
351
305
|
out[7] += b_cx;
|
|
352
306
|
out[8] += b_cy;
|
|
353
307
|
out[9] += b_cz;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {number[]|Float64Array} out length >= CAPSULE_TRIANGLE_MAX_CONTACTS *
|
|
3
|
+
* CAPSULE_TRIANGLE_CONTACT_STRIDE
|
|
4
|
+
* @param {number} c_cx capsule centre x
|
|
5
|
+
* @param {number} c_cy
|
|
6
|
+
* @param {number} c_cz
|
|
7
|
+
* @param {number} c_qx capsule rotation
|
|
8
|
+
* @param {number} c_qy
|
|
9
|
+
* @param {number} c_qz
|
|
10
|
+
* @param {number} c_qw
|
|
11
|
+
* @param {number} c_radius
|
|
12
|
+
* @param {number} c_half_h capsule half-height (not including caps)
|
|
13
|
+
* @param {number} ax triangle vertex A x (world)
|
|
14
|
+
* @param {number} ay
|
|
15
|
+
* @param {number} az
|
|
16
|
+
* @param {number} bx triangle vertex B x (world)
|
|
17
|
+
* @param {number} by
|
|
18
|
+
* @param {number} bz
|
|
19
|
+
* @param {number} cx triangle vertex C x (world)
|
|
20
|
+
* @param {number} cy
|
|
21
|
+
* @param {number} cz
|
|
22
|
+
* @returns {number} number of contacts emitted, in `[0, CAPSULE_TRIANGLE_MAX_CONTACTS]`
|
|
23
|
+
*/
|
|
24
|
+
export function capsule_triangle_contact(out: number[] | Float64Array, c_cx: number, c_cy: number, c_cz: number, c_qx: number, c_qy: number, c_qz: number, c_qw: number, c_radius: number, c_half_h: number, ax: number, ay: number, az: number, bx: number, by: number, bz: number, cx: number, cy: number, cz: number): number;
|
|
25
|
+
/**
|
|
26
|
+
* Multi-point capsule-vs-triangle contact generation.
|
|
27
|
+
*
|
|
28
|
+
* A capsule is the Minkowski sum of a line segment and a sphere of
|
|
29
|
+
* radius `r`, so capsule-vs-triangle reduces to:
|
|
30
|
+
*
|
|
31
|
+
* 1. Find the closest pair of points between the capsule's central
|
|
32
|
+
* segment and the triangle (segment ↔ triangle distance).
|
|
33
|
+
* 2. If distance ≥ radius, no overlap.
|
|
34
|
+
* 3. Otherwise emit a primary contact at the segment-triangle
|
|
35
|
+
* closest pair, then additionally treat each cap centre (segment
|
|
36
|
+
* endpoint) as a sphere of cap-radius and run
|
|
37
|
+
* {@link sphere_triangle_contact} to produce up to 2 endpoint
|
|
38
|
+
* contacts. Spatial dedup against the primary so a capsule
|
|
39
|
+
* touching with one cap doesn't emit two contacts at the same
|
|
40
|
+
* point.
|
|
41
|
+
*
|
|
42
|
+
* The multi-point manifold (1 primary + 2 endpoints) is what allows a
|
|
43
|
+
* capsule resting flat on a triangle face to be stable — a single
|
|
44
|
+
* contact at the segment's closest point is positionally ambiguous
|
|
45
|
+
* (every interior segment point is equidistant from the face), and
|
|
46
|
+
* the body wobbles around whichever point the iterative closest-point
|
|
47
|
+
* solver happened to converge on. The endpoint contacts pin the
|
|
48
|
+
* capsule's rotation about its long axis.
|
|
49
|
+
*
|
|
50
|
+
* Output layout per contact (matches
|
|
51
|
+
* {@link CAPSULE_BOX_CONTACT_STRIDE} so the same downstream wiring
|
|
52
|
+
* works):
|
|
53
|
+
* 0..2 : world contact on capsule side
|
|
54
|
+
* 3..5 : world contact on triangle side
|
|
55
|
+
* 6..8 : normal from triangle (B) toward capsule (A)
|
|
56
|
+
* 9 : depth (positive = penetration)
|
|
57
|
+
*
|
|
58
|
+
* @author Alex Goldring
|
|
59
|
+
* @copyright Company Named Limited (c) 2026
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Output stride per contact.
|
|
63
|
+
* @type {number}
|
|
64
|
+
*/
|
|
65
|
+
export const CAPSULE_TRIANGLE_CONTACT_STRIDE: number;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum contacts emitted: 1 primary + 2 endpoint sphere contacts.
|
|
68
|
+
* @type {number}
|
|
69
|
+
*/
|
|
70
|
+
export const CAPSULE_TRIANGLE_MAX_CONTACTS: number;
|
|
71
|
+
//# sourceMappingURL=capsule_triangle_contact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capsule_triangle_contact.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/capsule_triangle_contact.js"],"names":[],"mappings":"AAgPA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,8CAtBW,MAAM,EAAE,GAAC,YAAY,QAErB,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,QACN,MAAM,YACN,MAAM,YACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,GACJ,MAAM,CAqElB;AArUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH;;;GAGG;AACH,8CAFU,MAAM,CAEkC;AAElD;;;GAGG;AACH,4CAFU,MAAM,CAE+B"}
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { computeTriangleClosestPointToPointBarycentric } from "../../../core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js";
|
|
2
|
+
import { line3_closest_points_segment_segment } from "../../../core/geom/3d/line/line3_closest_points_segment_segment.js";
|
|
3
|
+
import { capsule_world_segment } from "./capsule_contacts.js";
|
|
4
|
+
import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Multi-point capsule-vs-triangle contact generation.
|
|
8
|
+
*
|
|
9
|
+
* A capsule is the Minkowski sum of a line segment and a sphere of
|
|
10
|
+
* radius `r`, so capsule-vs-triangle reduces to:
|
|
11
|
+
*
|
|
12
|
+
* 1. Find the closest pair of points between the capsule's central
|
|
13
|
+
* segment and the triangle (segment ↔ triangle distance).
|
|
14
|
+
* 2. If distance ≥ radius, no overlap.
|
|
15
|
+
* 3. Otherwise emit a primary contact at the segment-triangle
|
|
16
|
+
* closest pair, then additionally treat each cap centre (segment
|
|
17
|
+
* endpoint) as a sphere of cap-radius and run
|
|
18
|
+
* {@link sphere_triangle_contact} to produce up to 2 endpoint
|
|
19
|
+
* contacts. Spatial dedup against the primary so a capsule
|
|
20
|
+
* touching with one cap doesn't emit two contacts at the same
|
|
21
|
+
* point.
|
|
22
|
+
*
|
|
23
|
+
* The multi-point manifold (1 primary + 2 endpoints) is what allows a
|
|
24
|
+
* capsule resting flat on a triangle face to be stable — a single
|
|
25
|
+
* contact at the segment's closest point is positionally ambiguous
|
|
26
|
+
* (every interior segment point is equidistant from the face), and
|
|
27
|
+
* the body wobbles around whichever point the iterative closest-point
|
|
28
|
+
* solver happened to converge on. The endpoint contacts pin the
|
|
29
|
+
* capsule's rotation about its long axis.
|
|
30
|
+
*
|
|
31
|
+
* Output layout per contact (matches
|
|
32
|
+
* {@link CAPSULE_BOX_CONTACT_STRIDE} so the same downstream wiring
|
|
33
|
+
* works):
|
|
34
|
+
* 0..2 : world contact on capsule side
|
|
35
|
+
* 3..5 : world contact on triangle side
|
|
36
|
+
* 6..8 : normal from triangle (B) toward capsule (A)
|
|
37
|
+
* 9 : depth (positive = penetration)
|
|
38
|
+
*
|
|
39
|
+
* @author Alex Goldring
|
|
40
|
+
* @copyright Company Named Limited (c) 2026
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Output stride per contact.
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
export const CAPSULE_TRIANGLE_CONTACT_STRIDE = 10;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Maximum contacts emitted: 1 primary + 2 endpoint sphere contacts.
|
|
51
|
+
* @type {number}
|
|
52
|
+
*/
|
|
53
|
+
export const CAPSULE_TRIANGLE_MAX_CONTACTS = 3;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Two world positions are treated as the same contact when their
|
|
57
|
+
* squared distance is below this threshold. Same value as the box
|
|
58
|
+
* variant — looser than the manifold's warm-start match tolerance
|
|
59
|
+
* (≈0.02) because we specifically want to dedupe coincident
|
|
60
|
+
* primary/endpoint contacts; small lateral shifts produce distinct
|
|
61
|
+
* contacts that should both be kept.
|
|
62
|
+
* @type {number}
|
|
63
|
+
*/
|
|
64
|
+
const DEDUPE_DIST_SQR = 1e-6;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Numerical floor for "segment direction roughly parallel to triangle
|
|
68
|
+
* plane". Used to bail out of the case-0 (segment-plane intersection)
|
|
69
|
+
* branch before dividing by `d · n`.
|
|
70
|
+
* @type {number}
|
|
71
|
+
*/
|
|
72
|
+
const PARALLEL_EPS = 1e-12;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Tolerance for "reconstructed point matches input" in the inside-
|
|
76
|
+
* triangle check during case-0.
|
|
77
|
+
* @type {number}
|
|
78
|
+
*/
|
|
79
|
+
const INSIDE_TRIANGLE_EPS_SQR = 1e-10;
|
|
80
|
+
|
|
81
|
+
// --- scratch (allocation-free across calls) ---------------------------------
|
|
82
|
+
|
|
83
|
+
const scratch_seg = new Float64Array(6);
|
|
84
|
+
const scratch_bary = new Float64Array(2);
|
|
85
|
+
const scratch_pair_st = new Float64Array(2);
|
|
86
|
+
const scratch_seg_tri = new Float64Array(6); // closest segment / triangle world points
|
|
87
|
+
const scratch_sphere = new Float64Array(10);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Compute the closest pair of points between a 3D line segment
|
|
91
|
+
* `(P1, P2)` and a triangle `(A, B, C)`. Writes the closest segment
|
|
92
|
+
* point into `out[0..2]` and the closest triangle point into
|
|
93
|
+
* `out[3..5]`, and returns the (positive) distance between them.
|
|
94
|
+
*
|
|
95
|
+
* Implementation: take the minimum of 6 candidate distances —
|
|
96
|
+
* - Case 0: segment crosses triangle's plane inside the face
|
|
97
|
+
* → distance 0; both points coincide on the face.
|
|
98
|
+
* - Cases 1, 2: each segment endpoint vs its closest point on the
|
|
99
|
+
* triangle (via {@link computeTriangleClosestPointToPointBarycentric}).
|
|
100
|
+
* Subsumes the "endpoint vs triangle vertex / edge / face" cases.
|
|
101
|
+
* - Cases 3, 4, 5: segment vs each of the triangle's 3 edges, via
|
|
102
|
+
* {@link line3_closest_points_segment_segment}. Covers the
|
|
103
|
+
* "segment interior closest to triangle edge" case that the
|
|
104
|
+
* endpoint queries miss.
|
|
105
|
+
*
|
|
106
|
+
* @param {number[]|Float64Array} out length >= 6
|
|
107
|
+
*/
|
|
108
|
+
function segment_triangle_closest(
|
|
109
|
+
out,
|
|
110
|
+
p1x, p1y, p1z, p2x, p2y, p2z,
|
|
111
|
+
ax, ay, az, bx, by, bz, cx, cy, cz
|
|
112
|
+
) {
|
|
113
|
+
let best_dist_sqr = Infinity;
|
|
114
|
+
let best_sx = 0, best_sy = 0, best_sz = 0;
|
|
115
|
+
let best_tx = 0, best_ty = 0, best_tz = 0;
|
|
116
|
+
|
|
117
|
+
// --- Case 0: segment intersects triangle plane INSIDE the face -----------
|
|
118
|
+
const dx_seg = p2x - p1x, dy_seg = p2y - p1y, dz_seg = p2z - p1z;
|
|
119
|
+
const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
|
|
120
|
+
const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
|
|
121
|
+
const tnx = e1y * e2z - e1z * e2y;
|
|
122
|
+
const tny = e1z * e2x - e1x * e2z;
|
|
123
|
+
const tnz = e1x * e2y - e1y * e2x;
|
|
124
|
+
const d_dot_n = dx_seg * tnx + dy_seg * tny + dz_seg * tnz;
|
|
125
|
+
if (Math.abs(d_dot_n) > PARALLEL_EPS) {
|
|
126
|
+
const t = ((ax - p1x) * tnx + (ay - p1y) * tny + (az - p1z) * tnz) / d_dot_n;
|
|
127
|
+
if (t >= 0 && t <= 1) {
|
|
128
|
+
const ix = p1x + dx_seg * t;
|
|
129
|
+
const iy = p1y + dy_seg * t;
|
|
130
|
+
const iz = p1z + dz_seg * t;
|
|
131
|
+
// Inside-triangle test: feed the intersection through the
|
|
132
|
+
// closest-point algorithm; it returns the BARYCENTRIC of the
|
|
133
|
+
// closest point on the triangle. If the closest point equals
|
|
134
|
+
// the input (within INSIDE_TRIANGLE_EPS_SQR), the intersection
|
|
135
|
+
// is inside the face.
|
|
136
|
+
computeTriangleClosestPointToPointBarycentric(
|
|
137
|
+
scratch_bary, 0,
|
|
138
|
+
ix, iy, iz,
|
|
139
|
+
ax, ay, az,
|
|
140
|
+
bx, by, bz,
|
|
141
|
+
cx, cy, cz
|
|
142
|
+
);
|
|
143
|
+
const alpha = scratch_bary[0];
|
|
144
|
+
const beta = scratch_bary[1];
|
|
145
|
+
const gamma = 1 - alpha - beta;
|
|
146
|
+
const rx = alpha * ax + beta * bx + gamma * cx;
|
|
147
|
+
const ry = alpha * ay + beta * by + gamma * cy;
|
|
148
|
+
const rz = alpha * az + beta * bz + gamma * cz;
|
|
149
|
+
const diff_x = rx - ix, diff_y = ry - iy, diff_z = rz - iz;
|
|
150
|
+
if (diff_x * diff_x + diff_y * diff_y + diff_z * diff_z < INSIDE_TRIANGLE_EPS_SQR) {
|
|
151
|
+
out[0] = ix; out[1] = iy; out[2] = iz;
|
|
152
|
+
out[3] = ix; out[4] = iy; out[5] = iz;
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// --- Case 1: P1 vs closest-on-triangle -----------------------------------
|
|
159
|
+
computeTriangleClosestPointToPointBarycentric(
|
|
160
|
+
scratch_bary, 0,
|
|
161
|
+
p1x, p1y, p1z,
|
|
162
|
+
ax, ay, az,
|
|
163
|
+
bx, by, bz,
|
|
164
|
+
cx, cy, cz
|
|
165
|
+
);
|
|
166
|
+
{
|
|
167
|
+
const alpha = scratch_bary[0];
|
|
168
|
+
const beta = scratch_bary[1];
|
|
169
|
+
const gamma = 1 - alpha - beta;
|
|
170
|
+
const qx = alpha * ax + beta * bx + gamma * cx;
|
|
171
|
+
const qy = alpha * ay + beta * by + gamma * cy;
|
|
172
|
+
const qz = alpha * az + beta * bz + gamma * cz;
|
|
173
|
+
const dx = p1x - qx, dy = p1y - qy, dz = p1z - qz;
|
|
174
|
+
const d_sqr = dx * dx + dy * dy + dz * dz;
|
|
175
|
+
if (d_sqr < best_dist_sqr) {
|
|
176
|
+
best_dist_sqr = d_sqr;
|
|
177
|
+
best_sx = p1x; best_sy = p1y; best_sz = p1z;
|
|
178
|
+
best_tx = qx; best_ty = qy; best_tz = qz;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- Case 2: P2 vs closest-on-triangle -----------------------------------
|
|
183
|
+
computeTriangleClosestPointToPointBarycentric(
|
|
184
|
+
scratch_bary, 0,
|
|
185
|
+
p2x, p2y, p2z,
|
|
186
|
+
ax, ay, az,
|
|
187
|
+
bx, by, bz,
|
|
188
|
+
cx, cy, cz
|
|
189
|
+
);
|
|
190
|
+
{
|
|
191
|
+
const alpha = scratch_bary[0];
|
|
192
|
+
const beta = scratch_bary[1];
|
|
193
|
+
const gamma = 1 - alpha - beta;
|
|
194
|
+
const qx = alpha * ax + beta * bx + gamma * cx;
|
|
195
|
+
const qy = alpha * ay + beta * by + gamma * cy;
|
|
196
|
+
const qz = alpha * az + beta * bz + gamma * cz;
|
|
197
|
+
const dx = p2x - qx, dy = p2y - qy, dz = p2z - qz;
|
|
198
|
+
const d_sqr = dx * dx + dy * dy + dz * dz;
|
|
199
|
+
if (d_sqr < best_dist_sqr) {
|
|
200
|
+
best_dist_sqr = d_sqr;
|
|
201
|
+
best_sx = p2x; best_sy = p2y; best_sz = p2z;
|
|
202
|
+
best_tx = qx; best_ty = qy; best_tz = qz;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- Cases 3..5: segment vs each triangle edge ---------------------------
|
|
207
|
+
// Helper inline to avoid array allocation.
|
|
208
|
+
function test_edge(e_p1x, e_p1y, e_p1z, e_p2x, e_p2y, e_p2z) {
|
|
209
|
+
line3_closest_points_segment_segment(
|
|
210
|
+
scratch_pair_st,
|
|
211
|
+
p1x, p1y, p1z, p2x, p2y, p2z,
|
|
212
|
+
e_p1x, e_p1y, e_p1z, e_p2x, e_p2y, e_p2z
|
|
213
|
+
);
|
|
214
|
+
const s = scratch_pair_st[0];
|
|
215
|
+
const t = scratch_pair_st[1];
|
|
216
|
+
const sx = p1x + s * (p2x - p1x);
|
|
217
|
+
const sy = p1y + s * (p2y - p1y);
|
|
218
|
+
const sz = p1z + s * (p2z - p1z);
|
|
219
|
+
const tx = e_p1x + t * (e_p2x - e_p1x);
|
|
220
|
+
const ty = e_p1y + t * (e_p2y - e_p1y);
|
|
221
|
+
const tz = e_p1z + t * (e_p2z - e_p1z);
|
|
222
|
+
const dx = sx - tx, dy = sy - ty, dz = sz - tz;
|
|
223
|
+
const d_sqr = dx * dx + dy * dy + dz * dz;
|
|
224
|
+
if (d_sqr < best_dist_sqr) {
|
|
225
|
+
best_dist_sqr = d_sqr;
|
|
226
|
+
best_sx = sx; best_sy = sy; best_sz = sz;
|
|
227
|
+
best_tx = tx; best_ty = ty; best_tz = tz;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
test_edge(ax, ay, az, bx, by, bz);
|
|
231
|
+
test_edge(bx, by, bz, cx, cy, cz);
|
|
232
|
+
test_edge(cx, cy, cz, ax, ay, az);
|
|
233
|
+
|
|
234
|
+
out[0] = best_sx; out[1] = best_sy; out[2] = best_sz;
|
|
235
|
+
out[3] = best_tx; out[4] = best_ty; out[5] = best_tz;
|
|
236
|
+
return Math.sqrt(best_dist_sqr);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// --- main --------------------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @param {number[]|Float64Array} out length >= CAPSULE_TRIANGLE_MAX_CONTACTS *
|
|
243
|
+
* CAPSULE_TRIANGLE_CONTACT_STRIDE
|
|
244
|
+
* @param {number} c_cx capsule centre x
|
|
245
|
+
* @param {number} c_cy
|
|
246
|
+
* @param {number} c_cz
|
|
247
|
+
* @param {number} c_qx capsule rotation
|
|
248
|
+
* @param {number} c_qy
|
|
249
|
+
* @param {number} c_qz
|
|
250
|
+
* @param {number} c_qw
|
|
251
|
+
* @param {number} c_radius
|
|
252
|
+
* @param {number} c_half_h capsule half-height (not including caps)
|
|
253
|
+
* @param {number} ax triangle vertex A x (world)
|
|
254
|
+
* @param {number} ay
|
|
255
|
+
* @param {number} az
|
|
256
|
+
* @param {number} bx triangle vertex B x (world)
|
|
257
|
+
* @param {number} by
|
|
258
|
+
* @param {number} bz
|
|
259
|
+
* @param {number} cx triangle vertex C x (world)
|
|
260
|
+
* @param {number} cy
|
|
261
|
+
* @param {number} cz
|
|
262
|
+
* @returns {number} number of contacts emitted, in `[0, CAPSULE_TRIANGLE_MAX_CONTACTS]`
|
|
263
|
+
*/
|
|
264
|
+
export function capsule_triangle_contact(
|
|
265
|
+
out,
|
|
266
|
+
c_cx, c_cy, c_cz, c_qx, c_qy, c_qz, c_qw, c_radius, c_half_h,
|
|
267
|
+
ax, ay, az, bx, by, bz, cx, cy, cz
|
|
268
|
+
) {
|
|
269
|
+
// Capsule's world-space segment endpoints.
|
|
270
|
+
capsule_world_segment(scratch_seg, c_cx, c_cy, c_cz, c_qx, c_qy, c_qz, c_qw, c_half_h);
|
|
271
|
+
const sax = scratch_seg[0], say = scratch_seg[1], saz = scratch_seg[2];
|
|
272
|
+
const sbx = scratch_seg[3], sby = scratch_seg[4], sbz = scratch_seg[5];
|
|
273
|
+
|
|
274
|
+
// Primary contact: closest segment point ↔ closest triangle point.
|
|
275
|
+
const dist = segment_triangle_closest(
|
|
276
|
+
scratch_seg_tri,
|
|
277
|
+
sax, say, saz, sbx, sby, sbz,
|
|
278
|
+
ax, ay, az, bx, by, bz, cx, cy, cz
|
|
279
|
+
);
|
|
280
|
+
if (dist >= c_radius) return 0;
|
|
281
|
+
|
|
282
|
+
const seg_pt_x = scratch_seg_tri[0];
|
|
283
|
+
const seg_pt_y = scratch_seg_tri[1];
|
|
284
|
+
const seg_pt_z = scratch_seg_tri[2];
|
|
285
|
+
const tri_pt_x = scratch_seg_tri[3];
|
|
286
|
+
const tri_pt_y = scratch_seg_tri[4];
|
|
287
|
+
const tri_pt_z = scratch_seg_tri[5];
|
|
288
|
+
|
|
289
|
+
// Normal points from the triangle closest point toward the segment.
|
|
290
|
+
let nx, ny, nz;
|
|
291
|
+
if (dist > 0) {
|
|
292
|
+
const inv = 1 / dist;
|
|
293
|
+
nx = (seg_pt_x - tri_pt_x) * inv;
|
|
294
|
+
ny = (seg_pt_y - tri_pt_y) * inv;
|
|
295
|
+
nz = (seg_pt_z - tri_pt_z) * inv;
|
|
296
|
+
} else {
|
|
297
|
+
// Segment crosses or touches the triangle plane on the face —
|
|
298
|
+
// pick the triangle's outward face normal as a deterministic
|
|
299
|
+
// tie-break, same convention as sphere_triangle_contact.
|
|
300
|
+
const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
|
|
301
|
+
const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
|
|
302
|
+
const fnx = e1y * e2z - e1z * e2y;
|
|
303
|
+
const fny = e1z * e2x - e1x * e2z;
|
|
304
|
+
const fnz = e1x * e2y - e1y * e2x;
|
|
305
|
+
const fn_mag = Math.sqrt(fnx * fnx + fny * fny + fnz * fnz);
|
|
306
|
+
if (fn_mag === 0) return 0; // degenerate triangle
|
|
307
|
+
nx = fnx / fn_mag;
|
|
308
|
+
ny = fny / fn_mag;
|
|
309
|
+
nz = fnz / fn_mag;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const depth = c_radius - dist;
|
|
313
|
+
|
|
314
|
+
// Primary contact emission.
|
|
315
|
+
// Capsule surface point: segment point pushed -radius along the normal
|
|
316
|
+
// (back toward the triangle).
|
|
317
|
+
out[0] = seg_pt_x - nx * c_radius;
|
|
318
|
+
out[1] = seg_pt_y - ny * c_radius;
|
|
319
|
+
out[2] = seg_pt_z - nz * c_radius;
|
|
320
|
+
out[3] = tri_pt_x; out[4] = tri_pt_y; out[5] = tri_pt_z;
|
|
321
|
+
out[6] = nx; out[7] = ny; out[8] = nz;
|
|
322
|
+
out[9] = depth;
|
|
323
|
+
let count = 1;
|
|
324
|
+
|
|
325
|
+
// Endpoint sphere contacts. Inline the 2 endpoint queries to keep
|
|
326
|
+
// allocation-free.
|
|
327
|
+
count = try_endpoint_contact(out, count, sax, say, saz, c_radius, ax, ay, az, bx, by, bz, cx, cy, cz);
|
|
328
|
+
count = try_endpoint_contact(out, count, sbx, sby, sbz, c_radius, ax, ay, az, bx, by, bz, cx, cy, cz);
|
|
329
|
+
|
|
330
|
+
return count;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Run sphere_triangle_contact at one cap centre and append the
|
|
335
|
+
* resulting contact to `out` (with spatial dedup against the
|
|
336
|
+
* already-emitted entries). Returns the new contact count.
|
|
337
|
+
*
|
|
338
|
+
* @returns {number}
|
|
339
|
+
*/
|
|
340
|
+
function try_endpoint_contact(
|
|
341
|
+
out, count,
|
|
342
|
+
cap_x, cap_y, cap_z, radius,
|
|
343
|
+
ax, ay, az, bx, by, bz, cx, cy, cz
|
|
344
|
+
) {
|
|
345
|
+
const ok = sphere_triangle_contact(
|
|
346
|
+
scratch_sphere,
|
|
347
|
+
cap_x, cap_y, cap_z, radius,
|
|
348
|
+
ax, ay, az, bx, by, bz, cx, cy, cz
|
|
349
|
+
);
|
|
350
|
+
if (!ok) return count;
|
|
351
|
+
|
|
352
|
+
const e_nx = scratch_sphere[0], e_ny = scratch_sphere[1], e_nz = scratch_sphere[2];
|
|
353
|
+
const e_depth = scratch_sphere[3];
|
|
354
|
+
const e_cap_x = scratch_sphere[4], e_cap_y = scratch_sphere[5], e_cap_z = scratch_sphere[6];
|
|
355
|
+
const e_tri_x = scratch_sphere[7], e_tri_y = scratch_sphere[8], e_tri_z = scratch_sphere[9];
|
|
356
|
+
|
|
357
|
+
// Dedup: compare capsule-side world position against every prior
|
|
358
|
+
// contact. Two distinct triangle-surface points always have
|
|
359
|
+
// distinct capsule-side projections along the cap-radius vector,
|
|
360
|
+
// so capsule-side position uniquely identifies a contact.
|
|
361
|
+
for (let k = 0; k < count; k++) {
|
|
362
|
+
const ko = k * CAPSULE_TRIANGLE_CONTACT_STRIDE;
|
|
363
|
+
const dx = e_cap_x - out[ko];
|
|
364
|
+
const dy = e_cap_y - out[ko + 1];
|
|
365
|
+
const dz = e_cap_z - out[ko + 2];
|
|
366
|
+
if (dx * dx + dy * dy + dz * dz < DEDUPE_DIST_SQR) return count;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const o = count * CAPSULE_TRIANGLE_CONTACT_STRIDE;
|
|
370
|
+
out[o] = e_cap_x; out[o + 1] = e_cap_y; out[o + 2] = e_cap_z;
|
|
371
|
+
out[o + 3] = e_tri_x; out[o + 4] = e_tri_y; out[o + 5] = e_tri_z;
|
|
372
|
+
out[o + 6] = e_nx; out[o + 7] = e_ny; out[o + 8] = e_nz;
|
|
373
|
+
out[o + 9] = e_depth;
|
|
374
|
+
return count + 1;
|
|
375
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute the penetration depth between two shapes at world poses,
|
|
3
|
+
* returning the depth and writing the separation direction.
|
|
4
|
+
*
|
|
5
|
+
* If the shapes overlap, the return value is the positive distance
|
|
6
|
+
* along `out_direction` you would have to translate `shape_a` to
|
|
7
|
+
* separate it from `shape_b`. `out_direction` is the unit vector
|
|
8
|
+
* pointing from `shape_b` toward `shape_a` — applying
|
|
9
|
+
* `out_direction * return_value` to `position_a` (or equivalently
|
|
10
|
+
* `-out_direction * return_value` to `position_b`) is the minimum
|
|
11
|
+
* translation that produces separation.
|
|
12
|
+
*
|
|
13
|
+
* If the shapes do not overlap (or EPA degenerates on a tangent
|
|
14
|
+
* contact), the return value is `0` and `out_direction` is left
|
|
15
|
+
* untouched. Callers should treat 0 as "no penetration".
|
|
16
|
+
*
|
|
17
|
+
* Sign convention matches the narrowphase's stored contact normal:
|
|
18
|
+
* - `out_direction` ≡ "B → A" direction
|
|
19
|
+
* - Negative of cast direction in shape_cast at the kiss point
|
|
20
|
+
* - The outward normal of B's surface at the contact, pointing
|
|
21
|
+
* toward A
|
|
22
|
+
*
|
|
23
|
+
* Built on the existing GJK + EPA + PosedShape primitives — same
|
|
24
|
+
* precision characteristics (essentially exact for sign-based supports
|
|
25
|
+
* like cubes; asymptotic on curved supports like spheres near tangent,
|
|
26
|
+
* where the polytope iteration cap leaves a small angular residual on
|
|
27
|
+
* the direction).
|
|
28
|
+
*
|
|
29
|
+
* ## Non-convex support
|
|
30
|
+
*
|
|
31
|
+
* Exactly **one** of the two shapes may be non-convex (heightmap,
|
|
32
|
+
* mesh). The non-convex shape is decomposed into triangles overlapping
|
|
33
|
+
* the convex shape's AABB (via the same machinery the narrowphase
|
|
34
|
+
* uses), and per-triangle GJK + EPA is run; the deepest contact's
|
|
35
|
+
* direction and depth are reported. Concave-vs-concave throws — the
|
|
36
|
+
* M×N triangle-pair cost is out of scope for this primitive (and is
|
|
37
|
+
* also refused by the narrowphase for dynamic pairs).
|
|
38
|
+
*
|
|
39
|
+
* ## Non-convex precision limits
|
|
40
|
+
*
|
|
41
|
+
* The concave path is a per-triangle half-space test (convex's deepest
|
|
42
|
+
* point along −face_normal, compared to the triangle's plane). This
|
|
43
|
+
* is exact for **heightmaps** — adjacent triangles cover the
|
|
44
|
+
* boundary cases, and the face normal IS the contact direction.
|
|
45
|
+
*
|
|
46
|
+
* For **closed meshes** the half-space test extrapolates each triangle
|
|
47
|
+
* as an infinite plane, which can over-report depth on side faces
|
|
48
|
+
* when the convex shape extends past a face's 2D extent. The
|
|
49
|
+
* deepest-wins aggregation then picks a "false-deepest" face whose
|
|
50
|
+
* direction may not be the geometrically optimal one. A closed-form
|
|
51
|
+
* triangle-vs-X solver per primitive shape would fix this; until
|
|
52
|
+
* then, the function reports *some* outward direction with positive
|
|
53
|
+
* depth, which still resolves penetration over multiple iterations.
|
|
54
|
+
*
|
|
55
|
+
* Bodies fully inside the concave solid (or below a heightmap
|
|
56
|
+
* surface) are correctly recovered: every face's deepest-inward
|
|
57
|
+
* support point lands on the inward side, so the half-space test
|
|
58
|
+
* fires, and the deepest face wins. The reported direction pushes
|
|
59
|
+
* the body outward through that face.
|
|
60
|
+
*
|
|
61
|
+
* @param {Float64Array|number[]} out_direction length ≥ 3; receives
|
|
62
|
+
* the unit separation direction (B → A) on penetration
|
|
63
|
+
* @param {AbstractShape3D} shape_a in shape_a's local frame; may be concave
|
|
64
|
+
* @param {{x:number,y:number,z:number}} position_a world position of A
|
|
65
|
+
* @param {{x:number,y:number,z:number,w:number}} rotation_a world rotation of A
|
|
66
|
+
* @param {AbstractShape3D} shape_b in shape_b's local frame; may be concave
|
|
67
|
+
* @param {{x:number,y:number,z:number}} position_b world position of B
|
|
68
|
+
* @param {{x:number,y:number,z:number,w:number}} rotation_b world rotation of B
|
|
69
|
+
* @returns {number} penetration depth (positive) on overlap, 0 otherwise
|
|
70
|
+
* @throws {Error} if both shapes have `is_convex === false`
|
|
71
|
+
*/
|
|
72
|
+
export function compute_penetration(out_direction: Float64Array | number[], shape_a: AbstractShape3D, position_a: {
|
|
73
|
+
x: number;
|
|
74
|
+
y: number;
|
|
75
|
+
z: number;
|
|
76
|
+
}, rotation_a: {
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
z: number;
|
|
80
|
+
w: number;
|
|
81
|
+
}, shape_b: AbstractShape3D, position_b: {
|
|
82
|
+
x: number;
|
|
83
|
+
y: number;
|
|
84
|
+
z: number;
|
|
85
|
+
}, rotation_b: {
|
|
86
|
+
x: number;
|
|
87
|
+
y: number;
|
|
88
|
+
z: number;
|
|
89
|
+
w: number;
|
|
90
|
+
}): number;
|
|
91
|
+
//# sourceMappingURL=compute_penetration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute_penetration.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/compute_penetration.js"],"names":[],"mappings":"AAkEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AACH,mDAXW,YAAY,GAAC,MAAM,EAAE,wCAGrB;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,cAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,wCAErC;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,cAC5B;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,GACnC,MAAM,CA4BlB"}
|