@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
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
import { BVH } from "../../../bvh2/bvh3/BVH.js";
|
|
2
|
+
import { bvh_query_user_data_overlaps_aabb } from "../../../bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js";
|
|
3
|
+
import { computeTriangleClosestPointToPointBarycentric } from "../triangle/computeTriangleClosestPointToPointBarycentric.js";
|
|
4
|
+
import { compute_tetrahedron_volume } from "../tetrahedra/compute_tetrahedron_volume.js";
|
|
5
|
+
import { TetrahedralMesh } from "../tetrahedra/TetrahedralMesh.js";
|
|
6
|
+
import { AbstractShape3D } from "./AbstractShape3D.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Arbitrary triangle-mesh collider.
|
|
10
|
+
*
|
|
11
|
+
* The shape carries two synchronized representations:
|
|
12
|
+
* - flat (`positions`, `indices`) surface — used by GJK's support
|
|
13
|
+
* function and the per-triangle distance queries;
|
|
14
|
+
* - a {@link TetrahedralMesh} of the *interior* (built from the surface
|
|
15
|
+
* by `compute_tetrahedral_mesh_from_surface`) plus a {@link BVH}
|
|
16
|
+
* keyed by tet AABB — used by `contains_point` and any future
|
|
17
|
+
* volume-aware query.
|
|
18
|
+
*
|
|
19
|
+
* The tet decomposition gives us convex primitives. Two consequences:
|
|
20
|
+
* 1. `contains_point` is a point-in-AABB BVH query followed by a
|
|
21
|
+
* per-tet `orient3d` test. Critically this works for **disconnected
|
|
22
|
+
* meshes** (two cubes welded into one buffer; a torus and a sphere
|
|
23
|
+
* handed to the same shape). A walking query started from one
|
|
24
|
+
* component can't reach the others — that was the previous
|
|
25
|
+
* implementation's silent bug.
|
|
26
|
+
* 2. Per-point queries are O(log N_tets) on average — the BVH's
|
|
27
|
+
* traversal short-circuits cleanly when the query point is far
|
|
28
|
+
* from the mesh.
|
|
29
|
+
*
|
|
30
|
+
* Construct via {@link shape_mesh_from_geometry} rather than `new
|
|
31
|
+
* MeshShape3D()` directly — the factory compacts the tet mesh, builds
|
|
32
|
+
* the BVH, and caches the bbox / volume / surface area.
|
|
33
|
+
*
|
|
34
|
+
* Narrowphase routing:
|
|
35
|
+
* - GJK + EPA for the general case, using {@link support}.
|
|
36
|
+
* - The support function currently returns the deepest tet-mesh
|
|
37
|
+
* vertex, which gives GJK the *convex hull* of the mesh — not the
|
|
38
|
+
* true non-convex surface. For convex authored geometry that's
|
|
39
|
+
* fine; for highly concave shapes (a torus's hole, an L-bracket,
|
|
40
|
+
* two disconnected components) the convex-hull approximation
|
|
41
|
+
* overstates the collision volume. The accurate fix is a per-tet
|
|
42
|
+
* GJK loop in the narrowphase, which would consume `tet_mesh` and
|
|
43
|
+
* `tet_positions` directly — see PLAN.md's mesh-vs-convex
|
|
44
|
+
* closed-form item.
|
|
45
|
+
*
|
|
46
|
+
* @author Alex Goldring
|
|
47
|
+
* @copyright Company Named Limited (c) 2026
|
|
48
|
+
*/
|
|
49
|
+
export class MeshShape3D extends AbstractShape3D {
|
|
50
|
+
|
|
51
|
+
constructor() {
|
|
52
|
+
super();
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Surface vertex positions, flat `(x, y, z)` per vertex. Authored
|
|
56
|
+
* by the factory; mutate at your own risk (the cached bbox /
|
|
57
|
+
* volume / surface area / BVH will go stale).
|
|
58
|
+
* @type {Float32Array}
|
|
59
|
+
*/
|
|
60
|
+
this.positions = new Float32Array(0);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Surface triangle indices, three uint32 per face referring to
|
|
64
|
+
* {@link positions}. Same mutate-at-your-own-risk caveat.
|
|
65
|
+
* @type {Uint32Array}
|
|
66
|
+
*/
|
|
67
|
+
this.indices = new Uint32Array(0);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Tetrahedral decomposition of the interior. The factory
|
|
71
|
+
* compacts the mesh before storing (low-index slots that the
|
|
72
|
+
* carve pass freed get refilled), so iteration via
|
|
73
|
+
* `tet_id in [0, tet_mesh.count)` is safe — no need for
|
|
74
|
+
* `forEach` or `exists` filtering on the hot path.
|
|
75
|
+
* @type {TetrahedralMesh}
|
|
76
|
+
*/
|
|
77
|
+
this.tet_mesh = new TetrahedralMesh();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Flat positions array for {@link tet_mesh}. Includes the
|
|
81
|
+
* surface vertices and any hole-closing centroids added during
|
|
82
|
+
* tetrahedralisation. For closed airtight meshes this equals
|
|
83
|
+
* {@link positions} element-wise; for meshes with boundary holes
|
|
84
|
+
* (rare in authored content, common in mis-exported assets) the
|
|
85
|
+
* trailing slots carry the closure centroids.
|
|
86
|
+
* @type {Float32Array}
|
|
87
|
+
*/
|
|
88
|
+
this.tet_positions = new Float32Array(0);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* BVH over the tet mesh, keyed by tet index. Built by the
|
|
92
|
+
* factory after compacting the tet mesh, so leaf user_data
|
|
93
|
+
* values are valid tet indices that can be passed straight to
|
|
94
|
+
* `tet_mesh.getVertexIndex`.
|
|
95
|
+
* @type {BVH}
|
|
96
|
+
*/
|
|
97
|
+
this.tet_bvh = new BVH();
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Cached axis-aligned bounding box, layout `[minX, minY, minZ,
|
|
101
|
+
* maxX, maxY, maxZ]`.
|
|
102
|
+
* @private
|
|
103
|
+
* @type {Float64Array}
|
|
104
|
+
*/
|
|
105
|
+
this.__bbox = new Float64Array([0, 0, 0, 0, 0, 0]);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Cached total volume in the shape's local frame, computed from
|
|
109
|
+
* the tetrahedral decomposition at construction.
|
|
110
|
+
* @private
|
|
111
|
+
* @type {number}
|
|
112
|
+
*/
|
|
113
|
+
this.__volume = 0;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Cached total surface area, sum of triangle areas.
|
|
117
|
+
* @private
|
|
118
|
+
* @type {number}
|
|
119
|
+
*/
|
|
120
|
+
this.__surface_area = 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get volume() {
|
|
124
|
+
return this.__volume;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
get surface_area() {
|
|
128
|
+
return this.__surface_area;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
compute_bounding_box(result) {
|
|
132
|
+
const b = this.__bbox;
|
|
133
|
+
result[0] = b[0]; result[1] = b[1]; result[2] = b[2];
|
|
134
|
+
result[3] = b[3]; result[4] = b[4]; result[5] = b[5];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Refresh the cached bounding box, volume, and surface area from the
|
|
139
|
+
* current `positions` / `indices` / `tet_mesh`. Called by
|
|
140
|
+
* {@link shape_mesh_from_geometry} at construction; call again after
|
|
141
|
+
* mutating the underlying data.
|
|
142
|
+
*/
|
|
143
|
+
recompute_cached() {
|
|
144
|
+
const positions = this.positions;
|
|
145
|
+
const indices = this.indices;
|
|
146
|
+
const bbox = this.__bbox;
|
|
147
|
+
|
|
148
|
+
let min_x = Infinity, min_y = Infinity, min_z = Infinity;
|
|
149
|
+
let max_x = -Infinity, max_y = -Infinity, max_z = -Infinity;
|
|
150
|
+
const v_count = positions.length / 3;
|
|
151
|
+
for (let i = 0; i < v_count; i++) {
|
|
152
|
+
const x = positions[i * 3];
|
|
153
|
+
const y = positions[i * 3 + 1];
|
|
154
|
+
const z = positions[i * 3 + 2];
|
|
155
|
+
if (x < min_x) min_x = x; if (x > max_x) max_x = x;
|
|
156
|
+
if (y < min_y) min_y = y; if (y > max_y) max_y = y;
|
|
157
|
+
if (z < min_z) min_z = z; if (z > max_z) max_z = z;
|
|
158
|
+
}
|
|
159
|
+
if (v_count === 0) {
|
|
160
|
+
min_x = min_y = min_z = max_x = max_y = max_z = 0;
|
|
161
|
+
}
|
|
162
|
+
bbox[0] = min_x; bbox[1] = min_y; bbox[2] = min_z;
|
|
163
|
+
bbox[3] = max_x; bbox[4] = max_y; bbox[5] = max_z;
|
|
164
|
+
|
|
165
|
+
// Surface area: ½‖e1 × e2‖ per triangle.
|
|
166
|
+
let area_sum = 0;
|
|
167
|
+
const tri_count = indices.length / 3;
|
|
168
|
+
for (let i = 0; i < tri_count; i++) {
|
|
169
|
+
const ia = indices[i * 3] * 3;
|
|
170
|
+
const ib = indices[i * 3 + 1] * 3;
|
|
171
|
+
const ic = indices[i * 3 + 2] * 3;
|
|
172
|
+
const ax = positions[ia], ay = positions[ia + 1], az = positions[ia + 2];
|
|
173
|
+
const bx = positions[ib], by = positions[ib + 1], bz = positions[ib + 2];
|
|
174
|
+
const cx = positions[ic], cy = positions[ic + 1], cz = positions[ic + 2];
|
|
175
|
+
const e1x = bx - ax, e1y = by - ay, e1z = bz - az;
|
|
176
|
+
const e2x = cx - ax, e2y = cy - ay, e2z = cz - az;
|
|
177
|
+
const cx2 = e1y * e2z - e1z * e2y;
|
|
178
|
+
const cy2 = e1z * e2x - e1x * e2z;
|
|
179
|
+
const cz2 = e1x * e2y - e1y * e2x;
|
|
180
|
+
area_sum += 0.5 * Math.sqrt(cx2 * cx2 + cy2 * cy2 + cz2 * cz2);
|
|
181
|
+
}
|
|
182
|
+
this.__surface_area = area_sum;
|
|
183
|
+
|
|
184
|
+
// Volume: |Σ signed_tet_volume|. The mesh has been compacted by
|
|
185
|
+
// the factory so iteration via a flat range works (no holes).
|
|
186
|
+
let volume_sum = 0;
|
|
187
|
+
const tet_positions = this.tet_positions;
|
|
188
|
+
const tet_mesh = this.tet_mesh;
|
|
189
|
+
const tet_count = tet_mesh.count;
|
|
190
|
+
for (let t = 0; t < tet_count; t++) {
|
|
191
|
+
volume_sum += compute_tetrahedron_volume(tet_mesh, tet_positions, t);
|
|
192
|
+
}
|
|
193
|
+
this.__volume = Math.abs(volume_sum);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Support function: returns the deepest tet-mesh vertex along the
|
|
198
|
+
* supplied direction. This gives GJK the **convex hull** of the
|
|
199
|
+
* mesh — fine for convex authored geometry, an overestimate for
|
|
200
|
+
* concave shapes (a torus's hole or two disconnected components
|
|
201
|
+
* read as one filled blob through this function).
|
|
202
|
+
*
|
|
203
|
+
* Iterates `tet_positions` rather than the original surface
|
|
204
|
+
* `positions` so any hole-closing centroid that the
|
|
205
|
+
* tetrahedralisation added shows up. For airtight closed meshes the
|
|
206
|
+
* two arrays match element-wise.
|
|
207
|
+
*
|
|
208
|
+
* Future work: an accurate non-convex `support` would have to be
|
|
209
|
+
* per-tet (each tet is convex; the shape is the union). The
|
|
210
|
+
* narrowphase would then iterate tets via the BVH. See PLAN.md.
|
|
211
|
+
*
|
|
212
|
+
* @param {number[]|Float32Array} result
|
|
213
|
+
* @param {number} result_offset
|
|
214
|
+
* @param {number} direction_x
|
|
215
|
+
* @param {number} direction_y
|
|
216
|
+
* @param {number} direction_z
|
|
217
|
+
*/
|
|
218
|
+
support(result, result_offset, direction_x, direction_y, direction_z) {
|
|
219
|
+
const positions = this.tet_positions;
|
|
220
|
+
const v_count = positions.length / 3;
|
|
221
|
+
if (v_count === 0) {
|
|
222
|
+
result[result_offset] = 0;
|
|
223
|
+
result[result_offset + 1] = 0;
|
|
224
|
+
result[result_offset + 2] = 0;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
let best_dot = -Infinity;
|
|
228
|
+
let best_i = 0;
|
|
229
|
+
for (let i = 0; i < v_count; i++) {
|
|
230
|
+
const x = positions[i * 3];
|
|
231
|
+
const y = positions[i * 3 + 1];
|
|
232
|
+
const z = positions[i * 3 + 2];
|
|
233
|
+
const d = direction_x * x + direction_y * y + direction_z * z;
|
|
234
|
+
if (d > best_dot) {
|
|
235
|
+
best_dot = d;
|
|
236
|
+
best_i = i;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
result[result_offset] = positions[best_i * 3];
|
|
240
|
+
result[result_offset + 1] = positions[best_i * 3 + 1];
|
|
241
|
+
result[result_offset + 2] = positions[best_i * 3 + 2];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Inside test via the tet mesh. Queries the tet BVH for the leaves
|
|
246
|
+
* whose AABB contains the point, then runs an `orient3d`-based
|
|
247
|
+
* point-in-tet test on each candidate. Returns `true` on the first
|
|
248
|
+
* hit.
|
|
249
|
+
*
|
|
250
|
+
* Crucially this works for **disconnected meshes** — a single
|
|
251
|
+
* `MeshShape3D` holding two cubes welded into one buffer, or a
|
|
252
|
+
* torus with a separate sphere, will correctly classify points
|
|
253
|
+
* inside either component. The previous implementation walked tet
|
|
254
|
+
* neighbours starting from a single seed, which trapped the query
|
|
255
|
+
* in the seed's connected component and produced silent false
|
|
256
|
+
* negatives in the other components.
|
|
257
|
+
*
|
|
258
|
+
* @param {number[]|Float32Array} point
|
|
259
|
+
* @returns {boolean}
|
|
260
|
+
*/
|
|
261
|
+
contains_point(point) {
|
|
262
|
+
const px = point[0], py = point[1], pz = point[2];
|
|
263
|
+
|
|
264
|
+
// Early-out: outside the shape's bbox there can't be any
|
|
265
|
+
// containing tet, and the BVH would still report no overlap —
|
|
266
|
+
// skip the BVH call to save a few ns on the common miss path.
|
|
267
|
+
const bbox = this.__bbox;
|
|
268
|
+
if (px < bbox[0] || py < bbox[1] || pz < bbox[2]
|
|
269
|
+
|| px > bbox[3] || py > bbox[4] || pz > bbox[5]) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
scratch_query_aabb[0] = px; scratch_query_aabb[1] = py; scratch_query_aabb[2] = pz;
|
|
274
|
+
scratch_query_aabb[3] = px; scratch_query_aabb[4] = py; scratch_query_aabb[5] = pz;
|
|
275
|
+
|
|
276
|
+
const n = bvh_query_user_data_overlaps_aabb(
|
|
277
|
+
scratch_candidates, 0, this.tet_bvh, scratch_query_aabb
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const tet_mesh = this.tet_mesh;
|
|
281
|
+
const positions = this.tet_positions;
|
|
282
|
+
for (let i = 0; i < n; i++) {
|
|
283
|
+
const tet_id = scratch_candidates[i];
|
|
284
|
+
const a = tet_mesh.getVertexIndex(tet_id, 0);
|
|
285
|
+
const b = tet_mesh.getVertexIndex(tet_id, 1);
|
|
286
|
+
const c = tet_mesh.getVertexIndex(tet_id, 2);
|
|
287
|
+
const d = tet_mesh.getVertexIndex(tet_id, 3);
|
|
288
|
+
if (tet_contains_point_coords(positions, a, b, c, d, px, py, pz)) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Closest world-space point on the surface to `reference`. Linear
|
|
297
|
+
* scan over triangles using the barycentric closest-point helper.
|
|
298
|
+
* For mesh sizes where this becomes a bottleneck, a triangle BVH
|
|
299
|
+
* would be the next acceleration (sibling to {@link tet_bvh} but
|
|
300
|
+
* keyed on surface triangles).
|
|
301
|
+
*
|
|
302
|
+
* @param {number[]|Float32Array} result
|
|
303
|
+
* @param {number[]|Float32Array} reference
|
|
304
|
+
*/
|
|
305
|
+
nearest_point_on_surface(result, reference) {
|
|
306
|
+
const positions = this.positions;
|
|
307
|
+
const indices = this.indices;
|
|
308
|
+
const tri_count = indices.length / 3;
|
|
309
|
+
const px = reference[0], py = reference[1], pz = reference[2];
|
|
310
|
+
|
|
311
|
+
let best_d2 = Infinity;
|
|
312
|
+
let best_x = 0, best_y = 0, best_z = 0;
|
|
313
|
+
const baryc = scratch_barycentric;
|
|
314
|
+
|
|
315
|
+
for (let i = 0; i < tri_count; i++) {
|
|
316
|
+
const ia = indices[i * 3] * 3;
|
|
317
|
+
const ib = indices[i * 3 + 1] * 3;
|
|
318
|
+
const ic = indices[i * 3 + 2] * 3;
|
|
319
|
+
const ax = positions[ia], ay = positions[ia + 1], az = positions[ia + 2];
|
|
320
|
+
const bx = positions[ib], by = positions[ib + 1], bz = positions[ib + 2];
|
|
321
|
+
const cx = positions[ic], cy = positions[ic + 1], cz = positions[ic + 2];
|
|
322
|
+
|
|
323
|
+
computeTriangleClosestPointToPointBarycentric(
|
|
324
|
+
baryc, 0,
|
|
325
|
+
px, py, pz,
|
|
326
|
+
ax, ay, az,
|
|
327
|
+
bx, by, bz,
|
|
328
|
+
cx, cy, cz
|
|
329
|
+
);
|
|
330
|
+
const u = baryc[0], v = baryc[1];
|
|
331
|
+
const w = 1 - u - v;
|
|
332
|
+
const qx = u * ax + v * bx + w * cx;
|
|
333
|
+
const qy = u * ay + v * by + w * cy;
|
|
334
|
+
const qz = u * az + v * bz + w * cz;
|
|
335
|
+
|
|
336
|
+
const dx = qx - px, dy = qy - py, dz = qz - pz;
|
|
337
|
+
const d2 = dx * dx + dy * dy + dz * dz;
|
|
338
|
+
if (d2 < best_d2) {
|
|
339
|
+
best_d2 = d2;
|
|
340
|
+
best_x = qx; best_y = qy; best_z = qz;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
result[0] = best_x;
|
|
345
|
+
result[1] = best_y;
|
|
346
|
+
result[2] = best_z;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Signed distance to the surface: positive outside the mesh,
|
|
351
|
+
* negative inside. Linear scan over triangles for nearest unsigned
|
|
352
|
+
* distance, then the BVH-backed {@link contains_point} flips the
|
|
353
|
+
* sign for interior points.
|
|
354
|
+
*
|
|
355
|
+
* @param {number[]|Float32Array} point
|
|
356
|
+
* @returns {number}
|
|
357
|
+
*/
|
|
358
|
+
signed_distance_at_point(point) {
|
|
359
|
+
const positions = this.positions;
|
|
360
|
+
const indices = this.indices;
|
|
361
|
+
const tri_count = indices.length / 3;
|
|
362
|
+
const px = point[0], py = point[1], pz = point[2];
|
|
363
|
+
|
|
364
|
+
let best_d2 = Infinity;
|
|
365
|
+
const baryc = scratch_barycentric;
|
|
366
|
+
|
|
367
|
+
for (let i = 0; i < tri_count; i++) {
|
|
368
|
+
const ia = indices[i * 3] * 3;
|
|
369
|
+
const ib = indices[i * 3 + 1] * 3;
|
|
370
|
+
const ic = indices[i * 3 + 2] * 3;
|
|
371
|
+
const ax = positions[ia], ay = positions[ia + 1], az = positions[ia + 2];
|
|
372
|
+
const bx = positions[ib], by = positions[ib + 1], bz = positions[ib + 2];
|
|
373
|
+
const cx = positions[ic], cy = positions[ic + 1], cz = positions[ic + 2];
|
|
374
|
+
|
|
375
|
+
computeTriangleClosestPointToPointBarycentric(
|
|
376
|
+
baryc, 0,
|
|
377
|
+
px, py, pz,
|
|
378
|
+
ax, ay, az,
|
|
379
|
+
bx, by, bz,
|
|
380
|
+
cx, cy, cz
|
|
381
|
+
);
|
|
382
|
+
const u = baryc[0], v = baryc[1];
|
|
383
|
+
const w = 1 - u - v;
|
|
384
|
+
const qx = u * ax + v * bx + w * cx;
|
|
385
|
+
const qy = u * ay + v * by + w * cy;
|
|
386
|
+
const qz = u * az + v * bz + w * cz;
|
|
387
|
+
const dx = qx - px, dy = qy - py, dz = qz - pz;
|
|
388
|
+
const d2 = dx * dx + dy * dy + dz * dz;
|
|
389
|
+
if (d2 < best_d2) best_d2 = d2;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const dist = Math.sqrt(best_d2);
|
|
393
|
+
return this.contains_point(point) ? -dist : dist;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Reject-sample inside the bounding box until a point lands in the
|
|
398
|
+
* mesh volume. For convex or near-convex shapes this terminates in
|
|
399
|
+
* 1–3 attempts; for thin or pinched shapes the worst-case cost can
|
|
400
|
+
* be high. Falls back to the bbox centre after 256 misses.
|
|
401
|
+
*
|
|
402
|
+
* @param {number[]|Float32Array} result
|
|
403
|
+
* @param {number} result_offset
|
|
404
|
+
* @param {function():number} random
|
|
405
|
+
*/
|
|
406
|
+
sample_random_point_in_volume(result, result_offset, random) {
|
|
407
|
+
const b = this.__bbox;
|
|
408
|
+
const lx = b[0], ly = b[1], lz = b[2];
|
|
409
|
+
const sx = b[3] - b[0];
|
|
410
|
+
const sy = b[4] - b[1];
|
|
411
|
+
const sz = b[5] - b[2];
|
|
412
|
+
const probe = scratch_point;
|
|
413
|
+
for (let attempt = 0; attempt < 256; attempt++) {
|
|
414
|
+
probe[0] = lx + random() * sx;
|
|
415
|
+
probe[1] = ly + random() * sy;
|
|
416
|
+
probe[2] = lz + random() * sz;
|
|
417
|
+
if (this.contains_point(probe)) {
|
|
418
|
+
result[result_offset] = probe[0];
|
|
419
|
+
result[result_offset + 1] = probe[1];
|
|
420
|
+
result[result_offset + 2] = probe[2];
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
result[result_offset] = lx + sx * 0.5;
|
|
425
|
+
result[result_offset + 1] = ly + sy * 0.5;
|
|
426
|
+
result[result_offset + 2] = lz + sz * 0.5;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Numerical-gradient SDF gradient.
|
|
431
|
+
*
|
|
432
|
+
* @param {number[]|Float32Array} result
|
|
433
|
+
* @param {number[]|Float32Array} point
|
|
434
|
+
* @returns {number}
|
|
435
|
+
*/
|
|
436
|
+
signed_distance_gradient_at_point(result, point) {
|
|
437
|
+
const h = 1e-4;
|
|
438
|
+
const d_center = this.signed_distance_at_point(point);
|
|
439
|
+
const sample = scratch_point;
|
|
440
|
+
sample[0] = point[0] + h; sample[1] = point[1]; sample[2] = point[2];
|
|
441
|
+
const d_x = this.signed_distance_at_point(sample);
|
|
442
|
+
sample[0] = point[0]; sample[1] = point[1] + h;
|
|
443
|
+
const d_y = this.signed_distance_at_point(sample);
|
|
444
|
+
sample[1] = point[1]; sample[2] = point[2] + h;
|
|
445
|
+
const d_z = this.signed_distance_at_point(sample);
|
|
446
|
+
const inv_h = 1 / h;
|
|
447
|
+
result[0] = (d_x - d_center) * inv_h;
|
|
448
|
+
result[1] = (d_y - d_center) * inv_h;
|
|
449
|
+
result[2] = (d_z - d_center) * inv_h;
|
|
450
|
+
return d_center;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Topology equality: same vertex count, same triangle count, and
|
|
455
|
+
* positions / indices match element-by-element.
|
|
456
|
+
*
|
|
457
|
+
* @param {MeshShape3D} other
|
|
458
|
+
* @returns {boolean}
|
|
459
|
+
*/
|
|
460
|
+
equals(other) {
|
|
461
|
+
if (!super.equals(other)) return false;
|
|
462
|
+
if (this.positions.length !== other.positions.length) return false;
|
|
463
|
+
if (this.indices.length !== other.indices.length) return false;
|
|
464
|
+
for (let i = 0; i < this.positions.length; i++) {
|
|
465
|
+
if (this.positions[i] !== other.positions[i]) return false;
|
|
466
|
+
}
|
|
467
|
+
for (let i = 0; i < this.indices.length; i++) {
|
|
468
|
+
if (this.indices[i] !== other.indices[i]) return false;
|
|
469
|
+
}
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Cheap stable hash: vertex count + triangle count + sampled coords.
|
|
475
|
+
* @returns {number}
|
|
476
|
+
*/
|
|
477
|
+
hash() {
|
|
478
|
+
const v_count = this.positions.length / 3 | 0;
|
|
479
|
+
const t_count = this.indices.length / 3 | 0;
|
|
480
|
+
let h = (v_count * 31 + t_count) | 0;
|
|
481
|
+
if (this.positions.length >= 3) {
|
|
482
|
+
h = (h * 31 + Math.fround(this.positions[0]) * 1e6 | 0) | 0;
|
|
483
|
+
h = (h * 31 + Math.fround(this.positions[this.positions.length - 1]) * 1e6 | 0) | 0;
|
|
484
|
+
}
|
|
485
|
+
return h | 0;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Fast type-check marker.
|
|
491
|
+
* @readonly
|
|
492
|
+
* @type {boolean}
|
|
493
|
+
*/
|
|
494
|
+
MeshShape3D.prototype.isMeshShape3D = true;
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Arbitrary triangle meshes are non-convex in the general case. The current
|
|
498
|
+
* {@link support} implementation returns the convex hull's furthest vertex,
|
|
499
|
+
* which silently overstates collision against meshes with concavities,
|
|
500
|
+
* holes, or disconnected components. The narrowphase routes mesh pairs
|
|
501
|
+
* through per-triangle GJK instead of feeding the whole mesh's support
|
|
502
|
+
* into the Minkowski-difference loop.
|
|
503
|
+
*
|
|
504
|
+
* Authored geometry that happens to be convex is better expressed via a
|
|
505
|
+
* dedicated hull shape — keeping this flag uniformly `false` avoids the
|
|
506
|
+
* per-mesh convexity classification problem.
|
|
507
|
+
*
|
|
508
|
+
* @readonly
|
|
509
|
+
* @type {boolean}
|
|
510
|
+
*/
|
|
511
|
+
MeshShape3D.prototype.is_convex = false;
|
|
512
|
+
|
|
513
|
+
// ── Module-local helpers ────────────────────────────────────────────────────
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Signed-volume-style orient3d test with the test point passed as raw
|
|
517
|
+
* coordinates rather than an index into the positions array. Equivalent
|
|
518
|
+
* to {@link orient3d_fast} but specialised so the caller doesn't have to
|
|
519
|
+
* splice the query point into the positions buffer per call.
|
|
520
|
+
*
|
|
521
|
+
* @param {Float32Array|number[]} positions flat (x, y, z) per vertex
|
|
522
|
+
* @param {number} ai index of plane vertex A
|
|
523
|
+
* @param {number} bi index of plane vertex B
|
|
524
|
+
* @param {number} ci index of plane vertex C
|
|
525
|
+
* @param {number} ex test point x
|
|
526
|
+
* @param {number} ey
|
|
527
|
+
* @param {number} ez
|
|
528
|
+
* @returns {number} positive iff E lies below the plane (CCW from above)
|
|
529
|
+
*/
|
|
530
|
+
function orient3d_with_point(positions, ai, bi, ci, ex, ey, ez) {
|
|
531
|
+
const a3 = ai * 3, b3 = bi * 3, c3 = ci * 3;
|
|
532
|
+
const adx = positions[a3] - ex;
|
|
533
|
+
const ady = positions[a3 + 1] - ey;
|
|
534
|
+
const adz = positions[a3 + 2] - ez;
|
|
535
|
+
const bdx = positions[b3] - ex;
|
|
536
|
+
const bdy = positions[b3 + 1] - ey;
|
|
537
|
+
const bdz = positions[b3 + 2] - ez;
|
|
538
|
+
const cdx = positions[c3] - ex;
|
|
539
|
+
const cdy = positions[c3 + 1] - ey;
|
|
540
|
+
const cdz = positions[c3 + 2] - ez;
|
|
541
|
+
return adx * (bdy * cdz - bdz * cdy)
|
|
542
|
+
+ bdx * (cdy * adz - cdz * ady)
|
|
543
|
+
+ cdx * (ady * bdz - adz * bdy);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Point-in-tet test using four orient3d evaluations on the tet's four
|
|
548
|
+
* faces. Winding-agnostic: rather than assume a specific tet winding
|
|
549
|
+
* (Delaunay's output is CCW with positive signed volume; the existing
|
|
550
|
+
* {@link tetrahedron_contains_point} helper, by contrast, was authored
|
|
551
|
+
* against a CW convention), we accept the point as interior whenever
|
|
552
|
+
* all four face-orient3d evaluations share a sign — i.e. the test
|
|
553
|
+
* point is on the same side of every face. The function thus returns
|
|
554
|
+
* true for both winding conventions; the caller doesn't need to know
|
|
555
|
+
* which one its mesh uses.
|
|
556
|
+
*
|
|
557
|
+
* Takes the test point as raw coordinates so it can be called from a
|
|
558
|
+
* hot loop without scratch-buffer plumbing into `positions`.
|
|
559
|
+
*
|
|
560
|
+
* @param {Float32Array|number[]} positions
|
|
561
|
+
* @param {number} a tet vertex index 0
|
|
562
|
+
* @param {number} b tet vertex index 1
|
|
563
|
+
* @param {number} c tet vertex index 2
|
|
564
|
+
* @param {number} d tet vertex index 3
|
|
565
|
+
* @param {number} ex test point x
|
|
566
|
+
* @param {number} ey
|
|
567
|
+
* @param {number} ez
|
|
568
|
+
* @returns {boolean}
|
|
569
|
+
*/
|
|
570
|
+
function tet_contains_point_coords(positions, a, b, c, d, ex, ey, ez) {
|
|
571
|
+
const v_a = orient3d_with_point(positions, b, d, c, ex, ey, ez);
|
|
572
|
+
const v_b = orient3d_with_point(positions, c, d, a, ex, ey, ez);
|
|
573
|
+
const v_c = orient3d_with_point(positions, d, b, a, ex, ey, ez);
|
|
574
|
+
const v_d = orient3d_with_point(positions, a, b, c, ex, ey, ez);
|
|
575
|
+
|
|
576
|
+
// Boundary points (any value == 0) count as inside — the >= / <=
|
|
577
|
+
// bounds accept them under either winding.
|
|
578
|
+
const all_nonneg = v_a >= 0 && v_b >= 0 && v_c >= 0 && v_d >= 0;
|
|
579
|
+
const all_nonpos = v_a <= 0 && v_b <= 0 && v_c <= 0 && v_d <= 0;
|
|
580
|
+
return all_nonneg || all_nonpos;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const scratch_barycentric = new Float64Array(2);
|
|
584
|
+
const scratch_point = new Float64Array(3);
|
|
585
|
+
const scratch_query_aabb = new Float64Array(6);
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Scratch for tet-id candidates from the BVH overlap query. Sized
|
|
589
|
+
* generously — point-in-AABB queries typically hit 1–10 leaves for
|
|
590
|
+
* non-degenerate meshes. Grows by reallocation if a query overflows.
|
|
591
|
+
* @type {Uint32Array}
|
|
592
|
+
*/
|
|
593
|
+
let scratch_candidates = new Uint32Array(64);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TransformedShape3D.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/shape/TransformedShape3D.js"],"names":[],"mappings":"AAkBA;
|
|
1
|
+
{"version":3,"file":"TransformedShape3D.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/3d/shape/TransformedShape3D.js"],"names":[],"mappings":"AAkBA;IA4CI;;;;;OAKG;IACH,wBAHW,eAAe,MADf,MAAM,EAAE,GAAC,YAAY,OAAK,GAExB,kBAAkB,CAS9B;IAED;;;;;OAKG;IACH,uCAJW,eAAe,eACf,MAAM,EAAE,SACR,MAAM,EAAE,sBAclB;IAED;;;;OAIG;IACH,iCAHW,eAAe,eACf,MAAM,EAAE,sBAIlB;IAED;;;;;OAKG;IACH,2BAHW,eAAe,SACf,MAAM,EAAE,GAAC,YAAY,OAAK,GAFxB,kBAAkB,CAW9B;IAjGG;;;;OAIG;IACH,iBAAoC;IAEpC;;;;OAIG;IACH,yBAA4C;IAE5C;;;;OAIG;IACH,kBAAqB;IAGzB;;;OAGG;IACH,+BAEC;IAuED;;;OAGG;IACH,4CAIC;IAED,yCAEC;IAUD,wCAKC;IAED,mEAyDC;IAED,6CAEC;IAED,oCAKC;IAED,kFAIC;IAED,qGAwCC;IAED;;;;OAIG;IACH,cAHW,kBAAkB,GAChB,OAAO,CAOnB;CAMJ;;8BAIS,OAAO;;gCAtRe,sBAAsB"}
|
|
@@ -50,6 +50,16 @@ export class TransformedShape3D extends AbstractShape3D {
|
|
|
50
50
|
return this.__subject;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* An affine transform preserves convexity, so a TransformedShape3D is
|
|
55
|
+
* convex iff its subject is. A wrapper with no subject is treated as
|
|
56
|
+
* non-convex (there's nothing to support against).
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
get is_convex() {
|
|
60
|
+
return this.__subject !== null && this.__subject.is_convex;
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
/**
|
|
54
64
|
*
|
|
55
65
|
* @param {number[]|Float32Array|mat4} m4
|
|
@@ -215,11 +225,45 @@ export class TransformedShape3D extends AbstractShape3D {
|
|
|
215
225
|
}
|
|
216
226
|
|
|
217
227
|
support(result, result_offset, direction_x, direction_y, direction_z) {
|
|
228
|
+
// Support function under a 4x4 affine transform.
|
|
229
|
+
//
|
|
230
|
+
// For a shape S = { M·x : x ∈ subject }, the support point is
|
|
231
|
+
// arg max_{p ∈ S} dot(p, d) = M · arg max_{x ∈ subject} dot(x, Mᵀ·d).
|
|
232
|
+
// So we must transform the query direction by Mᵀ (the linear-part
|
|
233
|
+
// transpose), normalise it (the subject's `support` contract
|
|
234
|
+
// assumes a unit input), delegate to the subject, then push the
|
|
235
|
+
// result through the full M.
|
|
236
|
+
//
|
|
237
|
+
// For a pure rigid rotation Mᵀ = M⁻¹ (orthonormal), which is the
|
|
238
|
+
// "rotate the direction into body space first" intuition. For
|
|
239
|
+
// uniform scale, Mᵀ·d differs from d only in magnitude and the
|
|
240
|
+
// post-normalisation cancels that out. Non-uniform scale gets
|
|
241
|
+
// the geometrically correct ellipsoid/box support direction
|
|
242
|
+
// automatically (the support of a stretched shape in world
|
|
243
|
+
// direction d is *not* the stretched version of the support in
|
|
244
|
+
// direction d in body space — it's the result of stretching the
|
|
245
|
+
// body-space support in direction Mᵀ·d, which is what this
|
|
246
|
+
// computes).
|
|
247
|
+
//
|
|
248
|
+
// Without this, the previous body of `support` passed the world
|
|
249
|
+
// direction straight to the subject — correct for pure
|
|
250
|
+
// translation and identity rotation, wrong for everything else.
|
|
251
|
+
const m = this.__matrix;
|
|
252
|
+
// (Mᵀ · d)[i] = Σⱼ M[j, i] · d[j]. With column-major storage,
|
|
253
|
+
// M[j, i] = m[j + i·4].
|
|
254
|
+
let ld_x = m[0] * direction_x + m[1] * direction_y + m[2] * direction_z;
|
|
255
|
+
let ld_y = m[4] * direction_x + m[5] * direction_y + m[6] * direction_z;
|
|
256
|
+
let ld_z = m[8] * direction_x + m[9] * direction_y + m[10] * direction_z;
|
|
257
|
+
|
|
258
|
+
const ld_len = Math.sqrt(ld_x * ld_x + ld_y * ld_y + ld_z * ld_z);
|
|
259
|
+
if (ld_len > 0) {
|
|
260
|
+
const inv = 1 / ld_len;
|
|
261
|
+
ld_x *= inv; ld_y *= inv; ld_z *= inv;
|
|
262
|
+
}
|
|
218
263
|
|
|
219
|
-
this.__subject.support(scratch_v3_0, 0,
|
|
264
|
+
this.__subject.support(scratch_v3_0, 0, ld_x, ld_y, ld_z);
|
|
220
265
|
|
|
221
266
|
v3_matrix4_multiply(result, result_offset, scratch_v3_0, 0, this.__matrix);
|
|
222
|
-
|
|
223
267
|
}
|
|
224
268
|
|
|
225
269
|
/**
|