diff --git a/src/entity.h b/src/entity.h index 697254f9..af9f3396 100644 --- a/src/entity.h +++ b/src/entity.h @@ -155,10 +155,10 @@ INLINE struct motor_joint motor_joint_from_def(struct motor_joint_def def) struct mouse_joint { struct entity_handle target; - - struct v2 point_local_target; - struct v2 point_local_mouse; - + struct v2 point_local_start; + struct v2 point_local_end; + struct math_spring_result linear_softness; + struct math_spring_result angular_softness; f32 max_force; f32 inv_m; @@ -168,9 +168,6 @@ struct mouse_joint { f32 angular_impulse; struct xform linear_mass_xf; - - struct math_spring_result linear_softness; - struct math_spring_result angular_softness; }; diff --git a/src/game.c b/src/game.c index 4ede1961..aa9b0faf 100644 --- a/src/game.c +++ b/src/game.c @@ -19,6 +19,9 @@ GLOBAL struct { b32 paused; struct sprite_scope *sprite_frame_scope; + /* For debugging */ + struct v2 user_cursor; + /* TODO: Remove this (testing) */ b32 extra_spawn; @@ -27,6 +30,12 @@ GLOBAL struct { struct arena game_cmds_arena; struct entity *root; + /* Constants */ + struct math_spring_result contact_softness; + struct math_spring_result mouse_joint_linear_softness; + struct math_spring_result mouse_joint_angular_softness; + f32 mouse_joint_max_force; + /* Ticks */ struct sys_mutex prev_tick_mutex; struct atomic_u64 prev_tick_id; @@ -49,6 +58,23 @@ struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr, (UNUSED)sheet_sr; (UNUSED)sound_sr; + /* Initialize constants */ + { + const f32 substep_dt = (1.f / ((f32)GAME_FPS * (f32)GAME_PHYSICS_SUBSTEPS)); + + const f32 contact_frequency = (1.f / substep_dt) / 8.f; + const f32 contact_damping_ratio = 10; + G.contact_softness = math_spring(contact_frequency, contact_damping_ratio, substep_dt); + + const f32 mouse_joint_linear_frequency = 5.0; + const f32 mouse_joint_linear_damping_ratio = 0.7; + const f32 mouse_joint_angular_frequency = 0.5f; + const f32 mouse_joint_angular_damping_ratio = 0.1f; + G.mouse_joint_max_force = 1000; + G.mouse_joint_linear_softness = math_spring(mouse_joint_linear_frequency, mouse_joint_linear_damping_ratio, substep_dt); + G.mouse_joint_angular_softness = math_spring(mouse_joint_angular_frequency, mouse_joint_angular_damping_ratio, substep_dt); + } + /* Initialize game cmd storage */ G.game_cmds_mutex = sys_mutex_alloc(); G.game_cmds_arena = arena_alloc(GIGABYTE(64)); @@ -178,9 +204,11 @@ INTERNAL void spawn_test_entities(f32 offset) //e->control_force = 500; e->control_force = 500; - e->control_force_max_speed = 4; + //e->control_force_max_speed = 4; e->control_torque = 500; + e->control_force_max_speed = 4; + entity_enable_prop(e, ENTITY_PROP_PHYSICAL); e->mass_unscaled = 10; @@ -191,7 +219,7 @@ INTERNAL void spawn_test_entities(f32 offset) player_ent = e; -#if 1 +#if 0 { struct entity *joint_ent = entity_alloc(root); entity_enable_prop(joint_ent, ENTITY_PROP_PHYSICAL); @@ -529,10 +557,7 @@ INTERNAL void create_contacts(void) contact = &constraint->points[constraint->num_points++]; MEMZERO_STRUCT(contact); contact->id = id; - f32 substep_dt = (1.f / ((f32)GAME_FPS * (f32)GAME_PHYSICS_SUBSTEPS)); - f32 damping_ratio = 10.0f; - f32 frequency = (1.f / substep_dt) / 8.f; - constraint->softness = math_spring(frequency, damping_ratio, substep_dt); + constraint->softness = G.contact_softness; constraint->pushout_velocity = 3.0f; } @@ -773,9 +798,6 @@ INTERNAL void solve_contacts(f32 dt, b32 apply_bias) f32 vt = v2_dot(vrel, tangent); f32 j = vt * k; - //f32 friction = 0.6f; - //f32 friction = 1.0f; - //f32 friction = F32_INFINITY; f32 max_friction = constraint->friction * point->normal_impulse; f32 old_impulse = point->tangent_impulse; f32 new_impulse = clamp_f32(old_impulse + j, -max_friction, max_friction); @@ -892,8 +914,8 @@ INTERNAL void warm_start_motor_joints(void) e0->linear_velocity = v2_sub(e0->linear_velocity, v2_mul(joint->linear_impulse, inv_m0)); e1->linear_velocity = v2_add(e1->linear_velocity, v2_mul(joint->linear_impulse, inv_m1)); - e0->angular_velocity -= (v2_wedge(joint->linear_impulse, vcp0) + joint->angular_impulse) * inv_i0; - e1->angular_velocity += (v2_wedge(joint->linear_impulse, vcp1) + joint->angular_impulse) * inv_i1; + e0->angular_velocity -= (v2_wedge(vcp0, joint->linear_impulse) + joint->angular_impulse) * inv_i0; + e1->angular_velocity += (v2_wedge(vcp1, joint->linear_impulse) + joint->angular_impulse) * inv_i1; } } @@ -961,8 +983,8 @@ INTERNAL void solve_motor_joints(f32 dt) v0 = v2_sub(v0, v2_mul(impulse, inv_m0)); v1 = v2_add(v1, v2_mul(impulse, inv_m1)); - w0 -= v2_wedge(impulse, vcp0) * inv_i0; - w1 += v2_wedge(impulse, vcp1) * inv_i1; + w0 -= v2_wedge(vcp0, impulse) * inv_i0; + w1 += v2_wedge(vcp1, impulse) * inv_i1; } e0->linear_velocity = v0; @@ -982,8 +1004,95 @@ INTERNAL void solve_motor_joints(f32 dt) * TESTING MOUSE JOINT * ========================== */ -INTERNAL void create_mouse_joints(void) +INTERNAL void create_mouse_joints(struct game_cmd_array game_cmds) { + struct entity_store *store = G.tick.entity_store; + struct entity *root = G.root; + + b32 start_dragging = false; + b32 stop_dragging = false; + for (u64 i = 0; i < game_cmds.count; ++i) { + struct game_cmd cmd = game_cmds.cmds[i]; + b32 start = cmd.state == GAME_CMD_STATE_START; + b32 stop = cmd.state == GAME_CMD_STATE_STOP; + switch (cmd.kind) { + case GAME_CMD_KIND_DRAG_OBJECT: + { + if (start) { + start_dragging = true; + } else if (stop) { + stop_dragging = true; + } + } break; + default: break; + } + } + + struct v2 cursor = G.user_cursor; + + /* TODO: Remove this */ + static struct entity_handle target_ent_handle = ZI; + static struct entity_handle joint_ent_handle = ZI; + + if (start_dragging) { + struct xform mouse_xf = xform_from_pos(cursor); + struct collider_shape mouse_shape = ZI; + mouse_shape.points[0] = V2(0, 0); + mouse_shape.count = 1; + + for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { + struct entity *ent = &store->entities[entity_index]; + if (!entity_is_valid_and_active(ent)) continue; + if (!entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) continue; + + struct collider_shape ent_collider = ent->local_collider; + if (ent_collider.count > 0) { + struct xform ent_xf = entity_get_xform(ent); + + /* TODO: Can just use boolean GJK */ + struct collider_collision_points_result res = collider_collision_points(&ent_collider, &mouse_shape, ent_xf, mouse_xf); + if (res.num_points > 0) { + target_ent_handle = ent->handle; + break; + } + } + } + } else if (stop_dragging) { + target_ent_handle = entity_nil_handle(); + } + + struct entity *target_ent = entity_from_handle(store, target_ent_handle); + struct entity *joint_ent = entity_from_handle(store, joint_ent_handle); + + if (entity_is_valid_and_active(target_ent)) { + if (!entity_is_valid_and_active(joint_ent)) { + joint_ent = entity_alloc(root); + joint_ent->mass_unscaled = F32_INFINITY; + joint_ent->inertia_unscaled = F32_INFINITY; + entity_enable_prop(joint_ent, ENTITY_PROP_PHYSICAL); + entity_enable_prop(joint_ent, ENTITY_PROP_MOUSE_JOINT); + entity_enable_prop(joint_ent, ENTITY_PROP_ACTIVE); + joint_ent_handle = joint_ent->handle; + } + struct mouse_joint *joint = &joint_ent->mouse_joint_data; + + struct xform xf = entity_get_xform(target_ent); + f32 mass = target_ent->mass_unscaled * math_fabs(xform_get_determinant(xf)); + + if (!entity_handle_eq(joint->target, target_ent_handle)) { + joint->point_local_start = xform_invert_mul_v2(xf, cursor); + joint->target = target_ent_handle; + } + joint->point_local_end = xform_invert_mul_v2(xf, cursor); + + joint->linear_softness = G.mouse_joint_linear_softness; + joint->angular_softness = G.mouse_joint_angular_softness; + joint->max_force = G.mouse_joint_max_force * mass; + } else { + if (entity_is_valid_and_active(joint_ent)) { + joint_ent->mouse_joint_data.target = entity_nil_handle(); + } + } } INTERNAL void prepare_mouse_joints(void) @@ -1011,7 +1120,7 @@ INTERNAL void prepare_mouse_joints(void) joint->inv_m = inv_m; joint->inv_i = inv_i; - struct v2 vcp = v2_sub(xform_mul_v2(xf, joint->point_local_target), xf.og); + struct v2 vcp = v2_sub(xform_mul_v2(xf, joint->point_local_start), xf.og); struct xform linear_mass_xf; linear_mass_xf.bx.x = inv_m + inv_i * vcp.y * vcp.y; @@ -1043,9 +1152,9 @@ INTERNAL void warm_start_mouse_joints(void) f32 inv_m = joint->inv_m; f32 inv_i = joint->inv_i; struct xform xf = entity_get_xform(ent); - struct v2 vcp = v2_sub(xform_mul_v2(xf, joint->point_local_target), xf.og); + struct v2 vcp = v2_sub(xform_mul_v2(xf, joint->point_local_start), xf.og); ent->linear_velocity = v2_add(ent->linear_velocity, v2_mul(joint->linear_impulse, inv_m)); - ent->angular_velocity += (v2_wedge(joint->linear_impulse, vcp) + joint->angular_impulse) * inv_i; + ent->angular_velocity += (v2_wedge(vcp, joint->linear_impulse) + joint->angular_impulse) * inv_i; } } } @@ -1083,11 +1192,11 @@ INTERNAL void solve_mouse_joints(f32 dt) struct xform xf = entity_get_xform(ent); - struct v2 point_target = xform_mul_v2(xf, joint->point_local_target); - struct v2 point_mouse = xform_mul_v2(xf, joint->point_local_mouse); + struct v2 point_start = xform_mul_v2(xf, joint->point_local_start); + struct v2 point_end = xform_mul_v2(xf, joint->point_local_end); - struct v2 vcp = v2_sub(point_target, xf.og); - struct v2 separation = v2_sub(point_mouse, point_target); + struct v2 vcp = v2_sub(point_start, xf.og); + struct v2 separation = v2_sub(point_start, point_end); struct math_spring_result softness = joint->linear_softness; struct v2 bias = v2_mul(separation, softness.bias_rate); @@ -1096,15 +1205,17 @@ INTERNAL void solve_mouse_joints(f32 dt) struct v2 vel = v2_add(v, v2_perp_mul(vcp, w)); struct v2 b = v2_mul(xform_basis_mul_v2(joint->linear_mass_xf, v2_add(vel, bias)), mass_scale); - struct v2 old_impulse = joint->linear_impulse; - struct v2 impulse = v2_add(v2_mul(joint->linear_impulse, impulse_scale), b); + struct v2 impulse = v2_sub(v2_mul(old_impulse, -impulse_scale), b); joint->linear_impulse = v2_clamp_len(v2_add(joint->linear_impulse, impulse), max_impulse); impulse = v2_sub(joint->linear_impulse, old_impulse); v = v2_add(v, v2_mul(impulse, inv_m)); - w += v2_wedge(impulse, vcp) * inv_i; + w += v2_wedge(vcp, impulse) * inv_i; } + + ent->linear_velocity = v; + ent->angular_velocity = w; } } } @@ -1273,6 +1384,12 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) struct game_cmd cmd = game_cmds.cmds[cmd_index]; switch (cmd.kind) { + /* Cursor */ + case GAME_CMD_KIND_CURSOR_MOVE: + { + G.user_cursor = cmd.cursor_pos; + } break; + /* Clear level */ case GAME_CMD_KIND_CLEAR_ALL: { @@ -1294,7 +1411,6 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) spawn_test_entities(0); #endif } break; - default: break; }; } @@ -1476,8 +1592,6 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) struct game_cmd cmd = game_cmds.cmds[i]; b32 start = cmd.state == GAME_CMD_STATE_START; b32 stop = cmd.state == GAME_CMD_STATE_STOP; - (UNUSED)start; - (UNUSED)stop; /* TODO: Combine movement from multiple inputs? E.G. a sudden * start and immediate stop cmd should still move the player a @@ -1486,7 +1600,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) case GAME_CMD_KIND_PLAYER_MOVE: { move = cmd.move_dir; - focus = v2_sub(cmd.aim_pos, entity_get_xform(ent).og); + focus = cmd.aim_dir; } break; case GAME_CMD_KIND_PLAYER_FIRE: @@ -1863,7 +1977,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) { integrate_velocities_from_forces(dt); create_contacts(); - create_mouse_joints(); + create_mouse_joints(game_cmds); prepare_contacts(); prepare_motor_joints(); diff --git a/src/game.h b/src/game.h index a48b4350..e13c0354 100644 --- a/src/game.h +++ b/src/game.h @@ -23,6 +23,8 @@ enum game_cmd_kind { GAME_CMD_KIND_SPAWN_TEST, GAME_CMD_KIND_PAUSE, GAME_CMD_KIND_STEP, + GAME_CMD_KIND_DRAG_OBJECT, + GAME_CMD_KIND_CURSOR_MOVE, GAME_CMD_KIND_COUNT }; @@ -33,7 +35,10 @@ struct game_cmd { /* GAME_CMD_KIND_PLAYER_MOVE */ struct v2 move_dir; - struct v2 aim_pos; + struct v2 aim_dir; + + /* GAME_CMD_KIND_CURSOR_MOVE */ + struct v2 cursor_pos; #if RTC u32 collider_gjk_steps; diff --git a/src/user.c b/src/user.c index d15b38d5..b012b6f3 100644 --- a/src/user.c +++ b/src/user.c @@ -87,6 +87,7 @@ GLOBAL READONLY enum user_bind_kind g_binds[SYS_BTN_COUNT] = { /* Testing */ + [SYS_BTN_M1] = USER_BIND_KIND_DEBUG_DRAG, [SYS_BTN_C] = USER_BIND_KIND_DEBUG_CLEAR, [SYS_BTN_V] = USER_BIND_KIND_DEBUG_SPAWN, [SYS_BTN_N] = USER_BIND_KIND_DEBUG_STEP, @@ -530,6 +531,12 @@ INTERNAL void user_update(void) active_camera = entity_find_first_match_all(store, (struct entity_prop_array) { .count = ARRAY_COUNT(props), .props = props }); } + struct entity *active_player; + { + enum entity_prop props[] = { ENTITY_PROP_PLAYER_CONTROLLED, ENTITY_PROP_ACTIVE }; + active_player = entity_find_first_match_all(store, (struct entity_prop_array) { .count = ARRAY_COUNT(props), .props = props }); + } + /* ========================== * * Read sys events * ========================== */ @@ -940,7 +947,7 @@ INTERNAL void user_update(void) debug_draw_xform(xf, color_x, color_y); } -#if 0 +#if 1 /* Draw focus arrow */ if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { struct sprite_sheet *sheet = sprite_sheet_from_tag_async(sprite_frame_scope, ent->sprite); @@ -1382,18 +1389,38 @@ INTERNAL void user_update(void) input_move_dir = xform_basis_invert_mul_v2(G.world_view, input_move_dir); /* Make move dir relative to world view */ input_move_dir = v2_mul(v2_norm(input_move_dir), move_speed); } - struct v2 input_aim_pos = G.world_cursor; + struct v2 input_aim_dir = v2_sub(G.world_cursor, entity_get_xform(active_player).og); + + /* Queue cursor move cmd */ + queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) { + .kind = GAME_CMD_KIND_CURSOR_MOVE, + .cursor_pos = G.world_cursor + }); + + /* Queue player input cmd */ if (!G.debug_camera) { queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) { .kind = GAME_CMD_KIND_PLAYER_MOVE, .move_dir = input_move_dir, - .aim_pos = input_aim_pos + .aim_dir = input_aim_dir }); + } - /* Queue player fire cmd */ + /* Queue player fire cmd */ + i32 fire_presses = G.bind_states[USER_BIND_KIND_FIRE].num_presses - G.bind_states[USER_BIND_KIND_FIRE].num_releases; + if (!G.debug_camera && fire_presses) { queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) { .kind = GAME_CMD_KIND_PLAYER_FIRE, - .state = (G.bind_states[USER_BIND_KIND_FIRE].num_presses > 0 || G.bind_states[USER_BIND_KIND_FIRE].is_held) ? GAME_CMD_STATE_START : GAME_CMD_STATE_STOP + .state = fire_presses > 0 ? GAME_CMD_STATE_START : GAME_CMD_STATE_STOP + }); + } + + /* Queue physics drag cmd */ + i32 drag_presses = G.bind_states[USER_BIND_KIND_DEBUG_DRAG].num_presses - G.bind_states[USER_BIND_KIND_DEBUG_DRAG].num_releases; + if (G.debug_camera && drag_presses) { + queue_game_cmd(scratch.arena, &cmd_list, (struct game_cmd) { + .kind = GAME_CMD_KIND_DRAG_OBJECT, + .state = drag_presses > 0 ? GAME_CMD_STATE_START : GAME_CMD_STATE_STOP }); } diff --git a/src/user.h b/src/user.h index e2e9b057..74bb2a29 100644 --- a/src/user.h +++ b/src/user.h @@ -29,6 +29,7 @@ enum user_bind_kind { USER_BIND_KIND_DEBUG_CAMERA, USER_BIND_KIND_DEBUG_PAUSE, USER_BIND_KIND_DEBUG_STEP, + USER_BIND_KIND_DEBUG_DRAG, USER_BIND_KIND_FULLSCREEN, USER_BIND_KIND_ZOOM_IN, USER_BIND_KIND_ZOOM_OUT,