├── README.md ├── demo.gif └── sr_resolve.h /README.md: -------------------------------------------------------------------------------- 1 | # sr_resolve.h 2 | 3 | A simple single header C/C++ Library for AABB Collision detection and resolution 4 | 5 | ![](./demo.gif) 6 | 7 | ## Structs 8 | 9 | ```c 10 | // Rectangle 11 | typedef struct sr_rec { 12 | float x; 13 | float y; 14 | float width; 15 | float height; 16 | } sr_rec; 17 | 18 | // 2D Vector 19 | typedef struct sr_vec2 { 20 | float x; 21 | float y; 22 | } sr_vec2; 23 | 24 | // 2D Ray 25 | typedef struct sr_vec2 { 26 | sr_vec2 position; 27 | sr_vec2 direction; 28 | } sr_vec2; 29 | ``` 30 | 31 | ## Function 32 | ```c 33 | bool sr_check_rec_vs_rec_collision(sr_rec rec1, sr_rec rec2); 34 | bool sr_check_ray_vs_rec_collision(const sr_ray2 ray, const sr_rec target, sr_vec2 *contact_point, sr_vec2 *contact_normal, float *t_hit_near); 35 | bool sr_dynamic_rect_vs_rect(const sr_rec in, const sr_rec target, sr_vec2 vel, sr_vec2 *contact_point, sr_vec2 *contact_normal, float *contact_time, float delta) 36 | // The function you will use in your game 37 | void sr_move_and_slide(sr_rec *obstacles, int obstacles_length, sr_vec2 hitbox, sr_vec2 *vel, sr_vec2 *pos , float delta); 38 | ``` 39 | 40 | ## Example 41 | Here is a demo 42 | https://github.com/siddharthroy12/sr_resolve_example -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddharthroy12/sr_resolve/99b7c68e5a8b38d316892bffd4ccdac2ab154687/demo.gif -------------------------------------------------------------------------------- /sr_resolve.h: -------------------------------------------------------------------------------- 1 | // sr_resolve.h - v1.0 - Swept AABB collision resolver - Siddharth Roy 2021 2 | 3 | #ifndef SR_RESOLVE_H 4 | #define SR_RESOLVE_H 5 | 6 | #include 7 | #include 8 | 9 | #define SR_RESOLVE_MAX(x, y) (x > y ? x : y) 10 | #define SR_RESOLVE_MIN(x, y) (x > y ? y : x) 11 | 12 | typedef struct sr_rec { 13 | float x; 14 | float y; 15 | float width; 16 | float height; 17 | } sr_rec; 18 | 19 | typedef struct sr_vec2 { 20 | float x; 21 | float y; 22 | } sr_vec2; 23 | 24 | typedef struct sr_ray2 { 25 | sr_vec2 position; 26 | sr_vec2 direction; 27 | } sr_ray2; 28 | 29 | typedef struct sr_sort_pair { 30 | int index; 31 | float time; 32 | } sr_sort_pair; 33 | 34 | static void swap_float(float *vec1, float *vec2) { 35 | float temp = *vec1; 36 | *vec1 = *vec2; 37 | *vec2 = temp; 38 | } 39 | 40 | // Get the length of a vector 41 | static float sr_vec2_length(sr_vec2 v) { 42 | float result = sqrtf((v.x*v.x) + (v.y*v.y)); 43 | return result; 44 | } 45 | 46 | // Scale a vector by a given float value 47 | static sr_vec2 sr_vec2_scale(sr_vec2 v, float scale) { 48 | return (sr_vec2){ v.x*scale, v.y*scale }; 49 | } 50 | 51 | // Divide two vectors 52 | static sr_vec2 sr_vec2_divide(sr_vec2 v1, sr_vec2 v2) { 53 | return (sr_vec2){ v1.x/v2.x, v1.y/v2.y }; 54 | } 55 | 56 | // Multiply two vectors 57 | static sr_vec2 sr_vec2_multiply(sr_vec2 v1, sr_vec2 v2) { 58 | return (sr_vec2){ v1.x*v2.x, v1.y*v2.y }; 59 | } 60 | 61 | // Normalize a vector 62 | static sr_vec2 sr_vec2_normalize(sr_vec2 v) { 63 | return sr_vec2_scale(v, 1 / sr_vec2_length(v)); 64 | } 65 | 66 | // Substract two vectors 67 | static sr_vec2 sr_vector2_sub(sr_vec2 v1, sr_vec2 v2) { 68 | return (sr_vec2){ v1.x - v2.x, v1.y - v2.y }; 69 | } 70 | 71 | // Add two vectors 72 | static sr_vec2 sr_vector2_add(sr_vec2 v1, sr_vec2 v2) { 73 | return (sr_vec2){ v1.x + v2.x, v1.y + v2.y }; 74 | } 75 | 76 | // Check collision between two rectangles 77 | static bool sr_check_rec_vs_rec_collision(sr_rec rec1, sr_rec rec2) { 78 | if ( 79 | (rec1.x < (rec2.x + rec2.width) && (rec1.x + rec1.width) > rec2.x) && 80 | (rec1.y < (rec2.y + rec2.height) && (rec1.y + rec1.height) > rec2.y) 81 | ) { 82 | return true; 83 | } else { 84 | return false; 85 | } 86 | } 87 | 88 | // Check collision between a ray and a rectangle 89 | static bool sr_check_ray_vs_rec_collision(const sr_ray2 ray, const sr_rec target, sr_vec2 *contact_point, sr_vec2 *contact_normal, float *t_hit_near) { 90 | // Cache division 91 | sr_vec2 indiv = sr_vec2_divide((sr_vec2){1.0f, 1.0f}, ray.direction); 92 | 93 | // Calculate intersections with rectangle bounding axes 94 | sr_vec2 t_near = sr_vec2_multiply( 95 | sr_vector2_sub((sr_vec2){ target.x, target.y }, ray.position), 96 | indiv 97 | ); 98 | sr_vec2 t_far = sr_vec2_multiply( 99 | sr_vector2_sub( 100 | sr_vector2_add((sr_vec2){target.x, target.y}, (sr_vec2){target.width, target.height}), 101 | ray.position 102 | ), 103 | indiv 104 | ); 105 | 106 | // Check for nan 107 | if (isnanf(t_far.y) || isnanf(t_far.x)) return false; 108 | if (isnanf(t_near.y) || isnanf(t_near.x)) return false; 109 | 110 | // Sort distances 111 | if (t_near.x > t_far.x) swap_float(&t_near.x, &t_far.x); 112 | if (t_near.y > t_far.y) swap_float(&t_near.y, &t_far.y); 113 | 114 | // Early rejection 115 | if (t_near.x > t_far.y || t_near.y > t_far.x) return false; 116 | 117 | // Closest 'time' will be the first contact 118 | *t_hit_near = SR_RESOLVE_MAX(t_near.x, t_near.y); 119 | 120 | // Furthest 'time' is contact on opposite side of target 121 | float t_hit_far = SR_RESOLVE_MIN(t_far.x, t_far.y); 122 | 123 | // Reject if ray direction is pointing away from object 124 | if (t_hit_far < 0) return false; 125 | 126 | // Contact point of collision from parametric line equation 127 | *contact_point = sr_vector2_add(ray.position, sr_vec2_scale(ray.direction, *t_hit_near)); 128 | 129 | if (t_near.x > t_near.y) 130 | if (ray.direction.x < 0) 131 | *contact_normal = (sr_vec2){ 1, 0 }; 132 | else 133 | *contact_normal = (sr_vec2){ -1, 0 }; 134 | else if (t_near.x < t_near.y) 135 | if (ray.direction.y < 0) 136 | *contact_normal = (sr_vec2){ 0, 1 }; 137 | else 138 | *contact_normal = (sr_vec2){ 0, -1 }; 139 | 140 | return true; 141 | } 142 | 143 | static bool sr_dynamic_rect_vs_rect(const sr_rec in, const sr_rec target, sr_vec2 vel, sr_vec2 *contact_point, sr_vec2 *contact_normal, float *contact_time, float delta) { 144 | // Check if dynamic rectangle is actually moving - we assume rectangles are NOT in collision to start 145 | if (vel.x == 0 && vel.y == 0) return false; 146 | 147 | // Expand target rectangle by source dimensions 148 | sr_rec expanded_target; 149 | expanded_target.x = target.x - (in.width/2); 150 | expanded_target.y = target.y - (in.height/2); 151 | expanded_target.width = target.width + in.width; 152 | expanded_target.height = target.height + in.height; 153 | 154 | if (sr_check_ray_vs_rec_collision( 155 | (sr_ray2){ 156 | (sr_vec2){ 157 | in.x + (in.width/2), 158 | in.y + (in.height/2) 159 | }, 160 | sr_vec2_scale(vel, delta) 161 | }, 162 | expanded_target, 163 | contact_point, 164 | contact_normal, 165 | contact_time 166 | )) { 167 | if (*contact_time <= 1.0f && *contact_time >= -1.0f) return true; 168 | } 169 | 170 | return false; 171 | } 172 | 173 | // Used for sorting obstacles 174 | static void sr_sort_indexes(sr_sort_pair *times, int length) { 175 | sr_sort_pair key; 176 | int i, j; 177 | 178 | for (i = 1; i < length; i++) { 179 | key = times[i]; 180 | j = i - 1; 181 | 182 | while(j >= 0 && times[j].time > key.time) { 183 | times[j+1] = times[j]; 184 | j = j -1; 185 | } 186 | 187 | times[j + 1] = key; 188 | } 189 | } 190 | 191 | static void sr_move_and_slide(sr_rec *obstacles, int obstacles_length, sr_vec2 hitbox, sr_vec2 *vel, sr_vec2 *pos , float delta) { 192 | sr_sort_pair times[obstacles_length]; 193 | sr_vec2 cp, cn; 194 | float time = 0; 195 | 196 | sr_rec hitbox_rec = { 197 | pos->x - (hitbox.x/2), 198 | pos->y - (hitbox.y/2), 199 | hitbox.x, 200 | hitbox.y 201 | }; 202 | 203 | sr_rec broadface = { 204 | .x = vel->x * delta > 0 ? hitbox_rec.x : hitbox_rec.x + vel->x * delta, 205 | .y = vel->y * delta > 0 ? hitbox_rec.y : hitbox_rec.y + vel->y * delta, 206 | .width = vel->x * delta > 0 ? vel->x * delta + hitbox_rec.width : hitbox_rec.width - vel->x * delta, 207 | .height = vel->y * delta > 0 ? vel->y * delta + hitbox_rec.height : hitbox_rec.height - vel->y * delta 208 | }; 209 | 210 | for (int i = 0; i < obstacles_length; i++) { 211 | sr_dynamic_rect_vs_rect(hitbox_rec, obstacles[i], *vel, &cp, &cn, &time, delta); 212 | times[i].index = i; 213 | times[i].time = time; 214 | } 215 | 216 | // TODO: Check what obstacles are in the way using broadface and then sort only them 217 | // Sorting obstacles because stopping issue 218 | sr_sort_indexes(times, obstacles_length); 219 | 220 | for (int i = 0; i < obstacles_length; i++) { 221 | // Broadface check for every obstacles 222 | if (sr_check_rec_vs_rec_collision(broadface, obstacles[times[i].index])) { 223 | // Resolve collision and velocity for every obstacles 224 | if (sr_dynamic_rect_vs_rect(hitbox_rec, obstacles[times[i].index], *vel, &cp, &cn, &time, delta)) { 225 | pos->x = cp.x; 226 | pos->y = cp.y; 227 | 228 | if (fabs(cn.x)) { 229 | vel->x = 0; 230 | } 231 | if (fabs(cn.y)) { 232 | vel->y = 0; 233 | } 234 | } 235 | } 236 | } 237 | } 238 | #endif 239 | --------------------------------------------------------------------------------