1use crate::distance::ShapeProxy;
7use crate::geometry::{Capsule, PlaneResult};
8use crate::id::ShapeId;
9use crate::math_functions::{Aabb, Plane, Pos, Vec3, POS_ZERO, VEC3_ZERO};
10use crate::recording::dispatch::RecReader;
11use crate::types::{QueryFilter, RayResult};
12use crate::world::{
13 world_cast_mover, world_cast_ray, world_cast_ray_closest, world_cast_shape,
14 world_collide_mover, world_overlap_aabb, world_overlap_shape, World,
15};
16
17#[derive(Clone, Default)]
18struct RecordedHit {
19 id: ShapeId,
20 point: Pos,
21 normal: Vec3,
22 fraction: f32,
23 user_material_id: u64,
24 triangle_index: i32,
25 child_index: i32,
26 user_return_f: f32,
27 user_return_b: bool,
28 plane: PlaneResult,
29 plane_count: i32,
30}
31
32struct ReplayCtx<'a> {
33 rdr: *mut RecReader<'a>,
34 hits: Vec<RecordedHit>,
35 cursor: usize,
36}
37
38fn f32_differs(a: f32, b: f32) -> bool {
39 a.to_bits() != b.to_bits()
40}
41
42fn vec3_differs(a: Vec3, b: Vec3) -> bool {
43 f32_differs(a.x, b.x) || f32_differs(a.y, b.y) || f32_differs(a.z, b.z)
44}
45
46fn pos_differs(a: Pos, b: Pos) -> bool {
47 #[cfg(feature = "double-precision")]
48 {
49 a.x.to_bits() != b.x.to_bits()
50 || a.y.to_bits() != b.y.to_bits()
51 || a.z.to_bits() != b.z.to_bits()
52 }
53 #[cfg(not(feature = "double-precision"))]
54 {
55 vec3_differs(a, b)
56 }
57}
58
59fn mark_diverged(rdr: &mut RecReader<'_>) {
60 rdr.diverged = true;
61}
62
63fn make_shape_id(rdr: &RecReader<'_>, recorded: ShapeId) -> ShapeId {
64 rdr.make_shape_id(recorded)
65}
66
67pub fn dispatch_query_overlap_aabb(
69 rdr: &mut RecReader<'_>,
70 world: &mut World,
71 aabb: Aabb,
72 filter: QueryFilter,
73) {
74 let mut s = rdr.snap();
75 let n = s.u32() as usize;
76 let mut hits = Vec::with_capacity(n);
77 for _ in 0..n {
78 let id = make_shape_id(rdr, s.shape_id());
79 let user_return_b = s.bool();
80 hits.push(RecordedHit {
81 id,
82 user_return_b,
83 ..Default::default()
84 });
85 }
86 let _ = s.i32(); let _ = s.i32(); rdr.sync_from(&s);
89 if !rdr.ok {
90 return;
91 }
92
93 let mut ctx = ReplayCtx {
94 rdr: rdr as *mut _,
95 hits,
96 cursor: 0,
97 };
98 world_overlap_aabb(world, aabb, &filter, |id| {
99 if ctx.cursor >= ctx.hits.len() {
100 unsafe { mark_diverged(&mut *ctx.rdr) };
101 return false;
102 }
103 let h = &ctx.hits[ctx.cursor];
104 ctx.cursor += 1;
105 if id.index1 != h.id.index1 || id.generation != h.id.generation {
106 unsafe { mark_diverged(&mut *ctx.rdr) };
107 }
108 h.user_return_b
109 });
110 if ctx.cursor != ctx.hits.len() {
111 rdr.diverged = true;
112 }
113 stash_simple(
114 rdr,
115 RecQueryKind::OverlapAabb,
116 &ctx.hits,
117 aabb,
118 POS_ZERO,
119 VEC3_ZERO,
120 filter,
121 );
122}
123
124pub fn dispatch_query_overlap_shape(
126 rdr: &mut RecReader<'_>,
127 world: &mut World,
128 origin: Pos,
129 proxy: ShapeProxy,
130 filter: QueryFilter,
131) {
132 let mut s = rdr.snap();
133 let n = s.u32() as usize;
134 let mut hits = Vec::with_capacity(n);
135 for _ in 0..n {
136 let id = make_shape_id(rdr, s.shape_id());
137 let user_return_b = s.bool();
138 hits.push(RecordedHit {
139 id,
140 user_return_b,
141 ..Default::default()
142 });
143 }
144 let _ = s.i32();
145 let _ = s.i32();
146 rdr.sync_from(&s);
147 if !rdr.ok {
148 return;
149 }
150
151 let mut ctx = ReplayCtx {
152 rdr: rdr as *mut _,
153 hits,
154 cursor: 0,
155 };
156 world_overlap_shape(world, origin, &proxy, &filter, |id| {
157 if ctx.cursor >= ctx.hits.len() {
158 unsafe { mark_diverged(&mut *ctx.rdr) };
159 return false;
160 }
161 let h = &ctx.hits[ctx.cursor];
162 ctx.cursor += 1;
163 if id.index1 != h.id.index1 || id.generation != h.id.generation {
164 unsafe { mark_diverged(&mut *ctx.rdr) };
165 }
166 h.user_return_b
167 });
168 if ctx.cursor != ctx.hits.len() {
169 rdr.diverged = true;
170 }
171 stash_simple(
172 rdr,
173 RecQueryKind::OverlapShape,
174 &ctx.hits,
175 Aabb::default(),
176 origin,
177 VEC3_ZERO,
178 filter,
179 );
180}
181
182pub fn dispatch_query_cast_ray(
184 rdr: &mut RecReader<'_>,
185 world: &mut World,
186 origin: Pos,
187 translation: Vec3,
188 filter: QueryFilter,
189) {
190 let hits = read_cast_hits(rdr);
191 if !rdr.ok {
192 return;
193 }
194 let mut ctx = ReplayCtx {
195 rdr: rdr as *mut _,
196 hits,
197 cursor: 0,
198 };
199 world_cast_ray(
200 world,
201 origin,
202 translation,
203 &filter,
204 |id, point, normal, fraction, mid, tri, child| {
205 if ctx.cursor >= ctx.hits.len() {
206 unsafe { mark_diverged(&mut *ctx.rdr) };
207 return 0.0;
208 }
209 let h = &ctx.hits[ctx.cursor];
210 ctx.cursor += 1;
211 if id.index1 != h.id.index1
212 || id.generation != h.id.generation
213 || pos_differs(point, h.point)
214 || vec3_differs(normal, h.normal)
215 || f32_differs(fraction, h.fraction)
216 || mid != h.user_material_id
217 || tri != h.triangle_index
218 || child != h.child_index
219 {
220 unsafe { mark_diverged(&mut *ctx.rdr) };
221 }
222 h.user_return_f
223 },
224 );
225 if ctx.cursor != ctx.hits.len() {
226 rdr.diverged = true;
227 }
228 stash_simple(
229 rdr,
230 RecQueryKind::CastRay,
231 &ctx.hits,
232 Aabb::default(),
233 origin,
234 translation,
235 filter,
236 );
237}
238
239pub fn dispatch_query_cast_shape(
241 rdr: &mut RecReader<'_>,
242 world: &mut World,
243 origin: Pos,
244 proxy: ShapeProxy,
245 translation: Vec3,
246 filter: QueryFilter,
247) {
248 let hits = read_cast_hits(rdr);
249 if !rdr.ok {
250 return;
251 }
252 let mut ctx = ReplayCtx {
253 rdr: rdr as *mut _,
254 hits,
255 cursor: 0,
256 };
257 world_cast_shape(
258 world,
259 origin,
260 &proxy,
261 translation,
262 &filter,
263 |id, point, normal, fraction, mid, tri, child| {
264 if ctx.cursor >= ctx.hits.len() {
265 unsafe { mark_diverged(&mut *ctx.rdr) };
266 return 0.0;
267 }
268 let h = &ctx.hits[ctx.cursor];
269 ctx.cursor += 1;
270 if id.index1 != h.id.index1
271 || id.generation != h.id.generation
272 || pos_differs(point, h.point)
273 || vec3_differs(normal, h.normal)
274 || f32_differs(fraction, h.fraction)
275 || mid != h.user_material_id
276 || tri != h.triangle_index
277 || child != h.child_index
278 {
279 unsafe { mark_diverged(&mut *ctx.rdr) };
280 }
281 h.user_return_f
282 },
283 );
284 if ctx.cursor != ctx.hits.len() {
285 rdr.diverged = true;
286 }
287 stash_simple(
288 rdr,
289 RecQueryKind::CastShape,
290 &ctx.hits,
291 Aabb::default(),
292 origin,
293 translation,
294 filter,
295 );
296}
297
298pub fn dispatch_query_cast_ray_closest(
300 rdr: &mut RecReader<'_>,
301 world: &mut World,
302 origin: Pos,
303 translation: Vec3,
304 filter: QueryFilter,
305) {
306 let mut s = rdr.snap();
307 let rec_shape = make_shape_id(rdr, s.shape_id());
308 let rec = RayResult {
309 shape_id: rec_shape,
310 point: s.pos(),
311 normal: s.vec3(),
312 user_material_id: s.u64(),
313 fraction: s.f32(),
314 triangle_index: s.i32(),
315 child_index: s.i32(),
316 hit: s.bool(),
317 node_visits: 0,
318 leaf_visits: 0,
319 };
320 rdr.sync_from(&s);
321 if !rdr.ok {
322 return;
323 }
324
325 let got = world_cast_ray_closest(world, origin, translation, &filter);
326 if got.hit != rec.hit
327 || (got.hit
328 && (got.shape_id.index1 != rec.shape_id.index1
329 || got.shape_id.generation != rec.shape_id.generation
330 || pos_differs(got.point, rec.point)
331 || vec3_differs(got.normal, rec.normal)
332 || f32_differs(got.fraction, rec.fraction)
333 || got.user_material_id != rec.user_material_id))
334 {
335 rdr.diverged = true;
336 }
337
338 let hits = if rec.hit {
339 vec![RecordedHit {
340 id: rec.shape_id,
341 point: rec.point,
342 normal: rec.normal,
343 fraction: rec.fraction,
344 ..Default::default()
345 }]
346 } else {
347 Vec::new()
348 };
349 stash_simple(
350 rdr,
351 RecQueryKind::CastRayClosest,
352 &hits,
353 Aabb::default(),
354 origin,
355 translation,
356 filter,
357 );
358}
359
360pub fn dispatch_query_cast_mover(
362 rdr: &mut RecReader<'_>,
363 world: &mut World,
364 origin: Pos,
365 mover: Capsule,
366 translation: Vec3,
367 filter: QueryFilter,
368) {
369 let mut s = rdr.snap();
370 let n = s.u32() as usize;
371 let mut hits = Vec::with_capacity(n);
372 for _ in 0..n {
373 let id = make_shape_id(rdr, s.shape_id());
374 let user_return_b = s.bool();
375 hits.push(RecordedHit {
376 id,
377 user_return_b,
378 ..Default::default()
379 });
380 }
381 let rec_fraction = s.f32();
382 rdr.sync_from(&s);
383 if !rdr.ok {
384 return;
385 }
386
387 let mut ctx = ReplayCtx {
388 rdr: rdr as *mut _,
389 hits,
390 cursor: 0,
391 };
392 let mut filter_fcn = |id: ShapeId| -> bool {
393 if ctx.cursor >= ctx.hits.len() {
394 unsafe { mark_diverged(&mut *ctx.rdr) };
395 return false;
396 }
397 let h = &ctx.hits[ctx.cursor];
398 ctx.cursor += 1;
399 if id.index1 != h.id.index1 || id.generation != h.id.generation {
400 unsafe { mark_diverged(&mut *ctx.rdr) };
401 }
402 h.user_return_b
403 };
404 let got = world_cast_mover(
405 world,
406 origin,
407 &mover,
408 translation,
409 &filter,
410 Some(&mut filter_fcn),
411 );
412 if ctx.cursor != ctx.hits.len() || f32_differs(got, rec_fraction) {
413 rdr.diverged = true;
414 }
415 stash_simple(
416 rdr,
417 RecQueryKind::CastMover,
418 &ctx.hits,
419 Aabb::default(),
420 origin,
421 translation,
422 filter,
423 );
424}
425
426pub fn dispatch_query_collide_mover(
428 rdr: &mut RecReader<'_>,
429 world: &mut World,
430 origin: Pos,
431 mover: Capsule,
432 filter: QueryFilter,
433) {
434 let mut s = rdr.snap();
435 let shape_count = s.u32() as usize;
436 let mut hits = Vec::new();
437 for _ in 0..shape_count {
438 let id = make_shape_id(rdr, s.shape_id());
439 let plane_count = s.i32().max(0);
440 let mut planes = Vec::with_capacity(plane_count as usize);
441 for _ in 0..plane_count {
442 planes.push(PlaneResult {
443 plane: Plane {
444 normal: s.vec3(),
445 offset: s.f32(),
446 },
447 point: s.vec3(),
448 });
449 }
450 let user_return_b = s.bool();
451 for p in &planes {
452 hits.push(RecordedHit {
453 id,
454 plane: *p,
455 plane_count,
456 user_return_b,
457 ..Default::default()
458 });
459 }
460 }
461 rdr.sync_from(&s);
462 if !rdr.ok {
463 return;
464 }
465
466 let total = hits.len();
467 let mut ctx = ReplayCtx {
468 rdr: rdr as *mut _,
469 hits,
470 cursor: 0,
471 };
472 world_collide_mover(world, origin, &mover, &filter, |id, planes| {
473 if ctx.cursor >= ctx.hits.len() {
474 unsafe { mark_diverged(&mut *ctx.rdr) };
475 return true;
476 }
477 let head = &ctx.hits[ctx.cursor];
478 let recorded_count = head.plane_count;
479 let ret = head.user_return_b;
480 if id.index1 != head.id.index1
481 || id.generation != head.id.generation
482 || recorded_count != planes.len() as i32
483 {
484 unsafe { mark_diverged(&mut *ctx.rdr) };
485 }
486 let n = recorded_count.min(planes.len() as i32) as usize;
487 for i in 0..n {
488 let h = &ctx.hits[ctx.cursor + i];
489 if vec3_differs(h.plane.plane.normal, planes[i].plane.normal)
490 || f32_differs(h.plane.plane.offset, planes[i].plane.offset)
491 || vec3_differs(h.plane.point, planes[i].point)
492 {
493 unsafe { mark_diverged(&mut *ctx.rdr) };
494 }
495 }
496 ctx.cursor += recorded_count as usize;
497 ret
498 });
499 if ctx.cursor != total {
500 rdr.diverged = true;
501 }
502 stash_simple(
503 rdr,
504 RecQueryKind::CollideMover,
505 &ctx.hits,
506 Aabb::default(),
507 origin,
508 VEC3_ZERO,
509 filter,
510 );
511}
512
513fn read_cast_hits(rdr: &mut RecReader<'_>) -> Vec<RecordedHit> {
514 let mut s = rdr.snap();
515 let n = s.u32() as usize;
516 let mut hits = Vec::with_capacity(n);
517 for _ in 0..n {
518 let id = make_shape_id(rdr, s.shape_id());
519 hits.push(RecordedHit {
520 id,
521 point: s.pos(),
522 normal: s.vec3(),
523 fraction: s.f32(),
524 user_material_id: s.u64(),
525 triangle_index: s.i32(),
526 child_index: s.i32(),
527 user_return_f: s.f32(),
528 ..Default::default()
529 });
530 }
531 let _ = s.i32();
532 let _ = s.i32();
533 rdr.sync_from(&s);
534 hits
535}
536
537#[derive(Debug, Clone, Copy, PartialEq, Eq)]
539#[repr(i32)]
540pub enum RecQueryKind {
541 OverlapAabb = 0,
542 OverlapShape = 1,
543 CastRay = 2,
544 CastShape = 3,
545 CastRayClosest = 4,
546 CastMover = 5,
547 CollideMover = 6,
548}
549
550#[derive(Clone)]
552pub struct FrameQuery {
553 pub kind: RecQueryKind,
554 pub hit_count: i32,
555 pub key: u64,
556 pub filter: QueryFilter,
557 pub origin: Pos,
558 pub translation: Vec3,
559 pub aabb: Aabb,
560}
561
562fn stash_simple(
563 rdr: &mut RecReader<'_>,
564 kind: RecQueryKind,
565 hits: &[RecordedHit],
566 aabb: Aabb,
567 origin: Pos,
568 translation: Vec3,
569 filter: QueryFilter,
570) {
571 let Some(owner) = rdr.owner else {
572 rdr.pending_query_key = 0;
573 return;
574 };
575 let player = unsafe { &mut *owner };
577 let key = rdr.pending_query_key;
578 rdr.pending_query_key = 0;
579 player.frame_queries.push(FrameQuery {
580 kind,
581 hit_count: hits.len() as i32,
582 key,
583 filter,
584 origin,
585 translation,
586 aabb,
587 });
588}