Whether the points in the collider represent a convex polygon (not concave or complex). This is important, because Rusty Engine's collision detection doesn't work correctly unless colliders are convex polygons. This implementation is based on Rory Daulton's answer on https://stackoverflow.com/questions/471962/how-do-i-efficiently-determine-if-a-polygon-is-convex-non-convex-or-complex?answertab=vo
(&self)
| 229 | /// |
| 230 | /// This implementation is based on Rory Daulton's answer on https://stackoverflow.com/questions/471962/how-do-i-efficiently-determine-if-a-polygon-is-convex-non-convex-or-complex?answertab=votes#tab-top |
| 231 | pub fn is_convex(&self) -> bool { |
| 232 | if let Collider::Poly(points) = self { |
| 233 | let length = points.len(); |
| 234 | if length < 3 { |
| 235 | return false; // empty sets, points and lines are not convex polygons |
| 236 | } |
| 237 | // the source algorithm deals with individual x's and y's and the combined points in |
| 238 | // disjoint ways, so we need to follow the pattern unless we want to modify the |
| 239 | // algorithm itself. |
| 240 | let mut old_x = points[length - 2].x; |
| 241 | let mut old_y = points[length - 2].y; |
| 242 | let mut new_x = points[length - 1].x; |
| 243 | let mut new_y = points[length - 1].y; |
| 244 | let mut new_direction = (new_y - old_y).atan2(new_x - old_x); |
| 245 | let mut angle_sum = 0.0; |
| 246 | let mut old_direction; |
| 247 | let mut orientation = 0.0; |
| 248 | for (idx, newpoint) in points.iter().enumerate() { |
| 249 | // The fact that new_x and new_y are re-used at the top of the loop with the |
| 250 | // expectation that they have the last loop's values is why we can't use the |
| 251 | // newpoint loop variable directly. Messy. :-/ |
| 252 | old_x = new_x; |
| 253 | old_y = new_y; |
| 254 | old_direction = new_direction; |
| 255 | new_x = newpoint.x; |
| 256 | new_y = newpoint.y; |
| 257 | new_direction = (new_y - old_y).atan2(new_x - old_x); |
| 258 | if (old_x == new_x) && (old_y == new_y) { |
| 259 | return false; // repeated consecutive points |
| 260 | } |
| 261 | // Calculate & check the normalized deriction-change angle |
| 262 | let mut angle = new_direction - old_direction; |
| 263 | if angle <= -PI { |
| 264 | angle += TAU; // make it in half-open interval (-Pi, Pi] |
| 265 | } else if angle > PI { |
| 266 | angle -= TAU; |
| 267 | } |
| 268 | if idx == 0 { |
| 269 | // if first time through loop, initialize orientation |
| 270 | if angle == 0.0 { |
| 271 | return false; // the source algorithm doesn't explain this one |
| 272 | } |
| 273 | if angle > 0.0 { |
| 274 | orientation = 1.0; |
| 275 | } else { |
| 276 | orientation = -1.0; |
| 277 | } |
| 278 | } else if orientation * angle <= 0.0 { |
| 279 | // not both positive or both negative |
| 280 | return false; |
| 281 | } |
| 282 | |
| 283 | // Accumulate the direction-change angle |
| 284 | angle_sum += angle; |
| 285 | } |
| 286 | // Check that the total number of full turns is plus-or-minus 1 |
| 287 | let full_turns = (angle_sum / TAU).abs(); |
| 288 | return (full_turns > 0.9999) && (full_turns < 1.0001); |