object dragging via mouse joint
This commit is contained in:
parent
8eadda9931
commit
c8b48b9537
11
src/entity.h
11
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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
172
src/game.c
172
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();
|
||||
|
||||
@ -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;
|
||||
|
||||
35
src/user.c
35
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 */
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user