object dragging via mouse joint

This commit is contained in:
jacob 2024-10-30 10:16:09 -05:00
parent 8eadda9931
commit c8b48b9537
5 changed files with 186 additions and 42 deletions

View File

@ -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;
};

View File

@ -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();

View File

@ -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;

View File

@ -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 */
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
});
}

View File

@ -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,