@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.
Files changed (172) hide show
  1. package/package.json +1 -1
  2. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts +3 -3
  3. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.d.ts.map +1 -1
  4. package/src/core/bvh2/bvh3/query/bvh_query_user_data_overlaps_aabb.js +4 -4
  5. package/src/{engine/physics/broadphase/aabb_transform_oriented.d.ts → core/geom/3d/aabb/aabb3_transform_oriented.d.ts} +2 -2
  6. package/src/core/geom/3d/aabb/aabb3_transform_oriented.d.ts.map +1 -0
  7. package/src/{engine/physics/broadphase/aabb_transform_oriented.js → core/geom/3d/aabb/aabb3_transform_oriented.js} +1 -1
  8. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts +54 -0
  9. package/src/core/geom/3d/quaternion/quat3_to_matrix3.d.ts.map +1 -0
  10. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -0
  11. package/src/core/geom/3d/shape/AbstractShape3D.d.ts +24 -2
  12. package/src/core/geom/3d/shape/AbstractShape3D.d.ts.map +1 -1
  13. package/src/core/geom/3d/shape/AbstractShape3D.js +24 -1
  14. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts +148 -0
  15. package/src/core/geom/3d/shape/HeightMapShape3D.d.ts.map +1 -0
  16. package/src/core/geom/3d/shape/HeightMapShape3D.js +451 -0
  17. package/src/core/geom/3d/shape/MeshShape3D.d.ts +210 -0
  18. package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -0
  19. package/src/core/geom/3d/shape/MeshShape3D.js +593 -0
  20. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  21. package/src/core/geom/3d/shape/TransformedShape3D.js +46 -2
  22. package/src/core/geom/3d/shape/Triangle3D.d.ts +95 -0
  23. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -0
  24. package/src/core/geom/3d/shape/Triangle3D.js +318 -0
  25. package/src/core/geom/3d/shape/UnionShape3D.js +13 -0
  26. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts +30 -0
  27. package/src/core/geom/3d/shape/shape_mesh_from_geometry.d.ts.map +1 -0
  28. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +64 -0
  29. package/src/core/geom/3d/tetrahedra/prototype_tetrahedrize_mesh.js +9 -11
  30. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts +28 -0
  31. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.d.ts.map +1 -0
  32. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -0
  33. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.d.ts.map +1 -1
  34. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +40 -18
  35. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts +9 -5
  36. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.d.ts.map +1 -1
  37. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +38 -10
  38. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts +14 -5
  39. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.d.ts.map +1 -1
  40. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +47 -5
  41. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +19 -0
  42. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
  43. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +75 -13
  44. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts +2 -2
  45. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.d.ts.map +1 -1
  46. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +1 -1
  47. package/src/core/geom/vec3/v3_dot_array_array.d.ts +3 -3
  48. package/src/core/geom/vec3/v3_dot_array_array.d.ts.map +1 -1
  49. package/src/core/geom/vec3/v3_dot_array_array.js +2 -2
  50. package/src/core/geom/vec3/v3_negate_array.d.ts +3 -3
  51. package/src/core/geom/vec3/v3_negate_array.d.ts.map +1 -1
  52. package/src/core/geom/vec3/v3_negate_array.js +2 -2
  53. package/src/core/geom/vec3/v3_quat3_apply.d.ts +29 -0
  54. package/src/core/geom/vec3/v3_quat3_apply.d.ts.map +1 -0
  55. package/src/core/geom/vec3/v3_quat3_apply.js +39 -0
  56. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts +30 -0
  57. package/src/core/geom/vec3/v3_quat3_apply_inverse.d.ts.map +1 -0
  58. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +41 -0
  59. package/src/core/geom/vec3/v3_triple_cross_product.d.ts +32 -0
  60. package/src/core/geom/vec3/v3_triple_cross_product.d.ts.map +1 -0
  61. package/src/core/geom/vec3/v3_triple_cross_product.js +45 -0
  62. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +16 -3
  63. package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
  64. package/src/engine/control/first-person/FirstPersonPlayerController.js +211 -211
  65. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +72 -8
  66. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
  67. package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +37 -5
  68. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +101 -3
  69. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
  70. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +1789 -1416
  71. package/src/engine/control/first-person/TODO.md +173 -127
  72. package/src/engine/control/first-person/abilities/Slide.d.ts.map +1 -1
  73. package/src/engine/control/first-person/abilities/Slide.js +9 -1
  74. package/src/engine/control/first-person/prototype_first_person_controller.js +88 -2
  75. package/src/engine/control/first-person/test/buildTestPlayer.d.ts.map +1 -1
  76. package/src/engine/control/first-person/test/buildTestPlayer.js +9 -1
  77. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts +42 -0
  78. package/src/engine/graphics/geometry/CapsuleGeometry.d.ts.map +1 -0
  79. package/src/engine/graphics/geometry/CapsuleGeometry.js +171 -0
  80. package/src/engine/physics/BULLET_REVIEW.md +945 -0
  81. package/src/engine/physics/CANNON_REVIEW.md +1300 -0
  82. package/src/engine/physics/JOLT_REVIEW.md +913 -0
  83. package/src/engine/physics/PLAN.md +461 -236
  84. package/src/engine/physics/RAPIER_REVIEW.md +934 -0
  85. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -0
  86. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +2 -2
  87. package/src/engine/physics/contact/ManifoldStore.d.ts +83 -10
  88. package/src/engine/physics/contact/ManifoldStore.d.ts.map +1 -1
  89. package/src/engine/physics/contact/ManifoldStore.js +608 -499
  90. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts +2 -2
  91. package/src/engine/physics/ecs/ColliderObserverSystem.d.ts.map +1 -1
  92. package/src/engine/physics/ecs/PhysicsSystem.d.ts +128 -20
  93. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  94. package/src/engine/physics/ecs/PhysicsSystem.js +1301 -1159
  95. package/src/engine/physics/fluid/FluidSimulator.d.ts.map +1 -1
  96. package/src/engine/physics/fluid/FluidSimulator.js +2 -1
  97. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts +28 -6
  98. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.d.ts.map +1 -1
  99. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +39 -17
  100. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts +6 -6
  101. package/src/engine/physics/gjk/expanding_polytope_algorithm.d.ts.map +1 -1
  102. package/src/engine/physics/gjk/expanding_polytope_algorithm.js +68 -22
  103. package/src/engine/physics/gjk/gjk.d.ts +28 -2
  104. package/src/engine/physics/gjk/gjk.d.ts.map +1 -1
  105. package/src/engine/physics/gjk/gjk.js +421 -378
  106. package/src/engine/physics/gjk/minkowski_support.d.ts +37 -0
  107. package/src/engine/physics/gjk/minkowski_support.d.ts.map +1 -0
  108. package/src/engine/physics/gjk/minkowski_support.js +75 -0
  109. package/src/engine/physics/gjk/mpr.d.ts +56 -0
  110. package/src/engine/physics/gjk/mpr.d.ts.map +1 -0
  111. package/src/engine/physics/gjk/mpr.js +344 -0
  112. package/src/engine/physics/inertia/world_inverse_inertia.d.ts +20 -5
  113. package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
  114. package/src/engine/physics/inertia/world_inverse_inertia.js +36 -38
  115. package/src/engine/physics/integration/integrate_position.d.ts +25 -7
  116. package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
  117. package/src/engine/physics/integration/integrate_position.js +43 -12
  118. package/src/engine/physics/integration/integrate_velocity.d.ts +30 -0
  119. package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
  120. package/src/engine/physics/integration/integrate_velocity.js +82 -1
  121. package/src/engine/physics/narrowphase/PosedShape.d.ts +0 -8
  122. package/src/engine/physics/narrowphase/PosedShape.d.ts.map +1 -1
  123. package/src/engine/physics/narrowphase/PosedShape.js +28 -30
  124. package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
  125. package/src/engine/physics/narrowphase/box_box_manifold.js +113 -17
  126. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts +30 -0
  127. package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -0
  128. package/src/engine/physics/narrowphase/box_triangle_contact.js +811 -0
  129. package/src/engine/physics/narrowphase/capsule_contacts.d.ts.map +1 -1
  130. package/src/engine/physics/narrowphase/capsule_contacts.js +10 -56
  131. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts +71 -0
  132. package/src/engine/physics/narrowphase/capsule_triangle_contact.d.ts.map +1 -0
  133. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +375 -0
  134. package/src/engine/physics/narrowphase/compute_penetration.d.ts +91 -0
  135. package/src/engine/physics/narrowphase/compute_penetration.d.ts.map +1 -0
  136. package/src/engine/physics/narrowphase/compute_penetration.js +396 -0
  137. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts +35 -0
  138. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.d.ts.map +1 -0
  139. package/src/engine/physics/narrowphase/decomposition/aabb_world_to_local.js +80 -0
  140. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts +31 -0
  141. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.d.ts.map +1 -0
  142. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -0
  143. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts +42 -0
  144. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.d.ts.map +1 -0
  145. package/src/engine/physics/narrowphase/decomposition/heightmap_enumerate_triangles.js +204 -0
  146. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts +42 -0
  147. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.d.ts.map +1 -0
  148. package/src/engine/physics/narrowphase/decomposition/mesh_enumerate_triangles.js +94 -0
  149. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts +37 -0
  150. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.d.ts.map +1 -0
  151. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -0
  152. package/src/engine/physics/narrowphase/narrowphase_step.d.ts +8 -2
  153. package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
  154. package/src/engine/physics/narrowphase/narrowphase_step.js +1422 -382
  155. package/src/engine/physics/narrowphase/sphere_box_contact.d.ts.map +1 -1
  156. package/src/engine/physics/narrowphase/sphere_box_contact.js +16 -23
  157. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts +48 -0
  158. package/src/engine/physics/narrowphase/sphere_triangle_contact.d.ts.map +1 -0
  159. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +143 -0
  160. package/src/engine/physics/queries/overlap_shape.d.ts +51 -0
  161. package/src/engine/physics/queries/overlap_shape.d.ts.map +1 -0
  162. package/src/engine/physics/queries/overlap_shape.js +183 -0
  163. package/src/engine/physics/queries/shape_cast.d.ts +56 -0
  164. package/src/engine/physics/queries/shape_cast.d.ts.map +1 -0
  165. package/src/engine/physics/queries/shape_cast.js +387 -0
  166. package/src/engine/physics/solver/solve_contacts.d.ts +116 -30
  167. package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
  168. package/src/engine/physics/solver/solve_contacts.js +641 -223
  169. package/src/engine/physics/broadphase/aabb_transform_oriented.d.ts.map +0 -1
  170. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts +0 -20
  171. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_unmasked_legacy.d.ts.map +0 -1
  172. 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;IAkCI;;;;;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;IAvFG;;;;OAIG;IACH,iBAAoC;IAEpC;;;;OAIG;IACH,yBAA4C;IAE5C;;;;OAIG;IACH,kBAAqB;IAGzB;;;OAGG;IACH,+BAEC;IA6DD;;;OAGG;IACH,4CAIC;IAED,yCAEC;IAUD,wCAKC;IAED,mEAyDC;IAED,6CAEC;IAED,oCAKC;IAED,kFAIC;IAED,qGAMC;IAED;;;;OAIG;IACH,cAHW,kBAAkB,GAChB,OAAO,CAOnB;CAMJ;;8BAIS,OAAO;;gCA1Oe,sBAAsB"}
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, direction_x, direction_y, direction_z);
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
  /**