diff --git a/src/common.h b/src/common.h index bb5270bb..521a0b7a 100644 --- a/src/common.h +++ b/src/common.h @@ -595,7 +595,10 @@ struct clip_rect { #define QUAD_UNIT_SQUARE (struct quad) { V2(0, 0), V2(0, 1), V2(1, 1), V2(1, 0) } #define QUAD_UNIT_SQUARE_CENTERED (struct quad) { V2(-0.5f, -0.5f), V2(0.5f, -0.5f), V2(0.5f, 0.5f), V2(-0.5f, 0.5f) } struct quad { - struct v2 p1, p2, p3, p4; + union { + struct { struct v2 p1, p2, p3, p4; }; + struct { struct v2 e[4]; }; + }; }; /* (T)ranslation, (R)otation, (S)cale */ diff --git a/src/draw.c b/src/draw.c index cf0271b4..48a37e02 100644 --- a/src/draw.c +++ b/src/draw.c @@ -246,7 +246,7 @@ void draw_solid_arrow_line(struct renderer_canvas *canvas, struct v2 start, stru struct v2 head_start = v2_add(end, head_start_dir); - struct v2 head_p1_dir = v2_mul(v2_perp(head_start_dir), head_width_ratio); + struct v2 head_p1_dir = v2_mul(v2_perp_cw(head_start_dir), head_width_ratio); struct v2 head_p2_dir = v2_neg(head_p1_dir); struct v2 head_p1 = v2_add(head_start, head_p1_dir); diff --git a/src/entity.h b/src/entity.h index 1ab5b567..a99f6859 100644 --- a/src/entity.h +++ b/src/entity.h @@ -73,6 +73,27 @@ struct entity { struct entity_handle first; struct entity_handle last; + + + + + + + + /* TODO: Remove this (testing) */ + b32 colliding; + + + + + + + + + + + + /* ====================================================================== */ /* Activation */ diff --git a/src/game.c b/src/game.c index 38e4b733..e6237cda 100644 --- a/src/game.c +++ b/src/game.c @@ -177,6 +177,10 @@ INTERNAL void spawn_test_entities(void) e->sprite = sprite_tag_from_path(STR("res/graphics/box.ase")); + entity_enable_prop(e, ENTITY_PROP_PHYSICAL); + e->mass_unscaled = 70; + e->ground_friction = 1000; + entity_set_xform(e, XFORM_TRS(.t = pos, .s = size, .r = rot)); } @@ -195,6 +199,117 @@ INTERNAL void spawn_test_entities(void) } } +/* ========================== * + * Collision test + * ========================== */ + +INTERNAL struct v2 quad_furthest_point(struct quad quad, struct v2 dir) +{ + struct v2 furthest = quad.e[0]; + f32 furthest_dot = v2_dot(dir, furthest); + for (u32 i = 1; i < ARRAY_COUNT(quad.e); ++i) { + struct v2 p = quad.e[i]; + f32 dot = v2_dot(dir, p); + if (dot > furthest_dot) { + furthest = p; + furthest_dot = dot; + } + } + return furthest; +} + +INTERNAL struct v2 quad_support_point(struct quad q0, struct quad q1, struct v2 dir) +{ + return v2_sub(quad_furthest_point(q0, dir), quad_furthest_point(q1, v2_neg(dir))); +} + +#if 0 +INTERNAL struct v2 normal_towards_origin(struct v2 p0, struct v2 p1) +{ + struct v2 dir = v2_norm(v2_perp_cw(v2_sub(p1, p0))); +#if 1 + if (v2_dot(dir, p0) > 0) { + dir = v2_neg(dir); + } +#else + dir = v2_mul(dir, 1 - ((v2_dot(dir, p0) > 0) << 1)); +#endif + return dir; +} +#else +INTERNAL struct v2 normal_towards_point(struct v2 start, struct v2 end, struct v2 p) +{ + struct v2 dir = v2_norm(v2_perp_cw(v2_sub(end, start))); +#if 1 + if (v2_dot(dir, v2_sub(p, start)) < 0) { + dir = v2_neg(dir); + } +#else + dir = v2_mul(dir, 1 - ((v2_dot(dir, v2_sub(p, start)) > 0) << 1)); +#endif + return dir; +} +#endif + +INTERNAL b32 quad_collision_test(struct quad q0, struct quad q1) +{ + struct v2 q0_center = v2_div(v2_add(q0.p4, v2_add(q0.p3, v2_add(q0.p2, q0.p1))), 4); + struct v2 q1_center = v2_div(v2_add(q1.p4, v2_add(q1.p3, v2_add(q1.p2, q1.p1))), 4); + + /* Simplex */ + struct v2 s[3] = { 0 }; + u32 s_len = 0; + + /* Append first point to simplex */ + struct v2 dir = v2_norm(v2_sub(q1_center, q0_center)); + s[0] = quad_support_point(q0, q1, dir); + s_len = 1; + + dir = v2_norm(v2_neg(s[0])); /* Next point is towards origin */ + while (true) { + /* Determine support point */ + struct v2 p = quad_support_point(q0, q1, dir); + if (v2_dot(dir, p) < 0) { + /* Point did not cross origin */ + return false; + } + + if (s_len == 1) { + s[1] = p; + ++s_len; + } else if (s_len == 2) { + /* Line case */ + /* Next dir is line normal towards origin */ + s[2] = p; + ++s_len; + dir = normal_towards_point(s[0], s[1], V2(0, 0)); + } else { + s[0] = s[1]; + s[1] = s[2]; + s[2] = p; + + /* Triangle case */ + struct v2 a = s[2]; + struct v2 b = s[1]; + struct v2 c = s[0]; + + dir = v2_neg(normal_towards_point(a, b, c)); /* Normal dir of ab pointing away from c */ + if (v2_dot(dir, v2_neg(a)) >= 0) { + /* Point is in region ab, remove c from simplex */ + } else { + dir = v2_neg(normal_towards_point(a, c, b)); /* Normal dir of ac pointing away from b */ + if (v2_dot(dir, v2_neg(a)) >= 0) { + /* Point is in region ac, remove b from simplex */ + s[1] = s[0]; + } else { + /* Point must be in simplex */ + return true; + } + } + } + } +} + /* ========================== * * Update * ========================== */ @@ -576,7 +691,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) activate_now(force); } else { /* If velocity is below clamp_epsilon, stop entity movement. */ - f32 mass = ent->mass_unscaled * xform_get_determinant(xf); + f32 mass = ent->mass_unscaled * math_fabs(xform_get_determinant(xf)); struct entity *impulse = entity_alloc(ent); entity_enable_prop(impulse, ENTITY_PROP_IMPULSE); impulse->force = v2_mul(v2_neg(velocity), mass); @@ -587,36 +702,76 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) } /* ========================== * - * Simulate physics + * Integrate forces & impulses * ========================== */ - /* TODO: Run physics on top-level entities, and then calculate child xforms as part of physics step (rather than caching at each xform_get). */ for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; - if (entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) { - struct xform xf = entity_get_xform(ent); - f32 mass = ent->mass_unscaled * xform_get_determinant(xf); + if (!entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) continue; - /* Determine acceleration from forces and impulses */ - struct v2 acceleration = V2(0, 0); - for (struct entity *child = entity_from_handle(store, ent->first); child->valid; child = entity_from_handle(store, child->next)) { - if (entity_has_prop(child, ENTITY_PROP_IMPULSE)) { - acceleration = v2_add(acceleration, child->force); - entity_enable_prop(child, ENTITY_PROP_RELEASE); - } else if (entity_has_prop(child, ENTITY_PROP_FORCE)) { - acceleration = v2_add(acceleration, v2_mul(child->force, dt)); - entity_enable_prop(child, ENTITY_PROP_RELEASE); - } + struct xform xf = entity_get_xform(ent); + f32 mass = ent->mass_unscaled * math_fabs(xform_get_determinant(xf)); + + /* Determine acceleration from forces and impulses */ + struct v2 acceleration = V2(0, 0); + for (struct entity *child = entity_from_handle(store, ent->first); child->valid; child = entity_from_handle(store, child->next)) { + if (entity_has_prop(child, ENTITY_PROP_IMPULSE)) { + acceleration = v2_add(acceleration, child->force); + entity_enable_prop(child, ENTITY_PROP_RELEASE); + } else if (entity_has_prop(child, ENTITY_PROP_FORCE)) { + acceleration = v2_add(acceleration, v2_mul(child->force, dt)); + entity_enable_prop(child, ENTITY_PROP_RELEASE); } - acceleration = v2_div(acceleration, mass); - - /* Verlet integration */ - struct v2 tick_velocity = v2_sub(xf.og, ent->verlet_xform.og); - ent->verlet_xform = xf; - xf.og = v2_add(xf.og, v2_add(tick_velocity, v2_mul(acceleration, dt))); - entity_set_xform(ent, xf); } + acceleration = v2_div(acceleration, mass); + + /* Verlet integration */ + struct v2 tick_velocity = v2_sub(xf.og, ent->verlet_xform.og); + ent->verlet_xform = xf; + xf.og = v2_add(xf.og, v2_add(tick_velocity, v2_mul(acceleration, dt))); + entity_set_xform(ent, xf); + } + + /* ========================== * + * Collision + * ========================== */ + + for (u64 e0_index = 0; e0_index < store->reserved; ++e0_index) { + struct entity *e0 = &store->entities[e0_index]; + if (!(e0->valid && entity_has_prop(e0, ENTITY_PROP_ACTIVE))) continue; + if (!entity_has_prop(e0, ENTITY_PROP_PHYSICAL)) continue; + + struct xform e0_xf = entity_get_xform(e0); + struct quad e0_quad; + { + struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, e0->sprite); + struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("shape"), e0->animation_frame); + e0_quad = xform_mul_quad(e0_xf, quad_from_rect(slice.rect)); + } + + b32 colliding = false; + for (u64 e1_index = 0; e1_index < store->reserved; ++e1_index) { + struct entity *e1 = &store->entities[e1_index]; + if (e1 == e0) continue; + if (!(e1->valid && entity_has_prop(e1, ENTITY_PROP_ACTIVE))) continue; + if (!entity_has_prop(e1, ENTITY_PROP_PHYSICAL)) continue; + + struct xform e1_xf = entity_get_xform(e1); + struct quad e1_quad; + { + struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, e1->sprite); + struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, STR("shape"), e1->animation_frame); + e1_quad = xform_mul_quad(e1_xf, quad_from_rect(slice.rect)); + } + + if (quad_collision_test(e0_quad, e1_quad)) { + colliding = true; + break; + } + } + + e0->colliding = colliding; } /* ========================== * diff --git a/src/math.h b/src/math.h index f304a792..f2815e51 100644 --- a/src/math.h +++ b/src/math.h @@ -543,11 +543,18 @@ INLINE f32 v2_len_squared(struct v2 a) return a.x * a.x + a.y * a.y; } -INLINE struct v2 v2_perp(struct v2 a) +/* Clockwise perpendicular vector */ +INLINE struct v2 v2_perp_cw(struct v2 a) { return V2(-a.y, a.x); } +/* Counterclockwise perpendicular vector */ +INLINE struct v2 v2_perp_ccw(struct v2 a) +{ + return V2(a.y, -a.x); +} + INLINE struct v2 v2_norm(struct v2 a) { f32 l = v2_len_squared(a); @@ -1008,9 +1015,8 @@ INLINE struct quad quad_from_line(struct v2 start, struct v2 end, f32 thickness) { f32 width = thickness / 2.f; - struct v2 rel = v2_sub(end, start); - struct v2 dir = v2_norm(rel); - struct v2 dir_perp = v2_perp(dir); + struct v2 dir = v2_norm(v2_sub(end, start)); + struct v2 dir_perp = v2_perp_cw(dir); struct v2 left = v2_mul(dir_perp, -width); struct v2 right = v2_mul(dir_perp, width); diff --git a/src/user.c b/src/user.c index 309a28b9..c26081bb 100644 --- a/src/user.c +++ b/src/user.c @@ -912,6 +912,14 @@ INTERNAL void user_update(void) if (!sprite_tag_is_nil(ent->sprite)) { struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, sprite); + u32 quad_color = RGBA_32_F(1, 0, 0.5, 1); + u32 point_color = RGBA_32_F(1, 0, 0, 1); + u32 ray_color = RGBA_32_F(1, 0, 0.5, 1); + + if (ent->colliding) { + quad_color = RGBA_32_F(1, 1, 1, 1); + } + for (u64 i = 0; i < sheet->slice_groups_count; ++i) { struct sprite_sheet_slice_group *group = &sheet->slice_groups[i]; if (string_ends_with(group->name, STR(".ray"))) continue; @@ -926,16 +934,16 @@ INTERNAL void user_update(void) struct quad quad = quad_from_rect(slice.rect); quad = xform_mul_quad(sprite_xform, quad); quad = xform_mul_quad(G.world_view, quad); - draw_solid_quad_line(G.viewport_canvas, quad, 2, RGBA_32_F(1, 0, 0.5, 1)); + draw_solid_quad_line(G.viewport_canvas, quad, 2, quad_color); } - draw_solid_circle(G.viewport_canvas, center, 3, RGBA_32_F(1, 0, 0, 1), 20); + draw_solid_circle(G.viewport_canvas, center, 3, point_color, 20); if (slice.has_ray) { struct v2 ray = xform_basis_mul_v2(sprite_xform, slice.dir); ray = xform_basis_mul_v2(G.world_view, ray); ray = v2_mul(v2_norm(ray), 25); - draw_solid_arrow_ray(G.viewport_canvas, center, ray, 2, 10, RGBA_32_F(1, 0, 0.5, 1)); + draw_solid_arrow_ray(G.viewport_canvas, center, ray, 2, 10, ray_color); } } }