Pico Headers
Loading...
Searching...
No Matches
pico_hit.h
Go to the documentation of this file.
1
64#ifndef PICO_HIT_H
65#define PICO_HIT_H
66
67#include "pico_math.h"
68
69#ifdef __cplusplus
70extern "C" {
71#endif
72
73// Maximum number of vertices in a polygon
74#ifndef PICO_HIT_MAX_POLY_VERTS
75#define PICO_HIT_MAX_POLY_VERTS 16
76#endif
77
81typedef struct
82{
86
99
105typedef struct
106{
110} ph_sat_t;
111
115typedef struct
116{
118 pfloat depth; //< Depth of the contact relative to the incident edge
120
121typedef struct
122{
125 ph_contact_t contacts[2];
126 int count;
128
132typedef struct
133{
137} ph_ray_t;
138
142typedef struct
143{
147
154
162ph_poly_t ph_make_poly(const pv2 vertices[], int count, bool reverse);
163
171
178
186bool ph_sat_poly_poly(const ph_poly_t* poly_a,
187 const ph_poly_t* poly_b,
188 ph_sat_t* result);
189
198 const ph_circle_t* circle,
199 ph_sat_t* result);
200
209 const ph_poly_t* poly,
210 ph_sat_t* result);
211
220 const ph_circle_t* circle_b,
221 ph_sat_t* result);
222
231 const ph_poly_t* poly_b,
232 ph_manifold_t* manifold);
233
242 const ph_circle_t *circle,
243 ph_manifold_t* manifold);
244
253 const ph_poly_t *poly,
254 ph_manifold_t* manifold);
255
264 const ph_circle_t* circle_b,
265 ph_manifold_t* manifold);
266
276bool ph_ray_line(const ph_ray_t* ray, pv2 s1, pv2 s2, ph_raycast_t* raycast);
277
286bool ph_ray_poly(const ph_ray_t* ray, const ph_poly_t* poly, ph_raycast_t* raycast);
287
296bool ph_ray_circle(const ph_ray_t* ray, const ph_circle_t* circle, ph_raycast_t* raycast);
297
301pv2 ph_ray_at(const ph_ray_t* ray, pfloat dist);
302
309ph_poly_t ph_transform_poly(const pt2* transform, const ph_poly_t* poly);
310
317ph_circle_t ph_transform_circle(const pt2* transform, const ph_circle_t* circle);
318
323
328
329#ifdef __cplusplus
330}
331#endif
332
333#endif // PICO_HIT_H
334
335#ifdef PICO_HIT_IMPLEMENTATION // Define once
336
337#ifdef NDEBUG
338 #define PICO_HIT_ASSERT(expr) ((void)0)
339#else
340 #ifndef PICO_HIT_ASSERT
341 #include <assert.h>
342 #define PICO_HIT_ASSERT(expr) (assert(expr))
343 #endif
344#endif
345
346#define PH_ASSERT PICO_HIT_ASSERT // Alias
347
348/*=============================================================================
349 * Internal function declarations
350 *============================================================================*/
351
352// Initializes a result
353static void ph_init_result(ph_sat_t* result);
354
355// Determines a polygon's limits under a projection onto an axis
356static void ph_project_poly(const ph_poly_t* poly,
357 pv2 normal,
358 pfloat* min,
359 pfloat* max);
360
361// Determines the limits of a circle's projection onto an axis
362static void ph_project_circle(const ph_circle_t *circle,
363 pv2 axis,
364 pfloat *min,
365 pfloat *max);
366
367// Calculates the overlap of two shapes from their projections on an axis
368static pfloat ph_calc_overlap(pfloat min1, pfloat max1, pfloat min2, pfloat max2);
369
370// Returns the vertex closest to a given point
371static int ph_closest_vertex(const ph_poly_t *poly, pv2 point);
372
373// Find the point on a line segment that is closest to the specified point
374static pv2 ph_closest_point_on_segment(pv2 end_point_a, pv2 end_point_b, pv2 point);
375
376// Find a reference candidate for the given polygon
377static int ph_find_best_edge(const ph_poly_t* poly, pv2 normal, pfloat* max_dot);
378
379// Find the incident edge for the given incident polygon
380static int ph_find_incident_edge(const ph_poly_t* poly, pv2 normal);
381
382// Clip input points against the specified plane normal
383static int ph_clip_segment_to_line(pv2* in_points, pv2* out_points, pv2 plane_normal, pfloat offset);
384
385// 2D matrix
386typedef struct
387{
388 pfloat a11, a12, a21, a22;
389} ph_m2;
390
391// Determinant of 2D matrix
392static pfloat ph_m2_det(ph_m2 m);
393
394// Inverse of 2D matrix
395static ph_m2 ph_m2_inverse(ph_m2 m, pfloat det);
396
397// Map 2D vector by 2D matrix
398static pv2 ph_m2_map(ph_m2 m, pv2 v);
399
400/*=============================================================================
401 * Public API implementation
402 *============================================================================*/
403
404ph_circle_t ph_make_circle(pv2 center, pfloat radius)
405{
406 ph_circle_t circle;
407 circle.center = center;
408 circle.radius = radius;
409 return circle;
410}
411
412ph_poly_t ph_make_poly(const pv2 vertices[], int count, bool reverse)
413{
414 PH_ASSERT(vertices);
415 PH_ASSERT(count <= PICO_HIT_MAX_POLY_VERTS);
416
417 ph_poly_t poly = { 0 };
418
419 // Copy vertices
420 poly.count = count;
421
422 // Convert CW to CCW if true
423 if (reverse)
424 {
425 for (int i = 0; i < count; i++)
426 {
427 poly.vertices[i] = vertices[count - i - 1];
428
429 }
430 }
431 else
432 {
433 for (int i = 0; i < count; i++)
434 {
435 poly.vertices[i] = vertices[i];
436 }
437 }
438
439 poly.centroid = pv2_zero();
440
441 // Cache edges and edge normals
442 for (int i = 0; i < count; i++)
443 {
444 pv2 v1 = poly.vertices[i];
445 pv2 v2 = poly.vertices[(i + 1) % count];
446
447 poly.edges[i] = pv2_sub(v2, v1);
448 poly.normals[i] = pv2_perp(poly.edges[i]);
449 poly.normals[i] = pv2_normalize(poly.normals[i]);
450
451 poly.centroid = pv2_add(poly.centroid, poly.vertices[i]);
452 }
453
454 poly.centroid = pv2_scale(poly.centroid, 1.0f / count);
455
456 return poly;
457}
458
459ph_ray_t ph_make_ray(pv2 origin, pv2 dir, pfloat len)
460{
461 return (ph_ray_t){ origin, pv2_normalize(dir), len};
462}
463
464ph_poly_t ph_aabb_to_poly(const pb2* aabb)
465{
466 PH_ASSERT(aabb);
467
468 // Get AABB properties
469 pv2 pos = pb2_get_pos(aabb);
470 pv2 size = pb2_get_size(aabb);
471
472 // Specify AABB vertices using CCW winding
473 pv2 vertices[] =
474 {
475 { pos.x, pos.y },
476 { pos.x, pos.y + size.y },
477 { pos.x + size.x, pos.y + size.y },
478 { pos.x + size.x, pos.y }
479 };
480
481 return ph_make_poly(vertices, 4, false);
482}
483
484/*
485 * The SAT test states that two convex polygons do not overlap if it is
486 * possible to draw a non-intersecting line between them (a separating
487 * axis). It is sufficient to check the normal directions of each face of the
488 * polygons to see if there is overlap.
489 */
490bool ph_sat_poly_poly(const ph_poly_t* poly_a,
491 const ph_poly_t* poly_b,
492 ph_sat_t* result)
493{
494 PH_ASSERT(poly_a);
495 PH_ASSERT(poly_b);
496
497 // Reset result
498 if (result)
499 ph_init_result(result);
500
501 // Test axises on poly_a
502 for (int i = 0; i < poly_a->count; i++)
503 {
504 pfloat poly_a_min, poly_a_max, poly_b_min, poly_b_max;
505
506 // Project poly_a onto the normal axis
507 ph_project_poly(poly_a, poly_a->normals[i], &poly_a_min, &poly_a_max);
508
509 // Project poly_b onto the normal axis
510 ph_project_poly(poly_b, poly_a->normals[i], &poly_b_min, &poly_b_max);
511
512 // Calculate the overlap of the two polygons
513 pfloat overlap = ph_calc_overlap(poly_a_min, poly_a_max, poly_b_min, poly_b_max);
514
515 // Axis is separating
516 if (overlap < 0.0f)
517 return false;
518
519 // Update result
520 if (result && overlap < result->overlap)
521 {
522 result->overlap = overlap;
523 result->normal = poly_a->normals[i];
524 }
525 }
526
527 // Test axises on poly_b
528 for (int i = 0; i < poly_b->count; i++)
529 {
530 pfloat poly_b_min, poly_b_max, poly_a_min, poly_a_max;
531
532 // Project poly_b onto the normal axis
533 ph_project_poly(poly_b, poly_b->normals[i], &poly_b_min, &poly_b_max);
534
535 // Project poly_a the normal axis
536 ph_project_poly(poly_a, poly_b->normals[i], &poly_a_min, &poly_a_max);
537
538 // Calculate the overlap of the two polygons
539 pfloat overlap = ph_calc_overlap(poly_b_min, poly_b_max, poly_a_min, poly_a_max);
540
541 // Axis is separating
542 if (overlap < 0.0f)
543 return false;
544
545 // Update result
546 if (result && overlap < result->overlap)
547 {
548 result->overlap = overlap;
549 result->normal = poly_a->normals[i];
550 }
551 }
552
553 if (result)
554 {
555 // Ensure the normal vector has the correct orientation
556 pv2 diff = pv2_sub(poly_b->centroid, poly_a->centroid);
557
558 if (pv2_dot(diff, result->normal) < 0.0f)
559 {
560 result->normal = pv2_reflect(result->normal);
561 }
562
563 result->mtv = pv2_scale(result->normal, -result->overlap);
564 }
565
566 return true;
567}
568
569/*
570 * As in the poly/poly case, if an axis separates the a polygon and a circe
571 * then they do not overlap (circles are essentially convex). Thus we check
572 * along the face normals of the polyon to see if there is overlap. There are
573 * cases, however, where this is not sufficent. In this case we check the axis
574 * from the vertex closest to the center of the circle and see if there is any
575 * overlap.
576 */
577bool ph_sat_poly_circle(const ph_poly_t* poly,
578 const ph_circle_t* circle,
579 ph_sat_t* result)
580{
581 // Reset result
582 if (result)
583 ph_init_result(result);
584
585 for (int i = 0; i < poly->count; i++)
586 {
587 pv2 axis = poly->normals[i];
588
589 float poly_min, poly_max, circle_min, circle_max;
590
591 // Project polygon onto axis
592 ph_project_poly(poly, axis, &poly_min, &poly_max);
593
594 // Project circle onto axis
595 ph_project_circle(circle, axis, &circle_min, &circle_max);
596
597 // Calculate overlap of the polygon and circle
598 float overlap = ph_calc_overlap(poly_min, poly_max, circle_min, circle_max);
599
600 // Axis is separating
601 if (overlap < 0.0f)
602 return false;
603
604 // Update result
605 if (result && overlap < result->overlap)
606 {
607 result->overlap = overlap;
608 result->normal = axis;
609 }
610 }
611
612 // Test axis from closest polygon vertex to circle center
613 int closest = ph_closest_vertex(poly, circle->center);
614 pv2 axis = pv2_normalize(pv2_sub(circle->center, poly->vertices[closest]));
615
616 if (pv2_len(axis) > PM_EPSILON)
617 {
618 pfloat poly_min, poly_max, circle_min, circle_max;
619
620 // Project polygon onto axis
621 ph_project_poly(poly, axis, &poly_min, &poly_max);
622
623 // Project circle onto axis
624 ph_project_circle(circle, axis, &circle_min, &circle_max);
625
626 // Calculate overlap of the polygon and circle
627 pfloat overlap = ph_calc_overlap(poly_min, poly_max, circle_min, circle_max);
628
629 // Axis is separating
630 if (overlap < 0.0f)
631 return false;
632
633 // Update result
634 if (result && overlap < result->overlap)
635 {
636 result->overlap = overlap;
637 result->normal = axis;
638 }
639 }
640
641 if (result)
642 {
643 // Ensure the normal vector has the correct orientation
644 pv2 diff = pv2_sub(circle->center, poly->centroid);
645
646
647 if (pv2_dot(result->normal, diff) < 0.0f)
648 {
649 result->normal = pv2_reflect(result->normal);
650 }
651
652 result->mtv = pv2_scale(result->normal, -result->overlap);
653 }
654
655 return true;
656}
657
658bool ph_sat_circle_poly(const ph_circle_t* circle,
659 const ph_poly_t* poly,
660 ph_sat_t* result)
661{
662 PH_ASSERT(poly);
663 PH_ASSERT(circle);
664
665 bool hit = ph_sat_poly_circle(poly, circle, (result) ? result : NULL);
666
667 if (hit && result)
668 {
669 // Since arguments were swapped, reversing these vectors is all that is
670 // required
671 result->normal = pv2_reflect(result->normal);
672 result->mtv = pv2_reflect(result->mtv);
673 }
674
675 return hit;
676}
677
678bool ph_sat_circle_circle(const ph_circle_t* circle_a,
679 const ph_circle_t* circle_b,
680 ph_sat_t* result)
681{
682 PH_ASSERT(circle_a);
683 PH_ASSERT(circle_b);
684
685 // Reset result
686 if (result)
687 ph_init_result(result);
688
689 // Position of circle_b relative to circle_a
690 pv2 diff = pv2_sub(circle_b->center, circle_a->center);
691
692 // Squared distance between circle centers
693 pfloat dist2 = pv2_len2(diff);
694
695 // Sum of radii
696 pfloat total_radius = circle_a->radius + circle_b->radius;
697
698 // Square sum of radii for optimization (avoid calculating length until we
699 // have to)
700 pfloat total_radius2 = total_radius * total_radius;
701
702 // Equivalent to dist >= total_radius
703 if (dist2 >= total_radius2)
704 return false;
705
706 if (result)
707 {
708 // Calculate distance because we need it now
709 pfloat dist = pf_sqrt(dist2);
710
711 // Calculate overlap
712 pfloat overlap = total_radius - dist;
713
714 // Normal direction is just circle_b relative to circle_a
715 pv2 normal = pv2_normalize(diff);
716
717 result->overlap = overlap;
718 result->normal = normal;
719
720 result->mtv = pv2_scale(result->normal, -result->overlap);
721 }
722
723 return true;
724}
725
726bool ph_manifold_poly_poly(const ph_poly_t* poly_a,
727 const ph_poly_t* poly_b,
728 ph_manifold_t* manifold)
729{
730 PH_ASSERT(poly_a);
731 PH_ASSERT(poly_b);
732 PH_ASSERT(manifold);
733
734 ph_sat_t result = { 0 };
735
736 if (!ph_sat_poly_poly(poly_a, poly_b, &result))
737 return false;
738
739 manifold->count = 0;
740 manifold->normal = result.normal;
741 manifold->overlap = result.overlap;
742
743 pv2 normal = result.normal;
744
745 pfloat max_dot_a = 0.f;
746 pfloat max_dot_b = 0.f;
747
748 // Get reference edge candidates
749 int best_edge_a = ph_find_best_edge(poly_a, normal, &max_dot_a);
750 int best_edge_b = ph_find_best_edge(poly_b, pv2_reflect(normal), &max_dot_b);
751
752 const ph_poly_t* ref_poly = NULL;
753 const ph_poly_t* inc_poly = NULL;
754 int ref_index = 0;
755
756 // Determine the reference and incident polygons
757 if (max_dot_a > max_dot_b)
758 {
759 ref_poly = poly_a;
760 inc_poly = poly_b;
761 ref_index = best_edge_a;
762 }
763 else
764 {
765 ref_poly = poly_b;
766 inc_poly = poly_a;
767 ref_index = best_edge_b;
768 normal = pv2_reflect(normal);
769 }
770
771 // Determine the incident edge in the incident polygon
772 int inc_index = ph_find_incident_edge(inc_poly, normal);
773
774 // Reference edge vertices
775 pv2 ref_v1 = ref_poly->vertices[ref_index];
776 pv2 ref_v2 = ref_poly->vertices[(ref_index + 1) % ref_poly->count];
777
778 // Incident edge vertices
779 pv2 inc_v1 = inc_poly->vertices[inc_index];
780 pv2 inc_v2 = inc_poly->vertices[(inc_index + 1) % inc_poly->count];
781
782 // Reference edge tangent and normal
783 pv2 ref_edge = pv2_sub(ref_v2, ref_v1);
784 pv2 ref_tangent = pv2_normalize(ref_edge);
785 pv2 ref_normal = pv2_perp(ref_tangent);
786
787 if (pv2_dot(ref_normal, normal) < 0.0f)
788 ref_normal = pv2_reflect(ref_normal);
789
790 // Clip incident edge against reference side planes
791 pv2 clip_in[2] = { inc_v1, inc_v2 };
792 pv2 clip_out[2] = { 0 };
793
794 // First clip against +tangent plane (side1)
795 pv2 side_normal1 = ref_tangent;
796 pfloat offset1 = pv2_dot(side_normal1, ref_v1);
797 int num_clipped = ph_clip_segment_to_line(clip_in, clip_out, side_normal1, offset1);
798
799 if (num_clipped < 2)
800 return false;
801
802 // Then clip against -tangent plane (side2)
803 pv2 side_normal2 = pv2_reflect(ref_tangent);
804 pfloat offset2 = pv2_dot(side_normal2, ref_v2);
805 num_clipped = ph_clip_segment_to_line(clip_out, clip_in, side_normal2, offset2);
806
807 if (num_clipped < 2)
808 return false;
809
810 // Keep points that are behind the reference face plane (penetrating)
811 for (int i = 0; i < num_clipped; i++)
812 {
813 pfloat separation = pv2_dot(ref_normal, pv2_sub(clip_in[i], ref_v1));
814
815 if (separation <= 0.0f)
816 {
817 ph_contact_t* contact = &manifold->contacts[manifold->count];
818 contact->point = clip_in[i];
819 contact->depth = -separation;
820 manifold->count++;
821
822 if (manifold->count >= 2)
823 break;
824 }
825 }
826
827 return (manifold->count > 0);
828}
829
830bool ph_manifold_poly_circle(const ph_poly_t *poly, const ph_circle_t *circle, ph_manifold_t* manifold)
831{
832 PH_ASSERT(poly);
833 PH_ASSERT(circle);
834 PH_ASSERT(manifold);
835
836 ph_sat_t result = { 0 };
837
838 if (!ph_sat_poly_circle(poly, circle, &result))
839 return false;
840
841 manifold->normal = result.normal;
842 manifold->overlap = result.overlap;
843 manifold->count = 1;
844
845 pv2 closest = poly->vertices[0];
846 pfloat min_dist2 = PM_FLOAT_MAX;
847
848 // Loop through all edges and find the point on the edge that is closest to
849 // to the circle center
850 for (int i = 0; i < poly->count; i++)
851 {
852 int next = (i + 1) % poly->count;
853
854 pv2 point = ph_closest_point_on_segment(
855 poly->vertices[i],
856 poly->vertices[next],
857 circle->center
858 );
859
860 pv2 diff = pv2_sub(point, circle->center);
861 float dist2 = pv2_dot(diff, diff);
862
863 if (dist2 < min_dist2)
864 {
865 min_dist2 = dist2;
866 closest = point;
867 }
868 }
869
870 // Use the SAT overlap for contact depth when available. This represents the
871 // minimum translational distance (MTD) separating the shapes along the
872 // collision normal. It handles cases where the circle is contained within
873 // the polygon as well as edge/vertex contacts.
874 manifold->contacts[0].depth = result.overlap;
875 manifold->contacts[0].point = closest;
876
877 return true;
878}
879
880bool ph_manifold_circle_poly(const ph_circle_t* circle, const ph_poly_t* poly, ph_manifold_t* manifold)
881{
882 PH_ASSERT(circle);
883 PH_ASSERT(poly);
884 PH_ASSERT(manifold);
885
886 ph_manifold_t tmp = { 0 };
887
888 if (!ph_manifold_poly_circle((ph_poly_t*)poly, (ph_circle_t*)circle, &tmp))
889 return false;
890
891 manifold->normal = pv2_reflect(tmp.normal);
892 manifold->overlap = tmp.overlap;
893 manifold->count = tmp.count;
894 manifold->contacts[0] = tmp.contacts[0];
895
896 return true;
897}
898
899bool ph_manifold_circle_circle(const ph_circle_t* circle_a,
900 const ph_circle_t* circle_b,
901 ph_manifold_t* manifold)
902{
903 PH_ASSERT(circle_a);
904 PH_ASSERT(circle_b);
905 PH_ASSERT(manifold);
906
907 ph_sat_t result = { 0 };
908
909 if (!ph_sat_circle_circle(circle_a, circle_b, &result))
910 return false;
911
912 manifold->normal = result.normal;
913 manifold->overlap = result.overlap;
914 manifold->count = 1;
915
916 // Compute contact point as midpoint between the two surface points
917 pv2 diff = pv2_sub(circle_b->center, circle_a->center);
918 pfloat dist2 = pv2_len2(diff);
919
920 pfloat dist = pf_sqrt(dist2);
921 pv2 normal = pv2_zero();
922
923 if (dist > PM_EPSILON)
924 normal = pv2_scale(diff, 1.0f / dist);
925 else
926 normal = pv2_make(1.0f, 0.0f);
927
928 /* Ensure manifold normal is well defined */
929 manifold->normal = normal;
930
931 pv2 pa = pv2_add(circle_a->center, pv2_scale(normal, circle_a->radius));
932 pv2 pb = pv2_sub(circle_b->center, pv2_scale(normal, circle_b->radius));
933
934 pv2 contact = pv2_scale(pv2_add(pa, pb), 0.5f);
935
936 manifold->contacts[0].point = contact;
937 manifold->contacts[0].depth = manifold->overlap;
938
939 return true;
940}
941
942/*
943 The basic idea here is to represent the rays in parametric form and
944 solve a linear equation to get the parameters where they intersect.
945 In this application we are only interested in the case where both of them
946 are contained in the interval [0, 1]
947*/
948bool ph_ray_line(const ph_ray_t* ray, pv2 s1, pv2 s2, ph_raycast_t* raycast)
949{
950 pv2 r1 = ray->origin;
951 pv2 r2 = pv2_add(ray->origin, pv2_scale(ray->dir, ray->len));
952
953 pv2 v = pv2_sub(r2, r1);
954 pv2 w = pv2_sub(s2, s1);
955
956 ph_m2 m =
957 {
958 -v.x, w.x,
959 -v.y, w.y,
960 };
961
962 pfloat det = ph_m2_det(m);
963
964 if (pf_equal(det, 0.0f))
965 return false;
966
967 ph_m2 m_inv = ph_m2_inverse(m, det);
968
969 pv2 c = pv2_sub(r1, s1);
970 pv2 p = ph_m2_map(m_inv, c);
971
972 bool hit = 0.0f <= p.x && p.x <= 1.0f &&
973 0.0f <= p.y && p.y <= 1.0f;
974
975 if (hit && raycast)
976 {
977 raycast->normal = pv2_normalize(pv2_perp(w)); //TODO: ensure correct orientation
978 raycast->dist = p.x * ray->len;
979 }
980
981 return hit;
982}
983
984/*
985 The idea behind this function is to use ph_ray_line on each of the edges
986 that make up the polygon. The function can exit early if the raycast is NULL.
987 Otherwise all edges of the polygon will be tested.
988*/
989bool ph_ray_poly(const ph_ray_t* ray, const ph_poly_t* poly, ph_raycast_t* raycast)
990{
991 pv2 min_normal = pv2_zero();
992 pfloat min_dist = PM_FLOAT_MAX;
993
994 bool has_hit = false;
995
996 for (int i = 0; i < poly->count; i++)
997 {
998 int next = (i + 1) % poly->count;
999
1000 pv2 s1 = poly->vertices[i];
1001 pv2 s2 = poly->vertices[next];
1002
1003 bool hit = ph_ray_line(ray, s1, s2, raycast);
1004
1005 if (hit && raycast)
1006 {
1007 has_hit = true;
1008
1009 if (raycast->dist < min_dist)
1010 {
1011 min_dist = raycast->dist;
1012 min_normal = raycast->normal;
1013 }
1014 }
1015 else if (hit)
1016 {
1017 return true;
1018 }
1019 }
1020
1021 if (has_hit)
1022 {
1023 raycast->dist = min_dist;
1024 raycast->normal = min_normal;
1025 return true;
1026 }
1027
1028 return false;
1029}
1030
1031/*
1032 The idea behind this function is to represent a constraint, that determines
1033 whether the ray intersects the circle, as a polynomial. The discriminant of
1034 the quadratic fomula is used to test various cases corresponding to the
1035 location and direction of the ray, and the circle.
1036
1037 Source: Real-Time Collision Detection by Christer Ericson
1038*/
1039bool ph_ray_circle(const ph_ray_t* ray, const ph_circle_t* circle, ph_raycast_t* raycast)
1040{
1041 pfloat r = circle->radius;
1042 pv2 m = pv2_sub(ray->origin, circle->center);
1043 pfloat b = pv2_dot(m, ray->dir);
1044 pfloat c = pv2_dot(m, m) - r * r;
1045
1046 if (c > 0.0f && b > 0.0f)
1047 return false;
1048
1049 pfloat discr = b * b - c;
1050
1051 if (discr < 0.0f)
1052 return false;
1053
1054 if (raycast)
1055 {
1056 pfloat dist = -b - pf_sqrt(discr);
1057
1058 if (dist < 0.0f)
1059 dist = 0.0f;
1060
1061 pv2 p = pv2_add(ray->origin, pv2_scale(ray->dir, dist));
1062
1063 raycast->dist = dist;
1064 raycast->normal = pv2_normalize(pv2_sub(p, circle->center));
1065 }
1066
1067 return true;
1068}
1069
1070pv2 ph_ray_at(const ph_ray_t* ray, pfloat dist)
1071{
1072 return pv2_add(ray->origin, pv2_scale(ray->dir, dist));
1073}
1074
1075ph_poly_t ph_transform_poly(const pt2* transform, const ph_poly_t* poly)
1076{
1077 pv2 vertices[poly->count];
1078
1079 for (int i = 0; i < poly->count; i++)
1080 {
1081 vertices[i] = pt2_map(transform, poly->vertices[i]);
1082 }
1083
1084 return ph_make_poly(vertices, poly->count, pt2_det(transform) < 0.0);
1085}
1086
1087ph_circle_t ph_transform_circle(const pt2* transform,
1088 const ph_circle_t* circle)
1089{
1090 return ph_make_circle(pt2_map(transform, circle->center), circle->radius);
1091}
1092
1093pb2 ph_poly_to_aabb(const ph_poly_t* poly)
1094{
1095 return pb2_enclosing(poly->vertices, poly->count);
1096}
1097
1098pb2 ph_circle_to_aabb(const ph_circle_t* circle)
1099{
1100 pv2 half_radius = pv2_make(circle->radius / 2.0f, circle->radius / 2.0f);
1101
1102 pv2 min = pv2_sub(circle->center, half_radius);
1103 pv2 max = pv2_add(circle->center, half_radius);
1104
1105 return pb2_make_minmax(min, max);
1106}
1107
1108/*=============================================================================
1109 * Internal function definitions
1110 *============================================================================*/
1111
1112static void ph_init_result(ph_sat_t* result)
1113{
1114 PH_ASSERT(result);
1115
1116 result->overlap = PM_FLOAT_MAX;
1117 result->normal = pv2_zero();
1118 result->mtv = pv2_zero();
1119}
1120
1121static pfloat ph_calc_overlap(pfloat min1, pfloat max1, pfloat min2, pfloat max2)
1122{
1123 // Compute signed overlap (negative => separated, zero => touching, positive => intersecting)
1124 pfloat overlap = pf_min(max1, max2) - pf_max(min1, min2);
1125
1126 if (overlap < 0.0f)
1127 return overlap;
1128
1129 // Treat touching (zero overlap) as a collision by returning a small positive value
1130 if (pf_equal(overlap, 0.0f))
1131 return PM_EPSILON;
1132
1133 return overlap;
1134}
1135
1136static void ph_project_poly(const ph_poly_t* poly,
1137 pv2 axis,
1138 pfloat* min,
1139 pfloat* max)
1140{
1141 PH_ASSERT(min);
1142 PH_ASSERT(max);
1143
1144 pfloat dot = pv2_dot(poly->vertices[0], axis);
1145 *min = dot;
1146 *max = dot;
1147
1148 // Find the minimum and maximum distance of the polygon along the normal
1149 for (int i = 1; i < poly->count; i++)
1150 {
1151 dot = pv2_dot(poly->vertices[i], axis);
1152
1153 if (dot < *min)
1154 *min = dot;
1155
1156 if (dot > *max)
1157 *max = dot;
1158 }
1159}
1160
1161static void ph_project_circle(const ph_circle_t *circle, pv2 axis, pfloat *min, pfloat *max)
1162{
1163 pfloat proj = pv2_dot(axis, circle->center);
1164 *min = proj - circle->radius;
1165 *max = proj + circle->radius;
1166}
1167
1168static int ph_closest_vertex(const ph_poly_t *poly, pv2 point)
1169{
1170 int closest = 0;
1171 pfloat min_dist2 = PM_FLOAT_MAX;
1172
1173 for (int i = 0; i < poly->count; i++)
1174 {
1175 pv2 diff = pv2_sub(poly->vertices[i], point);
1176 pfloat dist2 = pv2_dot(diff, diff);
1177
1178 if (dist2 < min_dist2)
1179 {
1180 min_dist2 = dist2;
1181 closest = i;
1182 }
1183 }
1184
1185 return closest;
1186}
1187
1188// Works by projecting (end_point_a, point) onto (end_point_a, pv2 end_point_b)
1189static pv2 ph_closest_point_on_segment(pv2 end_point_a, pv2 end_point_b, pv2 point)
1190{
1191 pv2 ab = pv2_sub(end_point_b, end_point_a);
1192 pv2 ap = pv2_sub(point, end_point_b);
1193
1194 pfloat ab_len2 = pv2_dot(ab, ab);
1195
1196 if (ab_len2 < PM_EPSILON)
1197 return end_point_a;
1198
1199 pfloat alpha = pv2_dot(ap, ab) / ab_len2;
1200 alpha = pf_max(0.0f, pf_min(1.0f, alpha));
1201
1202 return pv2_add(end_point_a, pv2_scale(ab, alpha));
1203}
1204
1205// Finds the edge that is most perpendicular to the separation normal vector
1206static int ph_find_best_edge(const ph_poly_t* poly, pv2 normal, pfloat* max_dot)
1207{
1208 int index = 0;
1209 *max_dot = PM_FLOAT_MIN;
1210
1211 for (int i = 0; i < poly->count; i++)
1212 {
1213 pfloat dot = pv2_dot(poly->normals[i], normal);
1214
1215 if (dot > *max_dot)
1216 {
1217 *max_dot = dot;
1218 index = i;
1219 }
1220 }
1221
1222 return index;
1223}
1224
1225// Finds the edge most anti-parallel to reference edge
1226static int ph_find_incident_edge(const ph_poly_t* poly, pv2 normal)
1227{
1228 pfloat min_dot = PM_FLOAT_MAX;
1229
1230 int index = 0;
1231
1232 for (int i = 0; i < poly->count; i++)
1233 {
1234 pfloat dot = pv2_dot(poly->normals[i], normal);
1235
1236 if (dot < min_dot)
1237 {
1238 min_dot = dot;
1239 index = i;
1240 }
1241 }
1242
1243 return index;
1244}
1245
1246static int ph_clip_segment_to_line(pv2* in_points, pv2* out_points, pv2 plane_normal, pfloat offset)
1247{
1248 int num_out = 0;
1249
1250 float d0 = pv2_dot(plane_normal, in_points[0]) - offset;
1251 float d1 = pv2_dot(plane_normal, in_points[1]) - offset;
1252
1253 // Both points in front of plane - keep both
1254 if (d0 >= 0.0f) out_points[num_out++] = in_points[0];
1255 if (d1 >= 0.0f) out_points[num_out++] = in_points[1];
1256
1257 // Points on opposite sides - find intersection
1258 if (d0 * d1 < 0.0f)
1259 {
1260 pfloat alpha = d0 / (d0 - d1);
1261 pv2 intersection = pv2_add(in_points[0], pv2_scale(pv2_sub(in_points[1], in_points[0]), alpha));
1262 out_points[num_out++] = intersection;
1263 }
1264
1265 return num_out;
1266}
1267
1268// Determinant of 2D matrix
1269static pfloat ph_m2_det(ph_m2 m)
1270{
1271 return m.a11 * m.a22 - m.a21 * m.a12;
1272}
1273
1274// Inverse of 2D matrix
1275static ph_m2 ph_m2_inverse(ph_m2 m, pfloat det)
1276{
1277 pfloat inv_det = 1.0f / det;
1278 return (ph_m2) { m.a22 * inv_det, -m.a12 * inv_det, -m.a21 * inv_det, m.a11 * inv_det };
1279}
1280
1281// Map 2D vector by 2D matrix
1282static pv2 ph_m2_map(ph_m2 m, pv2 v)
1283{
1284 return (pv2){ m.a11 * v.x + m.a12 * v.y, m.a21 * v.x + m.a22 * v.y };
1285}
1286
1287#endif // PICO_HIT_IMPLEMENTATION
1288
1289/*
1290The MIT License (MIT)
1291
1292Copyright (C) 2022 - 2026 by James McLean
1293Copyright (C) 2012 - 2015 by Jim Riecken
1294
1295Permission is hereby granted, free of charge, to any person obtaining a copy
1296of this software and associated documentation files (the "Software"), to deal
1297in the Software without restriction, including without limitation the rights
1298to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1299copies of the Software, and to permit persons to whom the Software is
1300furnished to do so, subject to the following conditions:
1301
1302The above copyright notice and this permission notice shall be included in
1303all copies or substantial portions of the Software.
1304
1305THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1306IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1307FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1308AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1309LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1310OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1311THE SOFTWARE.
1312*/
ph_poly_t ph_make_poly(const pv2 vertices[], int count, bool reverse)
Initializes a polygon.
bool ph_sat_poly_circle(const ph_poly_t *poly, const ph_circle_t *circle, ph_sat_t *result)
Tests to see if a convex polygon overlaps a circle.
ph_poly_t ph_transform_poly(const pt2 *transform, const ph_poly_t *poly)
Transforms a polygon using an affine transform.
bool ph_sat_circle_poly(const ph_circle_t *circle, const ph_poly_t *poly, ph_sat_t *result)
Tests to see if a circle overlaps a polygon.
pb2 ph_poly_to_aabb(const ph_poly_t *poly)
Returns the bounding box for the given polygon.
ph_circle_t ph_transform_circle(const pt2 *transform, const ph_circle_t *circle)
Transforms a circle using an affine transform.
bool ph_manifold_circle_poly(const ph_circle_t *circle, const ph_poly_t *poly, ph_manifold_t *manifold)
Tests if a circle and polygon collide and generates contact information.
bool ph_manifold_circle_circle(const ph_circle_t *circle_a, const ph_circle_t *circle_b, ph_manifold_t *manifold)
Tests if two circles collide and generates contact information.
#define PICO_HIT_MAX_POLY_VERTS
Definition pico_hit.h:75
ph_circle_t ph_make_circle(pv2 center, pfloat radius)
Initializes a circle.
bool ph_manifold_poly_circle(const ph_poly_t *poly, const ph_circle_t *circle, ph_manifold_t *manifold)
Tests if a polygon and circle collide and generates contact information.
bool ph_ray_circle(const ph_ray_t *ray, const ph_circle_t *circle, ph_raycast_t *raycast)
Tests if ray intersects a circle.
bool ph_sat_poly_poly(const ph_poly_t *poly_a, const ph_poly_t *poly_b, ph_sat_t *result)
Tests to see if one convex polygon overlaps with another.
ph_poly_t ph_aabb_to_poly(const pb2 *aabb)
Converts an axis-aligned bounding box (AABB) to a polygon.
bool ph_manifold_poly_poly(const ph_poly_t *poly_a, const ph_poly_t *poly_b, ph_manifold_t *manifold)
Tests if a polygon collides with another polygon and generates contact information.
pb2 ph_circle_to_aabb(const ph_circle_t *circle)
Returns the bounding box for the given circle.
bool ph_ray_line(const ph_ray_t *ray, pv2 s1, pv2 s2, ph_raycast_t *raycast)
Tests if ray intersects a (directed) line segment.
bool ph_sat_circle_circle(const ph_circle_t *circle_a, const ph_circle_t *circle_b, ph_sat_t *result)
Tests to see if two circles overlap.
bool ph_ray_poly(const ph_ray_t *ray, const ph_poly_t *poly, ph_raycast_t *raycast)
Tests if ray intersects a polygon.
pv2 ph_ray_at(const ph_ray_t *ray, pfloat dist)
Finds the point along the ray at the specified distance from the origin.
ph_ray_t ph_make_ray(pv2 origin, pv2 dir, pfloat len)
Constructs a ray.
A 2D math library for games.
PM_INLINE pfloat pv2_dot(pv2 v1, pv2 v2)
Dot product.
Definition pico_math.h:312
#define pv2_make(x, y)
Constructs a vector.
Definition pico_math.h:268
#define pv2_zero()
Returns the zero vector.
Definition pico_math.h:423
PM_INLINE pfloat pt2_det(const pt2 *t)
Returns the determinant of the transform.
Definition pico_math.h:560
PM_INLINE pv2 pv2_scale(pv2 v, pfloat c)
Scales a vector.
Definition pico_math.h:304
PM_INLINE pv2 pv2_normalize(pv2 v)
Normalizes a vector (sets its length to one)
Definition pico_math.h:338
PM_INLINE pv2 pb2_get_pos(const pb2 *b)
Returns the position of an AABB.
Definition pico_math.h:666
PM_INLINE bool pf_equal(pfloat c1, pfloat c2)
Returns true if the values are within epsilon of one another.
Definition pico_math.h:221
PM_INLINE pv2 pv2_sub(pv2 v1, pv2 v2)
Subtracts two vectors.
Definition pico_math.h:294
float pfloat
A single precision floating point number.
Definition pico_math.h:137
#define PM_FLOAT_MAX
Definition pico_math.h:146
#define pb2_make_minmax(min, max)
Definition pico_math.h:651
#define pf_sqrt
Definition pico_math.h:148
#define PM_EPSILON
Definition pico_math.h:139
PM_INLINE pfloat pv2_len(pv2 v)
Returns the length of the vector.
Definition pico_math.h:328
#define pf_max
Definition pico_math.h:161
#define pf_min
Definition pico_math.h:162
PM_INLINE pfloat pv2_len2(pv2 v)
Returns the square of the length of the vector.
Definition pico_math.h:320
PM_INLINE pv2 pv2_perp(pv2 v)
Construct a vector that is perpendicular to the specified vector.
Definition pico_math.h:363
PM_INLINE pv2 pb2_get_size(const pb2 *b)
Returns the dimensions of an AABB.
Definition pico_math.h:674
PM_INLINE pv2 pv2_add(pv2 v1, pv2 v2)
Adds two vectors.
Definition pico_math.h:284
pb2 pb2_enclosing(const pv2 verts[], int count)
Computes the minimum box containing all of the vertices.
PM_INLINE pv2 pv2_reflect(pv2 v)
Negates a vector (scales it by -1.0)
Definition pico_math.h:353
#define PM_FLOAT_MIN
Definition pico_math.h:145
PM_INLINE pv2 pt2_map(const pt2 *t, pv2 v)
Transforms a vector.
Definition pico_math.h:549
A 2D axis-aligned-bounding-box (AABB)
Definition pico_math.h:189
A circle shape.
Definition pico_hit.h:82
pfloat radius
Radius of the circle.
Definition pico_hit.h:84
pv2 center
Center of circle.
Definition pico_hit.h:83
A contact point.
Definition pico_hit.h:116
pfloat depth
Definition pico_hit.h:118
pv2 point
Position of the contact in world-space.
Definition pico_hit.h:117
Definition pico_hit.h:122
pfloat overlap
Amount of overlap between two shapes along colliding axis (MTD)
Definition pico_hit.h:124
ph_contact_t contacts[2]
Contact points (maximum of two)
Definition pico_hit.h:125
int count
Numer of contacts.
Definition pico_hit.h:126
pv2 normal
Contact normal (from SAT)
Definition pico_hit.h:123
A polygon shape Must use CCW (counter-clockwise) winding.
Definition pico_hit.h:92
pv2 centroid
Definition pico_hit.h:97
pv2 normals[PICO_HIT_MAX_POLY_VERTS]
Polygon edge normals.
Definition pico_hit.h:95
pv2 edges[PICO_HIT_MAX_POLY_VERTS]
Edges of polygon.
Definition pico_hit.h:96
pv2 vertices[PICO_HIT_MAX_POLY_VERTS]
Polygon vertices.
Definition pico_hit.h:94
int count
Number of vertices in polygon.
Definition pico_hit.h:93
A ray (directed line segment)
Definition pico_hit.h:133
pv2 dir
The direction of the ray (normalized)
Definition pico_hit.h:135
pv2 origin
The origin of the ray.
Definition pico_hit.h:134
pfloat len
The length of the ray.
Definition pico_hit.h:136
Raycast information.
Definition pico_hit.h:143
pv2 normal
The surface normal at the point of impact.
Definition pico_hit.h:144
pfloat dist
The distance from the ray's origin to the point of impact.
Definition pico_hit.h:145
A collision result Provides information about a collision. Normals always point from the target shape...
Definition pico_hit.h:106
pfloat overlap
Amount of overlap between two shapes along colliding axis (MTD)
Definition pico_hit.h:108
pv2 normal
Normal to colliding edge (in direction of MTV)
Definition pico_hit.h:107
pv2 mtv
Minimum Translation Vector (MTV) defined by `mtv = normal * overlap.
Definition pico_hit.h:109
A 2D transform.
Definition pico_math.h:181
A 2D vector.
Definition pico_math.h:173
pfloat y
Definition pico_math.h:174
pfloat x
Definition pico_math.h:174