@woosh/meep-engine 2.141.0 → 2.143.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.d.ts +1 -1
- package/src/core/geom/3d/shape/CapsuleShape3D.js +1 -1
- package/src/core/geom/3d/shape/SphereShape3D.d.ts +47 -0
- package/src/core/geom/3d/shape/SphereShape3D.d.ts.map +1 -0
- package/src/core/geom/3d/shape/SphereShape3D.js +127 -0
- package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts +30 -18
- package/src/core/geom/3d/shape/UnitSphereShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -92
- package/src/core/geom/3d/shape/json/shape_to_type.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/shape_to_type.js +4 -2
- package/src/core/geom/3d/shape/json/type_adapters.d.ts +12 -3
- package/src/core/geom/3d/shape/json/type_adapters.d.ts.map +1 -1
- package/src/core/geom/3d/shape/json/type_adapters.js +16 -4
- package/src/core/geom/3d/shape/util/shape_to_visual_entity.js +2 -2
- package/src/engine/control/first-person/DESIGN_COLLISION.md +255 -0
- package/src/engine/control/first-person/prototype_first_person_controller.js +5 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.d.ts.map +1 -1
- package/src/engine/graphics/render/buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js +70 -43
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.d.ts +12 -22
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.d.ts.map +1 -1
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +345 -186
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.d.ts +44 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.d.ts.map +1 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/SAOUpscaleShader.js +151 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/generateHilbertNoiseTexture.d.ts +14 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/generateHilbertNoiseTexture.d.ts.map +1 -0
- package/src/engine/graphics/render/buffer/simple-fx/ao/generateHilbertNoiseTexture.js +78 -0
- package/src/engine/physics/PLAN.md +705 -578
- package/src/engine/physics/REVIEW_003.md +166 -0
- package/src/engine/physics/constraint/solve_constraints.d.ts +24 -2
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
- package/src/engine/physics/constraint/solve_constraints.js +402 -165
- package/src/engine/physics/ecs/Joint.d.ts +115 -0
- package/src/engine/physics/ecs/Joint.d.ts.map +1 -1
- package/src/engine/physics/ecs/Joint.js +168 -0
- package/src/engine/physics/ecs/JointSerializationAdapter.d.ts +29 -0
- package/src/engine/physics/ecs/JointSerializationAdapter.d.ts.map +1 -0
- package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +20 -13
- package/src/engine/physics/narrowphase/ray_shapes.d.ts +66 -0
- package/src/engine/physics/narrowphase/ray_shapes.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/ray_shapes.js +187 -0
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts +16 -0
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/refine_ray_concave.js +145 -0
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +39 -0
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/refine_ray_hit.js +78 -0
- package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts +8 -7
- package/src/engine/physics/narrowphase/sphere_sphere_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/sphere_sphere_contact.js +8 -7
- package/src/engine/physics/queries/raycast.d.ts +11 -9
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +108 -159
- package/src/engine/physics/vehicle/RaycastVehicle.d.ts +114 -0
- package/src/engine/physics/vehicle/RaycastVehicle.d.ts.map +1 -0
- package/src/engine/physics/vehicle/RaycastVehicle.js +333 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* # Local-frame ray ↔ primitive intersections (raycast narrowphase)
|
|
3
|
+
*
|
|
4
|
+
* Each function intersects a ray, **expressed in the shape's local frame**,
|
|
5
|
+
* against a canonical primitive at the origin (sphere at the origin; box
|
|
6
|
+
* axis-aligned spanning `[-h, +h]`; capsule along the local Y axis). They
|
|
7
|
+
* return the hit distance `t` along the ray (`Infinity` on a miss) and write
|
|
8
|
+
* the **local** outward surface normal (unit) into `outNormal[0..2]`.
|
|
9
|
+
*
|
|
10
|
+
* The ray-narrowphase dispatch transforms the world ray into the body's local
|
|
11
|
+
* frame once (rotate by the inverse body rotation — a unit direction stays
|
|
12
|
+
* unit, so `t` is preserved), calls the matching primitive, then rotates the
|
|
13
|
+
* returned local normal back to world. Keeping the primitives canonical lets
|
|
14
|
+
* the box and capsule tests be axis-aligned (cheap slab / cylinder math) and
|
|
15
|
+
* shares a single transform across them.
|
|
16
|
+
*
|
|
17
|
+
* Conventions:
|
|
18
|
+
* - the ray direction is unit length (the caller guarantees it);
|
|
19
|
+
* - the first surface crossing at or after the origin within `tMax` is
|
|
20
|
+
* returned; a ray starting inside the shape returns its exit crossing;
|
|
21
|
+
* - the normal is the geometric outward surface normal at the hit.
|
|
22
|
+
*
|
|
23
|
+
* @author Alex Goldring
|
|
24
|
+
* @copyright Company Named Limited (c) 2026
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Ray vs a sphere of radius `r` centred at the local origin.
|
|
29
|
+
*
|
|
30
|
+
* @param {Float64Array} outNormal length-3, written on hit
|
|
31
|
+
* @param {number} ox @param {number} oy @param {number} oz ray origin (local)
|
|
32
|
+
* @param {number} dx @param {number} dy @param {number} dz ray dir (local, unit)
|
|
33
|
+
* @param {number} tMax
|
|
34
|
+
* @param {number} r sphere radius
|
|
35
|
+
* @returns {number} hit distance, or `Infinity` on miss
|
|
36
|
+
*/
|
|
37
|
+
export function ray_sphere_local(outNormal, ox, oy, oz, dx, dy, dz, tMax, r) {
|
|
38
|
+
// |o + t·d|² = r², d unit → t² + 2(o·d)t + (|o|² − r²) = 0.
|
|
39
|
+
const b = ox * dx + oy * dy + oz * dz;
|
|
40
|
+
const c = ox * ox + oy * oy + oz * oz - r * r;
|
|
41
|
+
const disc = b * b - c;
|
|
42
|
+
if (disc < 0) return Infinity;
|
|
43
|
+
const sq = Math.sqrt(disc);
|
|
44
|
+
let t = -b - sq; // near root (entry)
|
|
45
|
+
if (t < 0) t = -b + sq; // origin inside the sphere → far root (exit)
|
|
46
|
+
if (t < 0 || t > tMax) return Infinity;
|
|
47
|
+
const inv = 1 / r;
|
|
48
|
+
outNormal[0] = (ox + dx * t) * inv;
|
|
49
|
+
outNormal[1] = (oy + dy * t) * inv;
|
|
50
|
+
outNormal[2] = (oz + dz * t) * inv;
|
|
51
|
+
return t;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Ray vs an axis-aligned box spanning `[-hx,hx] × [-hy,hy] × [-hz,hz]` at the
|
|
56
|
+
* local origin (the canonical pose of {@link BoxShape3D}). Slab method, with
|
|
57
|
+
* the entry (or, origin-inside, exit) face's outward normal.
|
|
58
|
+
*
|
|
59
|
+
* @param {Float64Array} outNormal length-3, written on hit
|
|
60
|
+
* @param {number} ox @param {number} oy @param {number} oz ray origin (local)
|
|
61
|
+
* @param {number} dx @param {number} dy @param {number} dz ray dir (local, unit)
|
|
62
|
+
* @param {number} tMax
|
|
63
|
+
* @param {number} hx @param {number} hy @param {number} hz half-extents
|
|
64
|
+
* @returns {number} hit distance, or `Infinity` on miss
|
|
65
|
+
*/
|
|
66
|
+
export function ray_box_local(outNormal, ox, oy, oz, dx, dy, dz, tMax, hx, hy, hz) {
|
|
67
|
+
let tmin = -Infinity, tmax = Infinity;
|
|
68
|
+
let enterAxis = 0, enterSign = 0, exitAxis = 0, exitSign = 0;
|
|
69
|
+
|
|
70
|
+
// X slab.
|
|
71
|
+
if (dx !== 0) {
|
|
72
|
+
const inv = 1 / dx;
|
|
73
|
+
let tn = (-hx - ox) * inv, tf = (hx - ox) * inv, nsign = -1;
|
|
74
|
+
if (tn > tf) { const tmp = tn; tn = tf; tf = tmp; nsign = 1; }
|
|
75
|
+
if (tn > tmin) { tmin = tn; enterAxis = 0; enterSign = nsign; }
|
|
76
|
+
if (tf < tmax) { tmax = tf; exitAxis = 0; exitSign = -nsign; }
|
|
77
|
+
} else if (ox < -hx || ox > hx) {
|
|
78
|
+
return Infinity;
|
|
79
|
+
}
|
|
80
|
+
// Y slab.
|
|
81
|
+
if (dy !== 0) {
|
|
82
|
+
const inv = 1 / dy;
|
|
83
|
+
let tn = (-hy - oy) * inv, tf = (hy - oy) * inv, nsign = -1;
|
|
84
|
+
if (tn > tf) { const tmp = tn; tn = tf; tf = tmp; nsign = 1; }
|
|
85
|
+
if (tn > tmin) { tmin = tn; enterAxis = 1; enterSign = nsign; }
|
|
86
|
+
if (tf < tmax) { tmax = tf; exitAxis = 1; exitSign = -nsign; }
|
|
87
|
+
} else if (oy < -hy || oy > hy) {
|
|
88
|
+
return Infinity;
|
|
89
|
+
}
|
|
90
|
+
// Z slab.
|
|
91
|
+
if (dz !== 0) {
|
|
92
|
+
const inv = 1 / dz;
|
|
93
|
+
let tn = (-hz - oz) * inv, tf = (hz - oz) * inv, nsign = -1;
|
|
94
|
+
if (tn > tf) { const tmp = tn; tn = tf; tf = tmp; nsign = 1; }
|
|
95
|
+
if (tn > tmin) { tmin = tn; enterAxis = 2; enterSign = nsign; }
|
|
96
|
+
if (tf < tmax) { tmax = tf; exitAxis = 2; exitSign = -nsign; }
|
|
97
|
+
} else if (oz < -hz || oz > hz) {
|
|
98
|
+
return Infinity;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (tmax < tmin || tmax < 0) return Infinity;
|
|
102
|
+
|
|
103
|
+
let t, axis, sign;
|
|
104
|
+
if (tmin >= 0) { t = tmin; axis = enterAxis; sign = enterSign; }
|
|
105
|
+
else { t = tmax; axis = exitAxis; sign = exitSign; } // origin inside the box
|
|
106
|
+
|
|
107
|
+
if (t > tMax) return Infinity;
|
|
108
|
+
outNormal[0] = 0; outNormal[1] = 0; outNormal[2] = 0;
|
|
109
|
+
outNormal[axis] = sign;
|
|
110
|
+
return t;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Ray vs a capsule along the local Y axis: a cylinder of radius `r` over
|
|
115
|
+
* `y ∈ [−hh, hh]` capped by hemispheres of radius `r` at `(0, ±hh, 0)` — the
|
|
116
|
+
* canonical pose of {@link CapsuleShape3D} (`hh = height/2`). Tests the
|
|
117
|
+
* infinite cylinder (clamped to the segment) and the two cap spheres, taking
|
|
118
|
+
* the nearest valid crossing.
|
|
119
|
+
*
|
|
120
|
+
* @param {Float64Array} outNormal length-3, written on hit
|
|
121
|
+
* @param {number} ox @param {number} oy @param {number} oz ray origin (local)
|
|
122
|
+
* @param {number} dx @param {number} dy @param {number} dz ray dir (local, unit)
|
|
123
|
+
* @param {number} tMax
|
|
124
|
+
* @param {number} r capsule radius
|
|
125
|
+
* @param {number} hh half-height of the cylindrical section (`height/2`)
|
|
126
|
+
* @returns {number} hit distance, or `Infinity` on miss
|
|
127
|
+
*/
|
|
128
|
+
export function ray_capsule_local(outNormal, ox, oy, oz, dx, dy, dz, tMax, r, hh) {
|
|
129
|
+
let best = Infinity;
|
|
130
|
+
let nx = 0, ny = 0, nz = 0;
|
|
131
|
+
const r2 = r * r;
|
|
132
|
+
const inv_r = 1 / r;
|
|
133
|
+
|
|
134
|
+
// --- Cylinder side (infinite cylinder about Y, projected to XZ). ---
|
|
135
|
+
const a = dx * dx + dz * dz;
|
|
136
|
+
if (a > 1e-12) {
|
|
137
|
+
const b = ox * dx + oz * dz;
|
|
138
|
+
const c = ox * ox + oz * oz - r2;
|
|
139
|
+
const disc = b * b - a * c;
|
|
140
|
+
if (disc >= 0) {
|
|
141
|
+
const sq = Math.sqrt(disc);
|
|
142
|
+
const inv_a = 1 / a;
|
|
143
|
+
// Both roots; the smaller non-negative one whose hit lies on the
|
|
144
|
+
// cylindrical section is the side contact.
|
|
145
|
+
const roots = (-b - sq) * inv_a;
|
|
146
|
+
const rootf = (-b + sq) * inv_a;
|
|
147
|
+
for (let i = 0; i < 2; i++) {
|
|
148
|
+
const t = i === 0 ? roots : rootf;
|
|
149
|
+
if (t < 0 || t >= best || t > tMax) continue;
|
|
150
|
+
const y = oy + dy * t;
|
|
151
|
+
if (y < -hh || y > hh) continue; // off the cylinder, onto a cap
|
|
152
|
+
best = t;
|
|
153
|
+
const px = ox + dx * t, pz = oz + dz * t;
|
|
154
|
+
nx = px * inv_r; ny = 0; nz = pz * inv_r;
|
|
155
|
+
break; // roots is the nearer; if it qualified we're done
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// --- Cap spheres at (0, ±hh, 0), each valid only on its outer hemisphere. ---
|
|
161
|
+
for (let cap = 0; cap < 2; cap++) {
|
|
162
|
+
const cy = cap === 0 ? hh : -hh;
|
|
163
|
+
const oyc = oy - cy;
|
|
164
|
+
const bb = ox * dx + oyc * dy + oz * dz;
|
|
165
|
+
const cc = ox * ox + oyc * oyc + oz * oz - r2;
|
|
166
|
+
const disc = bb * bb - cc;
|
|
167
|
+
if (disc < 0) continue;
|
|
168
|
+
const sq = Math.sqrt(disc);
|
|
169
|
+
const tn = -bb - sq, tf = -bb + sq;
|
|
170
|
+
for (let i = 0; i < 2; i++) {
|
|
171
|
+
const t = i === 0 ? tn : tf;
|
|
172
|
+
if (t < 0 || t >= best || t > tMax) continue;
|
|
173
|
+
const hy = oy + dy * t;
|
|
174
|
+
// Only the hemisphere beyond the segment end belongs to this cap.
|
|
175
|
+
if (cap === 0 ? hy < hh : hy > -hh) continue;
|
|
176
|
+
best = t;
|
|
177
|
+
nx = (ox + dx * t) * inv_r;
|
|
178
|
+
ny = (hy - cy) * inv_r;
|
|
179
|
+
nz = (oz + dz * t) * inv_r;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (best === Infinity) return Infinity;
|
|
185
|
+
outNormal[0] = nx; outNormal[1] = ny; outNormal[2] = nz;
|
|
186
|
+
return best;
|
|
187
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {AbstractShape3D} shape a concave shape (`is_convex === false`)
|
|
3
|
+
* @param {{x:number,y:number,z:number}} position
|
|
4
|
+
* @param {ArrayLike<number>} rotation body rotation quaternion (x,y,z,w)
|
|
5
|
+
* @param {number} ox @param {number} oy @param {number} oz ray origin (world)
|
|
6
|
+
* @param {number} dx @param {number} dy @param {number} dz ray dir (world, unit)
|
|
7
|
+
* @param {number} tMax
|
|
8
|
+
* @param {Float64Array|Vector3} outNormal world surface normal, written on hit
|
|
9
|
+
* @returns {number} hit distance, or `Infinity` on miss
|
|
10
|
+
*/
|
|
11
|
+
export function refine_ray_concave(shape: AbstractShape3D, position: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
z: number;
|
|
15
|
+
}, rotation: ArrayLike<number>, ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, tMax: number, outNormal: Float64Array | Vector3): number;
|
|
16
|
+
//# sourceMappingURL=refine_ray_concave.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refine_ray_concave.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/refine_ray_concave.js"],"names":[],"mappings":"AAsCA;;;;;;;;;GASG;AACH,qEARW;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,YAC5B,UAAU,MAAM,CAAC,MACjB,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,QAC5C,MAAM,aACN,YAAY,UAAQ,GAClB,MAAM,CAkGlB"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
|
|
2
|
+
import { TRIANGLE_FLOAT_STRIDE } from "./decomposition/triangle_buffer_layout.js";
|
|
3
|
+
import { mesh_enumerate_triangles } from "./decomposition/mesh_enumerate_triangles.js";
|
|
4
|
+
import { heightmap_enumerate_triangles } from "./decomposition/heightmap_enumerate_triangles.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* # Ray vs a concave shape (mesh / heightmap)
|
|
8
|
+
*
|
|
9
|
+
* Refines a ray hit against a non-convex collider by triangle decomposition,
|
|
10
|
+
* mirroring the contact narrowphase's concave path. Working in the shape's
|
|
11
|
+
* local frame:
|
|
12
|
+
* 1. clip the ray to the shape's local bounding box (a quick reject, and a
|
|
13
|
+
* finite query segment even when `tMax` is unbounded);
|
|
14
|
+
* 2. enumerate the triangles overlapping that segment's AABB
|
|
15
|
+
* (`mesh_enumerate_triangles` / `heightmap_enumerate_triangles`);
|
|
16
|
+
* 3. two-sided Möller–Trumbore each, keeping the nearest crossing;
|
|
17
|
+
* 4. return its distance and the world surface normal (the triangle's
|
|
18
|
+
* geometric normal, oriented to face the ray).
|
|
19
|
+
*
|
|
20
|
+
* The triangle scratch grows on saturation and is bounded by {@link MAX_TRIS}
|
|
21
|
+
* (a ray crossing more triangles than that in one mesh is not a realistic
|
|
22
|
+
* raycast — it would mean skewering a 65k-triangle mesh lengthwise).
|
|
23
|
+
*
|
|
24
|
+
* @author Alex Goldring
|
|
25
|
+
* @copyright Company Named Limited (c) 2026
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const lo = new Float64Array(3); // ray origin, shape-local
|
|
29
|
+
const ld = new Float64Array(3); // ray direction, shape-local
|
|
30
|
+
const bounds = new Float64Array(6); // shape local AABB
|
|
31
|
+
|
|
32
|
+
const INITIAL_TRIS = 1024;
|
|
33
|
+
const MAX_TRIS = 1 << 16;
|
|
34
|
+
let tri_buf = new Float64Array(INITIAL_TRIS * TRIANGLE_FLOAT_STRIDE);
|
|
35
|
+
|
|
36
|
+
const EPS = 1e-12;
|
|
37
|
+
const AABB_FATTEN = 1e-5; // guards triangles whose AABB just abuts the segment
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {AbstractShape3D} shape a concave shape (`is_convex === false`)
|
|
41
|
+
* @param {{x:number,y:number,z:number}} position
|
|
42
|
+
* @param {ArrayLike<number>} rotation body rotation quaternion (x,y,z,w)
|
|
43
|
+
* @param {number} ox @param {number} oy @param {number} oz ray origin (world)
|
|
44
|
+
* @param {number} dx @param {number} dy @param {number} dz ray dir (world, unit)
|
|
45
|
+
* @param {number} tMax
|
|
46
|
+
* @param {Float64Array|Vector3} outNormal world surface normal, written on hit
|
|
47
|
+
* @returns {number} hit distance, or `Infinity` on miss
|
|
48
|
+
*/
|
|
49
|
+
export function refine_ray_concave(shape, position, rotation, ox, oy, oz, dx, dy, dz, tMax, outNormal) {
|
|
50
|
+
const qx = rotation[0], qy = rotation[1], qz = rotation[2], qw = rotation[3];
|
|
51
|
+
// World → local (inverse body rotation; unit dir stays unit → t preserved).
|
|
52
|
+
v3_quat3_apply(lo, 0, ox - position.x, oy - position.y, oz - position.z, -qx, -qy, -qz, qw);
|
|
53
|
+
v3_quat3_apply(ld, 0, dx, dy, dz, -qx, -qy, -qz, qw);
|
|
54
|
+
const lox = lo[0], loy = lo[1], loz = lo[2];
|
|
55
|
+
const ldx = ld[0], ldy = ld[1], ldz = ld[2];
|
|
56
|
+
|
|
57
|
+
// Clip the local ray to the shape's local AABB → segment [tEnter, tExit].
|
|
58
|
+
shape.compute_bounding_box(bounds);
|
|
59
|
+
let tEnter = 0, tExit = tMax;
|
|
60
|
+
if (ldx > EPS || ldx < -EPS) {
|
|
61
|
+
const inv = 1 / ldx;
|
|
62
|
+
let t1 = (bounds[0] - lox) * inv, t2 = (bounds[3] - lox) * inv;
|
|
63
|
+
if (t1 > t2) { const s = t1; t1 = t2; t2 = s; }
|
|
64
|
+
if (t1 > tEnter) tEnter = t1;
|
|
65
|
+
if (t2 < tExit) tExit = t2;
|
|
66
|
+
} else if (lox < bounds[0] || lox > bounds[3]) return Infinity;
|
|
67
|
+
if (ldy > EPS || ldy < -EPS) {
|
|
68
|
+
const inv = 1 / ldy;
|
|
69
|
+
let t1 = (bounds[1] - loy) * inv, t2 = (bounds[4] - loy) * inv;
|
|
70
|
+
if (t1 > t2) { const s = t1; t1 = t2; t2 = s; }
|
|
71
|
+
if (t1 > tEnter) tEnter = t1;
|
|
72
|
+
if (t2 < tExit) tExit = t2;
|
|
73
|
+
} else if (loy < bounds[1] || loy > bounds[4]) return Infinity;
|
|
74
|
+
if (ldz > EPS || ldz < -EPS) {
|
|
75
|
+
const inv = 1 / ldz;
|
|
76
|
+
let t1 = (bounds[2] - loz) * inv, t2 = (bounds[5] - loz) * inv;
|
|
77
|
+
if (t1 > t2) { const s = t1; t1 = t2; t2 = s; }
|
|
78
|
+
if (t1 > tEnter) tEnter = t1;
|
|
79
|
+
if (t2 < tExit) tExit = t2;
|
|
80
|
+
} else if (loz < bounds[2] || loz > bounds[5]) return Infinity;
|
|
81
|
+
if (tExit < tEnter) return Infinity; // ray misses the shape's AABB entirely
|
|
82
|
+
|
|
83
|
+
// Query AABB = bbox of the clipped local segment.
|
|
84
|
+
const sax = lox + ldx * tEnter, say = loy + ldy * tEnter, saz = loz + ldz * tEnter;
|
|
85
|
+
const sbx = lox + ldx * tExit, sby = loy + ldy * tExit, sbz = loz + ldz * tExit;
|
|
86
|
+
const qminx = (sax < sbx ? sax : sbx) - AABB_FATTEN;
|
|
87
|
+
const qminy = (say < sby ? say : sby) - AABB_FATTEN;
|
|
88
|
+
const qminz = (saz < sbz ? saz : sbz) - AABB_FATTEN;
|
|
89
|
+
const qmaxx = (sax > sbx ? sax : sbx) + AABB_FATTEN;
|
|
90
|
+
const qmaxy = (say > sby ? say : sby) + AABB_FATTEN;
|
|
91
|
+
const qmaxz = (saz > sbz ? saz : sbz) + AABB_FATTEN;
|
|
92
|
+
|
|
93
|
+
const enumerate = shape.isHeightMapShape3D === true ? heightmap_enumerate_triangles : mesh_enumerate_triangles;
|
|
94
|
+
let cap = (tri_buf.length / TRIANGLE_FLOAT_STRIDE) | 0;
|
|
95
|
+
let count = enumerate(tri_buf, 0, shape, qminx, qminy, qminz, qmaxx, qmaxy, qmaxz);
|
|
96
|
+
while (count === cap && cap < MAX_TRIS) {
|
|
97
|
+
cap = cap * 2 < MAX_TRIS ? cap * 2 : MAX_TRIS;
|
|
98
|
+
tri_buf = new Float64Array(cap * TRIANGLE_FLOAT_STRIDE);
|
|
99
|
+
count = enumerate(tri_buf, 0, shape, qminx, qminy, qminz, qmaxx, qmaxy, qmaxz);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Two-sided Möller–Trumbore over the candidates; keep the nearest.
|
|
103
|
+
let best = tMax;
|
|
104
|
+
let found = false;
|
|
105
|
+
let nx = 0, ny = 0, nz = 0;
|
|
106
|
+
for (let i = 0; i < count; i++) {
|
|
107
|
+
const o = i * TRIANGLE_FLOAT_STRIDE;
|
|
108
|
+
const ax = tri_buf[o], ay = tri_buf[o + 1], az = tri_buf[o + 2];
|
|
109
|
+
const e1x = tri_buf[o + 3] - ax, e1y = tri_buf[o + 4] - ay, e1z = tri_buf[o + 5] - az;
|
|
110
|
+
const e2x = tri_buf[o + 6] - ax, e2y = tri_buf[o + 7] - ay, e2z = tri_buf[o + 8] - az;
|
|
111
|
+
|
|
112
|
+
const px = ldy * e2z - ldz * e2y, py = ldz * e2x - ldx * e2z, pz = ldx * e2y - ldy * e2x;
|
|
113
|
+
const det = e1x * px + e1y * py + e1z * pz;
|
|
114
|
+
if (det < EPS && det > -EPS) continue; // ray parallel to the triangle
|
|
115
|
+
const invDet = 1 / det;
|
|
116
|
+
|
|
117
|
+
const tvx = lox - ax, tvy = loy - ay, tvz = loz - az;
|
|
118
|
+
const u = (tvx * px + tvy * py + tvz * pz) * invDet;
|
|
119
|
+
if (u < 0 || u > 1) continue;
|
|
120
|
+
|
|
121
|
+
const wx = tvy * e1z - tvz * e1y, wy = tvz * e1x - tvx * e1z, wz = tvx * e1y - tvy * e1x;
|
|
122
|
+
const v = (ldx * wx + ldy * wy + ldz * wz) * invDet;
|
|
123
|
+
if (v < 0 || u + v > 1) continue;
|
|
124
|
+
|
|
125
|
+
const t = (e2x * wx + e2y * wy + e2z * wz) * invDet;
|
|
126
|
+
if (t < 0 || t >= best) continue;
|
|
127
|
+
|
|
128
|
+
best = t;
|
|
129
|
+
found = true;
|
|
130
|
+
// Geometric normal e1 × e2 (local, unnormalised).
|
|
131
|
+
nx = e1y * e2z - e1z * e2y;
|
|
132
|
+
ny = e1z * e2x - e1x * e2z;
|
|
133
|
+
nz = e1x * e2y - e1y * e2x;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!found) return Infinity;
|
|
137
|
+
|
|
138
|
+
// Normalise, orient to face the ray, then rotate the local normal to world.
|
|
139
|
+
let nl = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
140
|
+
if (nl === 0) nl = 1;
|
|
141
|
+
nx /= nl; ny /= nl; nz /= nl;
|
|
142
|
+
if (nx * ldx + ny * ldy + nz * ldz > 0) { nx = -nx; ny = -ny; nz = -nz; }
|
|
143
|
+
v3_quat3_apply(outNormal, 0, nx, ny, nz, qx, qy, qz, qw);
|
|
144
|
+
return best;
|
|
145
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refine a broadphase ray hit against a body's true shape geometry.
|
|
3
|
+
*
|
|
4
|
+
* Transforms the world ray into the shape's local frame once (rotate by the
|
|
5
|
+
* inverse body rotation — a unit direction stays unit, so the hit distance is
|
|
6
|
+
* preserved), runs the matching canonical primitive ({@link ray_shapes}), and
|
|
7
|
+
* rotates the returned local normal back to world. Spheres are
|
|
8
|
+
* rotation-invariant, so they only need the translation.
|
|
9
|
+
*
|
|
10
|
+
* Handles the convex primitives (sphere / box / capsule). Concave shapes
|
|
11
|
+
* (mesh / heightmap) and arbitrary composite convex shapes return
|
|
12
|
+
* {@link RAY_REFINE_UNSUPPORTED} — the concave path is wired in separately, and
|
|
13
|
+
* composites fall back to the broadphase AABB hit (no regression).
|
|
14
|
+
*
|
|
15
|
+
* @param {AbstractShape3D} shape
|
|
16
|
+
* @param {{x:number,y:number,z:number}} position body world position
|
|
17
|
+
* @param {ArrayLike<number>} rotation body world rotation quaternion (x,y,z,w)
|
|
18
|
+
* @param {number} ox @param {number} oy @param {number} oz ray origin (world)
|
|
19
|
+
* @param {number} dx @param {number} dy @param {number} dz ray dir (world, unit)
|
|
20
|
+
* @param {number} tMax
|
|
21
|
+
* @param {Float64Array|Vector3} outNormal world surface normal, written on a
|
|
22
|
+
* finite hit
|
|
23
|
+
* @returns {number} hit distance, `Infinity` on a refined miss, or
|
|
24
|
+
* {@link RAY_REFINE_UNSUPPORTED} when the shape has no exact ray test here
|
|
25
|
+
*/
|
|
26
|
+
export function refine_ray_hit(shape: AbstractShape3D, position: {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
z: number;
|
|
30
|
+
}, rotation: ArrayLike<number>, ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, tMax: number, outNormal: Float64Array | Vector3): number;
|
|
31
|
+
/**
|
|
32
|
+
* Sentinel returned by {@link refine_ray_hit} for a shape it has no exact ray
|
|
33
|
+
* test for (composite / arbitrary convex). The caller should keep the
|
|
34
|
+
* broadphase AABB result for that leaf rather than treat it as a miss.
|
|
35
|
+
* Distinct from `Infinity` (a refined miss) and any finite hit distance.
|
|
36
|
+
* @type {number}
|
|
37
|
+
*/
|
|
38
|
+
export const RAY_REFINE_UNSUPPORTED: number;
|
|
39
|
+
//# sourceMappingURL=refine_ray_hit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refine_ray_hit.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/refine_ray_hit.js"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,iEAVW;IAAC,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAC;IAAA,CAAC,EAAC,MAAM,CAAA;CAAC,YAC5B,UAAU,MAAM,CAAC,MACjB,MAAM,MAAa,MAAM,MAAa,MAAM,MAC5C,MAAM,MAAa,MAAM,MAAa,MAAM,QAC5C,MAAM,aACN,YAAY,UAAQ,GAElB,MAAM,CAsClB;AAzED;;;;;;GAMG;AACH,qCAFU,MAAM,CAEyB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { v3_quat3_apply } from "../../../core/geom/vec3/v3_quat3_apply.js";
|
|
2
|
+
import { ray_sphere_local, ray_box_local, ray_capsule_local } from "./ray_shapes.js";
|
|
3
|
+
import { refine_ray_concave } from "./refine_ray_concave.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sentinel returned by {@link refine_ray_hit} for a shape it has no exact ray
|
|
7
|
+
* test for (composite / arbitrary convex). The caller should keep the
|
|
8
|
+
* broadphase AABB result for that leaf rather than treat it as a miss.
|
|
9
|
+
* Distinct from `Infinity` (a refined miss) and any finite hit distance.
|
|
10
|
+
* @type {number}
|
|
11
|
+
*/
|
|
12
|
+
export const RAY_REFINE_UNSUPPORTED = -1;
|
|
13
|
+
|
|
14
|
+
const lo = new Float64Array(3); // ray origin in shape-local frame
|
|
15
|
+
const ld = new Float64Array(3); // ray direction in shape-local frame
|
|
16
|
+
const ln = new Float64Array(3); // surface normal in shape-local frame
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Refine a broadphase ray hit against a body's true shape geometry.
|
|
20
|
+
*
|
|
21
|
+
* Transforms the world ray into the shape's local frame once (rotate by the
|
|
22
|
+
* inverse body rotation — a unit direction stays unit, so the hit distance is
|
|
23
|
+
* preserved), runs the matching canonical primitive ({@link ray_shapes}), and
|
|
24
|
+
* rotates the returned local normal back to world. Spheres are
|
|
25
|
+
* rotation-invariant, so they only need the translation.
|
|
26
|
+
*
|
|
27
|
+
* Handles the convex primitives (sphere / box / capsule). Concave shapes
|
|
28
|
+
* (mesh / heightmap) and arbitrary composite convex shapes return
|
|
29
|
+
* {@link RAY_REFINE_UNSUPPORTED} — the concave path is wired in separately, and
|
|
30
|
+
* composites fall back to the broadphase AABB hit (no regression).
|
|
31
|
+
*
|
|
32
|
+
* @param {AbstractShape3D} shape
|
|
33
|
+
* @param {{x:number,y:number,z:number}} position body world position
|
|
34
|
+
* @param {ArrayLike<number>} rotation body world rotation quaternion (x,y,z,w)
|
|
35
|
+
* @param {number} ox @param {number} oy @param {number} oz ray origin (world)
|
|
36
|
+
* @param {number} dx @param {number} dy @param {number} dz ray dir (world, unit)
|
|
37
|
+
* @param {number} tMax
|
|
38
|
+
* @param {Float64Array|Vector3} outNormal world surface normal, written on a
|
|
39
|
+
* finite hit
|
|
40
|
+
* @returns {number} hit distance, `Infinity` on a refined miss, or
|
|
41
|
+
* {@link RAY_REFINE_UNSUPPORTED} when the shape has no exact ray test here
|
|
42
|
+
*/
|
|
43
|
+
export function refine_ray_hit(shape, position, rotation, ox, oy, oz, dx, dy, dz, tMax, outNormal) {
|
|
44
|
+
if (shape.isSphereShape3D === true) {
|
|
45
|
+
// Rotation-invariant: translate into the sphere's frame; the local
|
|
46
|
+
// normal is already the world normal.
|
|
47
|
+
return ray_sphere_local(outNormal, ox - position.x, oy - position.y, oz - position.z, dx, dy, dz, tMax, shape.radius);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (shape.isBoxShape3D === true || shape.isCapsuleShape3D === true) {
|
|
51
|
+
const qx = rotation[0], qy = rotation[1], qz = rotation[2], qw = rotation[3];
|
|
52
|
+
// World → local: rotate by the inverse (conjugate) body rotation.
|
|
53
|
+
v3_quat3_apply(lo, 0, ox - position.x, oy - position.y, oz - position.z, -qx, -qy, -qz, qw);
|
|
54
|
+
v3_quat3_apply(ld, 0, dx, dy, dz, -qx, -qy, -qz, qw);
|
|
55
|
+
|
|
56
|
+
let t;
|
|
57
|
+
if (shape.isBoxShape3D === true) {
|
|
58
|
+
const h = shape.half_extents;
|
|
59
|
+
t = ray_box_local(ln, lo[0], lo[1], lo[2], ld[0], ld[1], ld[2], tMax, h.x, h.y, h.z);
|
|
60
|
+
} else {
|
|
61
|
+
t = ray_capsule_local(ln, lo[0], lo[1], lo[2], ld[0], ld[1], ld[2], tMax, shape.radius, shape.height * 0.5);
|
|
62
|
+
}
|
|
63
|
+
if (t === Infinity) return Infinity;
|
|
64
|
+
|
|
65
|
+
// Local normal → world: rotate by the body rotation.
|
|
66
|
+
v3_quat3_apply(outNormal, 0, ln[0], ln[1], ln[2], qx, qy, qz, qw);
|
|
67
|
+
return t;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Concave (mesh / heightmap): triangle decomposition.
|
|
71
|
+
if (shape.is_convex === false) {
|
|
72
|
+
return refine_ray_concave(shape, position, rotation, ox, oy, oz, dx, dy, dz, tMax, outNormal);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Arbitrary composite convex (TransformedShape3D / UnionShape3D / …):
|
|
76
|
+
// no exact ray test yet — caller falls back to the broadphase AABB hit.
|
|
77
|
+
return RAY_REFINE_UNSUPPORTED;
|
|
78
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Closed-form contact generation for two
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `depth` is the positive penetration distance.
|
|
2
|
+
* Closed-form contact generation for two spheres positioned in world space.
|
|
3
|
+
* Returns whether the spheres overlap. On overlap, `out` is populated with
|
|
4
|
+
* `[nx, ny, nz, depth]` where the normal (unit length) points from B toward A
|
|
5
|
+
* and `depth` is the positive penetration distance.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Each sphere's radius is passed explicitly (`radius_a` / `radius_b`) — the
|
|
8
|
+
* caller reads it from the shape (`SphereShape3D.radius`; 1 for the
|
|
9
|
+
* {@link UnitSphereShape3D} special case). Any scaling on the body's transform
|
|
10
|
+
* is irrelevant under our "no scale on physics transforms" assumption.
|
|
10
11
|
*
|
|
11
12
|
* Centres-coincident is a singular case for the general normal but is
|
|
12
13
|
* resolved here by picking +X as a deterministic tie-break direction.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sphere_sphere_contact.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/sphere_sphere_contact.js"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sphere_sphere_contact.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/narrowphase/sphere_sphere_contact.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,2CAXW,MAAM,EAAE,GAAC,YAAY,MACrB,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,YACN,MAAM,YACN,MAAM,GACJ,OAAO,CA4BnB"}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Closed-form contact generation for two
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* `depth` is the positive penetration distance.
|
|
2
|
+
* Closed-form contact generation for two spheres positioned in world space.
|
|
3
|
+
* Returns whether the spheres overlap. On overlap, `out` is populated with
|
|
4
|
+
* `[nx, ny, nz, depth]` where the normal (unit length) points from B toward A
|
|
5
|
+
* and `depth` is the positive penetration distance.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Each sphere's radius is passed explicitly (`radius_a` / `radius_b`) — the
|
|
8
|
+
* caller reads it from the shape (`SphereShape3D.radius`; 1 for the
|
|
9
|
+
* {@link UnitSphereShape3D} special case). Any scaling on the body's transform
|
|
10
|
+
* is irrelevant under our "no scale on physics transforms" assumption.
|
|
10
11
|
*
|
|
11
12
|
* Centres-coincident is a singular case for the general normal but is
|
|
12
13
|
* resolved here by picking +X as a deterministic tie-break direction.
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Raycast against both broadphase trees (static + dynamic) of a
|
|
3
|
-
* {@link PhysicsSystem},
|
|
4
|
-
* `true` on hit, `false` on
|
|
3
|
+
* {@link PhysicsSystem}, refined against each candidate's true shape geometry.
|
|
4
|
+
* Fills `result` with the nearest hit and returns `true` on hit, `false` on
|
|
5
|
+
* miss. `result.t` is the exact surface distance and `result.normal` the true
|
|
6
|
+
* surface normal for sphere / box / capsule / mesh / heightmap colliders;
|
|
7
|
+
* composite convex shapes (no exact ray test yet) fall back to the broadphase
|
|
8
|
+
* AABB hit + AABB-face normal.
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* call site, no API change).
|
|
10
|
+
* Multi-collider bodies resolve their primary (first-attached) collider — the
|
|
11
|
+
* BVH leaf encodes only `body_id`; per-collider rays need the leaf user-data to
|
|
12
|
+
* carry the collider index (future work).
|
|
10
13
|
*
|
|
11
14
|
* @param {PhysicsSystem} system
|
|
12
15
|
* @param {Ray3} ray origin + unit direction + `tMax`
|
|
13
16
|
* @param {PhysicsSurfacePoint} result populated on hit; untouched on miss
|
|
14
|
-
* @param {(entity:number, collider:Collider)=>boolean} [filter]
|
|
15
|
-
*
|
|
16
|
-
* per BVH leaf that crosses the ray.
|
|
17
|
+
* @param {(entity:number, collider:Collider)=>boolean} [filter] called once per
|
|
18
|
+
* crossing leaf; defaults to {@link returnTrue}.
|
|
17
19
|
* @returns {boolean} true on hit, false on miss
|
|
18
20
|
*/
|
|
19
21
|
export function raycast(system: PhysicsSystem, ray: Ray3, result: PhysicsSurfacePoint, filter?: (entity: number, collider: Collider) => boolean): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"raycast.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/queries/raycast.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"raycast.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/queries/raycast.js"],"names":[],"mappings":"AA2IA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,yGAJmB,MAAM,yBAAsB,OAAO,GAEzC,OAAO,CAwCnB"}
|