begin moving simulation logic to shared predictable implementation
This commit is contained in:
parent
f5a9733525
commit
3ad4579bb9
747
src/pp/pp.c
747
src/pp/pp.c
@ -63,6 +63,16 @@ P_Key P_RandKey(void)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 P_RandU64FromEnt(P_Ent *ent)
|
||||||
|
{
|
||||||
|
u64 result = MixU64s(ent->key.v, ent->rand_seq);
|
||||||
|
if (!P_IsEntNil(ent))
|
||||||
|
{
|
||||||
|
ent->rand_seq += 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
//~ Tile helpers
|
//~ Tile helpers
|
||||||
|
|
||||||
@ -83,7 +93,6 @@ String P_NameFromTileKind(P_TileKind kind)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
//~ Shape helpers
|
//~ Shape helpers
|
||||||
|
|
||||||
@ -1317,7 +1326,6 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick)
|
|||||||
|
|
||||||
for (P_Ent *src = P_FirstEnt(src_frame); !P_IsEntNil(src); src = P_NextEnt(src))
|
for (P_Ent *src = P_FirstEnt(src_frame); !P_IsEntNil(src); src = P_NextEnt(src))
|
||||||
{
|
{
|
||||||
// FIXME: Pull from freelist
|
|
||||||
P_Ent *dst = world->first_free_ent;
|
P_Ent *dst = world->first_free_ent;
|
||||||
if (dst)
|
if (dst)
|
||||||
{
|
{
|
||||||
@ -1340,8 +1348,737 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick)
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
//~ Step
|
//~ Step
|
||||||
|
|
||||||
P_Frame *P_StepWorld(P_World *world, P_Frame *prev_frame, P_CmdList cmds)
|
void P_StepFrame(P_Frame *frame, P_CmdList cmds)
|
||||||
{
|
{
|
||||||
P_Frame *result = &P_NilFrame;
|
TempArena scratch = BeginScratchNoConflict();
|
||||||
return result;
|
P_World *world = frame->world;
|
||||||
|
P_Frame *prev_frame = frame->prev;
|
||||||
|
|
||||||
|
i64 sim_dt_ns = NsFromSeconds(1) / SIM_TICKS_PER_SECOND;
|
||||||
|
f64 sim_dt = SecondsFromNs(sim_dt_ns);
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Update double-buffered entity data
|
||||||
|
|
||||||
|
for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
|
||||||
|
{
|
||||||
|
ent->prev_xf = ent->xf;
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Update ent controls
|
||||||
|
|
||||||
|
// FIXME: Only apply relevant cmds based on tick
|
||||||
|
|
||||||
|
for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
|
||||||
|
{
|
||||||
|
ent->fire_presses = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (P_CmdNode *cmd_node = cmds.first; cmd_node; cmd_node = cmd_node->next)
|
||||||
|
{
|
||||||
|
P_Cmd cmd = cmd_node->cmd;
|
||||||
|
if (cmd.kind == P_CmdKind_Control)
|
||||||
|
{
|
||||||
|
P_Ent *target = P_EntFromKey(frame, cmd.target);
|
||||||
|
if (!P_IsEntNil(target))
|
||||||
|
{
|
||||||
|
target->move = ClampVec2Len(cmd.move, 1);
|
||||||
|
target->look = cmd.look;
|
||||||
|
target->fire_held = cmd.fire_held;
|
||||||
|
target->fire_presses += cmd.fire_presses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Spawn entities
|
||||||
|
|
||||||
|
// {
|
||||||
|
// //////////////////////////////
|
||||||
|
// //- Push bullets
|
||||||
|
|
||||||
|
// for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
|
||||||
|
// {
|
||||||
|
// if (ent->fire_held)
|
||||||
|
// {
|
||||||
|
// if (ent->has_weapon)
|
||||||
|
// {
|
||||||
|
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Integrate control forces
|
||||||
|
|
||||||
|
for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
|
||||||
|
{
|
||||||
|
// Xform xf = ent->xf;
|
||||||
|
// Xform desired_xf = xf;
|
||||||
|
// if (!IsVec2Zero(ent->look))
|
||||||
|
// {
|
||||||
|
// desired_xf = XformWithWorldRotation(xf, AngleFromVec2(ent->look));
|
||||||
|
// }
|
||||||
|
// f32 move_speed = TweakFloat("Player move speed", 6.5, 0, 20);
|
||||||
|
// desired_xf.og = AddVec2(xf.og, MulVec2(ent->move, move_speed * sim_dt));
|
||||||
|
|
||||||
|
// Vec2 pos_diff = SubVec2(desired_xf.og, xf.og);
|
||||||
|
// f32 angle_diff = UnwindAngleF32(RotationFromXform(desired_xf) - RotationFromXform(xf));
|
||||||
|
|
||||||
|
// ent->solved_v = pos_diff;
|
||||||
|
// ent->solved_w = angle_diff;
|
||||||
|
|
||||||
|
{
|
||||||
|
// f32 damp_vel = damp_force * sim_dt;
|
||||||
|
|
||||||
|
if (Vec2Len(ent->solved_v) > 0.001)
|
||||||
|
{
|
||||||
|
f32 damp_force = TweakFloat("Player damp force", 50, 0, 100);
|
||||||
|
Vec2 damp = MulVec2(NegVec2(ent->solved_v), damp_force * sim_dt);
|
||||||
|
ent->solved_v = AddVec2(ent->solved_v, damp);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ent->solved_v = VEC2(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
f32 move_force = TweakFloat("Player move force", 400, 0, 400);
|
||||||
|
f32 max_speed = TweakFloat("Player max speed", 10, 0, 20);
|
||||||
|
|
||||||
|
Vec2 new_velocity = ent->solved_v;
|
||||||
|
new_velocity = AddVec2(new_velocity, MulVec2(ent->move, move_force * sim_dt));
|
||||||
|
|
||||||
|
// if (Vec2Len(new_velocity) > max_speed)
|
||||||
|
// {
|
||||||
|
// new_velocity = Vec2WithLen(new_velocity, max_speed);
|
||||||
|
// }
|
||||||
|
|
||||||
|
ent->solved_v = new_velocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Generate player wall constraints
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Not like this
|
||||||
|
|
||||||
|
// i64 max_constraints = 4096;
|
||||||
|
// i64 constraints_count = 0;
|
||||||
|
// P_Constraint *constraints = PushStructs(scratch.arena, P_Constraint, max_constraints);
|
||||||
|
|
||||||
|
PERSIST i64 max_constraints = 4096;
|
||||||
|
PERSIST i64 constraints_count = 0;
|
||||||
|
PERSIST P_Constraint *constraints = 0;
|
||||||
|
if (!constraints)
|
||||||
|
{
|
||||||
|
constraints = PushStructs(scratch.arena, P_Constraint, max_constraints);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0))
|
||||||
|
{
|
||||||
|
P_Shape shape0 = P_WorldShapeFromEnt(ent0);
|
||||||
|
for (P_Ent *ent1 = P_FirstEnt(frame); !P_IsEntNil(ent1); ent1 = P_NextEnt(ent1))
|
||||||
|
{
|
||||||
|
if (ent1 > ent0)
|
||||||
|
{
|
||||||
|
P_Shape shape1 = P_WorldShapeFromEnt(ent1);
|
||||||
|
|
||||||
|
// TODO: World query
|
||||||
|
P_CollisionResult collision = P_CollisionResultFromShapes(shape0, shape1);
|
||||||
|
if (collision.collision_points_count > 0)
|
||||||
|
{
|
||||||
|
// FIXME: Key lookup
|
||||||
|
P_Constraint *constraint = 0;
|
||||||
|
{
|
||||||
|
b32 match = 0;
|
||||||
|
for (i64 constraint_idx = 0; constraint_idx < constraints_count; ++constraint_idx)
|
||||||
|
{
|
||||||
|
constraint = &constraints[constraint_idx];
|
||||||
|
if (P_MatchKey(constraint->ent0, ent0->key) && P_MatchKey(constraint->ent1, ent1->key))
|
||||||
|
{
|
||||||
|
match = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!match)
|
||||||
|
{
|
||||||
|
if (constraints_count < max_constraints)
|
||||||
|
{
|
||||||
|
constraint = &constraints[constraints_count];
|
||||||
|
constraints_count += 1;
|
||||||
|
ZeroStruct(constraint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (constraint)
|
||||||
|
{
|
||||||
|
constraint->last_touched_tick = frame->tick;
|
||||||
|
constraint->normal = collision.collision_normal;
|
||||||
|
// constraint->friction = SqrtF32(ent0->friction * ent1->friction);
|
||||||
|
constraint->friction = 0;
|
||||||
|
|
||||||
|
// TODO: Real masses
|
||||||
|
f32 inv_m0 = 10;
|
||||||
|
f32 inv_m1 = 10;
|
||||||
|
f32 inv_i0 = 0;
|
||||||
|
f32 inv_i1 = 0;
|
||||||
|
|
||||||
|
constraint->ent0 = ent0->key;
|
||||||
|
constraint->ent1 = ent1->key;
|
||||||
|
// constraint->static_center1 = shape1.center_of_mass;
|
||||||
|
|
||||||
|
constraint->inv_m0 = inv_m0;
|
||||||
|
constraint->inv_m1 = inv_m1;
|
||||||
|
constraint->inv_i0 = inv_i0;
|
||||||
|
constraint->inv_i1 = inv_i1;
|
||||||
|
|
||||||
|
// Delete old contacts that are no longer present
|
||||||
|
for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx)
|
||||||
|
{
|
||||||
|
P_ContactPoint *contact = &constraint->points[contact_point_idx];
|
||||||
|
u32 id = contact->id;
|
||||||
|
b32 match = 0;
|
||||||
|
for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx)
|
||||||
|
{
|
||||||
|
if (collision.collision_points[collision_point_idx].id == id)
|
||||||
|
{
|
||||||
|
match = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!match)
|
||||||
|
{
|
||||||
|
// Delete contact by replacing with last in array
|
||||||
|
*contact = constraint->points[constraint->points_count - 1];
|
||||||
|
constraint->points_count -= 1;
|
||||||
|
contact_point_idx -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create / update contacts from collision
|
||||||
|
for (i32 collision_point_idx = 0; collision_point_idx < collision.collision_points_count; ++collision_point_idx)
|
||||||
|
{
|
||||||
|
P_CollisionPoint collision_point = collision.collision_points[collision_point_idx];
|
||||||
|
|
||||||
|
u32 id = collision_point.id;
|
||||||
|
P_ContactPoint *contact = 0;
|
||||||
|
{
|
||||||
|
for (i32 contact_point_idx = 0; contact_point_idx < constraint->points_count; ++contact_point_idx)
|
||||||
|
{
|
||||||
|
P_ContactPoint *tmp = &constraint->points[contact_point_idx];
|
||||||
|
if (tmp->id == id)
|
||||||
|
{
|
||||||
|
contact = tmp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!contact)
|
||||||
|
{
|
||||||
|
contact = &constraint->points[constraint->points_count];
|
||||||
|
constraint->points_count += 1;
|
||||||
|
ZeroStruct(contact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contact->id = id;
|
||||||
|
|
||||||
|
Vec2 vcp0 = SubVec2(collision_point.p, shape0.center_of_mass);
|
||||||
|
Vec2 vcp1 = SubVec2(collision_point.p, shape1.center_of_mass);
|
||||||
|
|
||||||
|
contact->vcp0 = vcp0;
|
||||||
|
contact->vcp1 = vcp1;
|
||||||
|
contact->starting_separation = collision_point.separation;
|
||||||
|
|
||||||
|
// // Debug draw
|
||||||
|
// {
|
||||||
|
// // P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
|
||||||
|
// // P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
|
||||||
|
// Vec2 normal = constraint->normal;
|
||||||
|
// Vec2 center0 = Zi;
|
||||||
|
// Vec2 center1 = Zi;
|
||||||
|
// if (!P_IsEntNil(ent0)) center0 = P_WorldShapeFromEnt(ent0).center_of_mass;
|
||||||
|
// if (!P_IsEntNil(ent1)) center1 = P_WorldShapeFromEnt(ent1).center_of_mass;
|
||||||
|
// Vec2 p0 = AddVec2(center0, vcp0);
|
||||||
|
// Vec2 p1 = AddVec2(center1, vcp1);
|
||||||
|
// P_DebugDrawPoint(p0, Color_Cyan);
|
||||||
|
// P_DebugDrawLine(p0, AddVec2(p0, normal), Color_White);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Prune constraints
|
||||||
|
|
||||||
|
{
|
||||||
|
i64 constraint_idx = 0;
|
||||||
|
while (constraint_idx < constraints_count)
|
||||||
|
{
|
||||||
|
P_Constraint *constraint = &constraints[constraint_idx];
|
||||||
|
b32 prune = 1;
|
||||||
|
if (constraint->last_touched_tick == frame->tick)
|
||||||
|
{
|
||||||
|
P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
|
||||||
|
P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
|
||||||
|
if (!P_IsEntNil(ent0) && !P_IsEntNil(ent1))
|
||||||
|
{
|
||||||
|
prune = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prune)
|
||||||
|
{
|
||||||
|
// Prune by replacing with last constraint
|
||||||
|
// TODO: Investigate whether the reordering here can degrade stability
|
||||||
|
P_Constraint *last_constraint = &constraints[constraints_count - 1];
|
||||||
|
*constraint = *last_constraint;
|
||||||
|
constraints_count -= 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
constraint_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Run solver steps
|
||||||
|
|
||||||
|
i32 solver_steps_count = 4;
|
||||||
|
f32 solver_dt = sim_dt / solver_steps_count;
|
||||||
|
f32 contact_spring_hz = TweakFloat("Contact spring hz", 25, 5, 100);
|
||||||
|
f32 contact_spring_damp = TweakFloat("Contact spring damp", 10, 5, 100);
|
||||||
|
for (i32 solver_step_idx = 0; solver_step_idx < solver_steps_count; ++solver_step_idx)
|
||||||
|
{
|
||||||
|
//////////////////////////////
|
||||||
|
//- Prepare constraints
|
||||||
|
|
||||||
|
for (i64 constraint_idx = 0; constraint_idx < constraints_count; ++constraint_idx)
|
||||||
|
{
|
||||||
|
P_Constraint *constraint = &constraints[constraint_idx];
|
||||||
|
Vec2 normal = constraint->normal;
|
||||||
|
Vec2 tangent = PerpVec2(normal);
|
||||||
|
f32 inv_m0 = constraint->inv_m0;
|
||||||
|
f32 inv_m1 = constraint->inv_m1;
|
||||||
|
f32 inv_i0 = constraint->inv_i0;
|
||||||
|
f32 inv_i1 = constraint->inv_i1;
|
||||||
|
|
||||||
|
for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
|
||||||
|
{
|
||||||
|
P_ContactPoint *contact = &constraint->points[contact_idx];
|
||||||
|
Vec2 vcp0 = contact->vcp0;
|
||||||
|
Vec2 vcp1 = contact->vcp1;
|
||||||
|
|
||||||
|
// Compute normal mass
|
||||||
|
{
|
||||||
|
f32 vcp0_wedge = WedgeVec2(vcp0, normal);
|
||||||
|
f32 vcp1_wedge = WedgeVec2(vcp1, normal);
|
||||||
|
f32 k = (inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge);
|
||||||
|
contact->inv_normal_mass = k > 0.0f ? 1.0f / k : 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute tangent mass
|
||||||
|
{
|
||||||
|
f32 vcp0_wedge = WedgeVec2(vcp0, tangent);
|
||||||
|
f32 vcp1_wedge = WedgeVec2(vcp1, tangent);
|
||||||
|
f32 k = (inv_m0 + inv_m1) + (inv_i0 * vcp0_wedge * vcp0_wedge) + (inv_i1 * vcp1_wedge * vcp1_wedge);
|
||||||
|
contact->inv_tangent_mass = k > 0.0f ? 1.0f / k : 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Warm start constraints
|
||||||
|
|
||||||
|
for (i64 constraint_idx = 0; constraint_idx < constraints_count; ++constraint_idx)
|
||||||
|
{
|
||||||
|
P_Constraint *constraint = &constraints[constraint_idx];
|
||||||
|
|
||||||
|
P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
|
||||||
|
P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
|
||||||
|
|
||||||
|
Vec2 v0 = ent0->solved_v;
|
||||||
|
Vec2 v1 = ent1->solved_v;
|
||||||
|
f32 w0 = ent0->solved_w;
|
||||||
|
f32 w1 = ent1->solved_w;
|
||||||
|
|
||||||
|
Vec2 normal = constraint->normal;
|
||||||
|
Vec2 tangent = PerpVec2(normal);
|
||||||
|
for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
|
||||||
|
{
|
||||||
|
P_ContactPoint *contact = &constraint->points[contact_idx];
|
||||||
|
Vec2 vcp0 = contact->vcp0;
|
||||||
|
Vec2 vcp1 = contact->vcp1;
|
||||||
|
|
||||||
|
Vec2 impulse = AddVec2(MulVec2(normal, contact->solved_normal_impulse), MulVec2(tangent, contact->solved_tangent_impulse));
|
||||||
|
// impulse = MulVec2(impulse, inv_num_points);
|
||||||
|
|
||||||
|
v0 = SubVec2(v0, MulVec2(impulse, constraint->inv_m0));
|
||||||
|
v1 = AddVec2(v1, MulVec2(impulse, constraint->inv_m1));
|
||||||
|
w0 -= WedgeVec2(vcp0, impulse) * constraint->inv_i0;
|
||||||
|
w1 += WedgeVec2(vcp1, impulse) * constraint->inv_i1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!P_IsEntNil(ent0))
|
||||||
|
{
|
||||||
|
ent0->solved_v = v0;
|
||||||
|
ent0->solved_w = w0;
|
||||||
|
}
|
||||||
|
if (!P_IsEntNil(ent1))
|
||||||
|
{
|
||||||
|
ent1->solved_v = v1;
|
||||||
|
ent1->solved_w = w1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Solve constraints
|
||||||
|
|
||||||
|
for (i64 constraint_idx = 0; constraint_idx < constraints_count; ++constraint_idx)
|
||||||
|
{
|
||||||
|
P_Constraint *constraint = &constraints[constraint_idx];
|
||||||
|
|
||||||
|
P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
|
||||||
|
P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
|
||||||
|
|
||||||
|
f32 inv_m0 = constraint->inv_m0;
|
||||||
|
f32 inv_m1 = constraint->inv_m1;
|
||||||
|
f32 inv_i0 = constraint->inv_i0;
|
||||||
|
f32 inv_i1 = constraint->inv_i1;
|
||||||
|
Vec2 v0 = ent0->solved_v;
|
||||||
|
Vec2 v1 = ent1->solved_v;
|
||||||
|
f32 w0 = ent0->solved_w;
|
||||||
|
f32 w1 = ent1->solved_w;
|
||||||
|
|
||||||
|
Vec2 center0 = constraint->static_center0;
|
||||||
|
Vec2 center1 = constraint->static_center1;
|
||||||
|
if (!P_IsEntNil(ent0))
|
||||||
|
{
|
||||||
|
center0 = P_WorldShapeFromEnt(ent0).center_of_mass;
|
||||||
|
}
|
||||||
|
if (!P_IsEntNil(ent1))
|
||||||
|
{
|
||||||
|
center1 = P_WorldShapeFromEnt(ent1).center_of_mass;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal impulse
|
||||||
|
Vec2 normal = constraint->normal;
|
||||||
|
for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
|
||||||
|
{
|
||||||
|
P_ContactPoint *contact = &constraint->points[contact_idx];
|
||||||
|
Vec2 vcp0 = contact->vcp0;
|
||||||
|
Vec2 vcp1 = contact->vcp1;
|
||||||
|
Vec2 p0 = AddVec2(center0, vcp0);
|
||||||
|
Vec2 p1 = AddVec2(center1, vcp1);
|
||||||
|
f32 separation = DotVec2(SubVec2(p1, p0), normal) + contact->starting_separation;
|
||||||
|
|
||||||
|
f32 velocity_bias = 0.0;
|
||||||
|
f32 mass_scale = 1.0;
|
||||||
|
f32 impulse_scale = 0.0;
|
||||||
|
|
||||||
|
// TDOO: Do a relaxation pass without bias
|
||||||
|
b32 apply_bias = 1;
|
||||||
|
if (separation > 0.0)
|
||||||
|
{
|
||||||
|
// Speculative
|
||||||
|
velocity_bias = separation / solver_dt;
|
||||||
|
}
|
||||||
|
else if (apply_bias)
|
||||||
|
{
|
||||||
|
// Soft constraint
|
||||||
|
SoftSpring softness = MakeSpring(contact_spring_hz, contact_spring_damp, solver_dt);
|
||||||
|
// f32 pushout_velocity = constraint->pushout_velocity;
|
||||||
|
f32 pushout_velocity = 3.0;
|
||||||
|
mass_scale = softness.mass_scale;
|
||||||
|
impulse_scale = softness.impulse_scale;
|
||||||
|
velocity_bias = MaxF32(softness.bias_rate * separation, -pushout_velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 vel0 = AddVec2(v0, MulPerpVec2(vcp0, w0));
|
||||||
|
Vec2 vel1 = AddVec2(v1, MulPerpVec2(vcp1, w1));
|
||||||
|
Vec2 vrel = SubVec2(vel0, vel1);
|
||||||
|
|
||||||
|
f32 k = contact->inv_normal_mass;
|
||||||
|
|
||||||
|
// (to be applied along n)
|
||||||
|
f32 vn = DotVec2(vrel, normal);
|
||||||
|
f32 j = ((k * mass_scale) * (vn - velocity_bias)) - (contact->solved_normal_impulse * impulse_scale);
|
||||||
|
|
||||||
|
f32 old_impulse = contact->solved_normal_impulse;
|
||||||
|
f32 new_impulse = MaxF32(old_impulse + j, 0);
|
||||||
|
f32 delta = new_impulse - old_impulse;
|
||||||
|
contact->solved_normal_impulse = new_impulse;
|
||||||
|
|
||||||
|
Vec2 impulse = MulVec2(normal, delta);
|
||||||
|
v0 = SubVec2(v0, MulVec2(impulse, inv_m0));
|
||||||
|
v1 = AddVec2(v1, MulVec2(impulse, inv_m1));
|
||||||
|
w0 -= WedgeVec2(vcp0, impulse) * inv_i0;
|
||||||
|
w1 += WedgeVec2(vcp1, impulse) * inv_i1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tangent impulse
|
||||||
|
Vec2 tangent = PerpVec2(normal);
|
||||||
|
for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
|
||||||
|
{
|
||||||
|
P_ContactPoint *contact = &constraint->points[contact_idx];
|
||||||
|
Vec2 vcp0 = contact->vcp0;
|
||||||
|
Vec2 vcp1 = contact->vcp1;
|
||||||
|
|
||||||
|
Vec2 vel0 = AddVec2(v0, MulPerpVec2(vcp0, w0));
|
||||||
|
Vec2 vel1 = AddVec2(v1, MulPerpVec2(vcp1, w1));
|
||||||
|
Vec2 vrel = SubVec2(vel0, vel1);
|
||||||
|
|
||||||
|
f32 k = contact->inv_tangent_mass;
|
||||||
|
|
||||||
|
// (to be applied along t)
|
||||||
|
f32 vt = DotVec2(vrel, tangent);
|
||||||
|
f32 j = vt * k;
|
||||||
|
|
||||||
|
f32 max_friction = constraint->friction * contact->solved_normal_impulse;
|
||||||
|
f32 old_impulse = contact->solved_tangent_impulse;
|
||||||
|
f32 new_impulse = ClampF32(old_impulse + j, -max_friction, max_friction);
|
||||||
|
f32 delta = new_impulse - old_impulse;
|
||||||
|
contact->solved_tangent_impulse = new_impulse;
|
||||||
|
|
||||||
|
Vec2 impulse = MulVec2(tangent, delta);
|
||||||
|
v0 = SubVec2(v0, MulVec2(impulse, inv_m0));
|
||||||
|
v1 = AddVec2(v1, MulVec2(impulse, inv_m1));
|
||||||
|
w0 -= WedgeVec2(vcp0, impulse) * inv_i0;
|
||||||
|
w1 += WedgeVec2(vcp1, impulse) * inv_i1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!P_IsEntNil(ent0))
|
||||||
|
{
|
||||||
|
ent0->solved_v = v0;
|
||||||
|
ent0->solved_w = w0;
|
||||||
|
}
|
||||||
|
if (!P_IsEntNil(ent1))
|
||||||
|
{
|
||||||
|
ent1->solved_v = v1;
|
||||||
|
ent1->solved_w = w1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Integrate velocities
|
||||||
|
|
||||||
|
for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
|
||||||
|
{
|
||||||
|
Xform xf = ent->xf;
|
||||||
|
xf.og = AddVec2(xf.og, MulVec2(ent->solved_v, solver_dt));
|
||||||
|
xf = RotateXform(xf, ent->solved_w * solver_dt);
|
||||||
|
ent->xf = xf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Move bullets
|
||||||
|
|
||||||
|
for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
|
||||||
|
{
|
||||||
|
if (bullet->is_bullet)
|
||||||
|
{
|
||||||
|
Vec2 start = bullet->bullet_start;
|
||||||
|
Vec2 end = bullet->bullet_end;
|
||||||
|
Vec2 vel = SubVec2(end, start);
|
||||||
|
bullet->bullet_start = end;
|
||||||
|
bullet->bullet_end = AddVec2(end, vel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Spawn new bullets
|
||||||
|
|
||||||
|
// TODO: Remove this
|
||||||
|
|
||||||
|
{
|
||||||
|
P_EntList bullets_to_spawn = Zi;
|
||||||
|
for (P_Ent *firer = P_FirstEnt(frame); !P_IsEntNil(firer); firer = P_NextEnt(firer))
|
||||||
|
{
|
||||||
|
if (firer->fire_held)
|
||||||
|
// if (firer->fire_presses)
|
||||||
|
{
|
||||||
|
// i64 fire_delta_ns = frame->time_ns - firer->last_fire_ns;
|
||||||
|
|
||||||
|
// i64 single_bullet_delta_ns = NsFromSeconds(1) / firer->fire_rate;
|
||||||
|
|
||||||
|
// i64 tick_bullets_count = sim_dt * firer->fire_rate;
|
||||||
|
|
||||||
|
f32 fire_rate = 50;
|
||||||
|
f32 bullets_per_fire = 1;
|
||||||
|
// f32 spread = Tau * 0.05;
|
||||||
|
f32 spread = Tau * 0.01;
|
||||||
|
f32 tweak_speed = TweakFloat("Bullet speed", 100, 1, 100);
|
||||||
|
|
||||||
|
b32 can_fire = (firer->last_fire_ns + NsFromSeconds(1.0 / fire_rate)) <= frame->time_ns;
|
||||||
|
if (can_fire)
|
||||||
|
{
|
||||||
|
i64 tick_bullets_count = bullets_per_fire;
|
||||||
|
if (tick_bullets_count > 0)
|
||||||
|
{
|
||||||
|
P_Shape firer_world_shape = P_WorldShapeFromEnt(firer);
|
||||||
|
|
||||||
|
Vec2 pos = P_EdgePointFromShape(firer_world_shape, firer->look);
|
||||||
|
|
||||||
|
for (i64 bullet_idx = 0; bullet_idx < tick_bullets_count; ++bullet_idx)
|
||||||
|
{
|
||||||
|
P_Ent *bullet = P_PushTempEnt(scratch.arena, &bullets_to_spawn);
|
||||||
|
bullet->is_bullet = 1;
|
||||||
|
bullet->key = P_RandKey();
|
||||||
|
|
||||||
|
f32 rand_speed = ((f32)P_RandU64FromEnt(firer) / (f32)0xFFFFFFFFFFFFFFFFull) - 0.5;
|
||||||
|
f32 rand_angle = ((f32)P_RandU64FromEnt(firer) / (f32)0xFFFFFFFFFFFFFFFFull) - 0.5;
|
||||||
|
|
||||||
|
f32 speed = tweak_speed * sim_dt;
|
||||||
|
f32 angle = AngleFromVec2(firer->look);
|
||||||
|
|
||||||
|
speed += (speed * 0.5) * rand_speed;
|
||||||
|
angle += rand_angle * spread;
|
||||||
|
|
||||||
|
Vec2 dir = Vec2FromAngle(angle);
|
||||||
|
|
||||||
|
bullet->bullet_start = pos;
|
||||||
|
bullet->bullet_end = AddVec2(bullet->bullet_start, MulVec2(dir, speed));
|
||||||
|
bullet->bullet_firer = firer->key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
firer->last_fire_ns = frame->time_ns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
P_SpawnEntsFromList(frame, bullets_to_spawn);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Update bullet hits
|
||||||
|
|
||||||
|
// TODO: Not like this
|
||||||
|
|
||||||
|
// TODO: Separate 'hits' from bullets, so that bullets can have multiple hits
|
||||||
|
|
||||||
|
for (P_Ent *bullet = P_FirstEnt(frame); !P_IsEntNil(bullet); bullet = P_NextEnt(bullet))
|
||||||
|
{
|
||||||
|
if (bullet->is_bullet)
|
||||||
|
{
|
||||||
|
bullet->has_hit = 0;
|
||||||
|
Vec2 ray_start = bullet->bullet_start;
|
||||||
|
Vec2 ray_end = bullet->bullet_end;
|
||||||
|
Vec2 ray_dir = SubVec2(ray_end, ray_start);
|
||||||
|
|
||||||
|
// TODO: Real raycast query
|
||||||
|
P_Ent *closest_victim = &P_NilEnt;
|
||||||
|
P_RaycastResult victim_raycast = Zi;
|
||||||
|
{
|
||||||
|
f32 closest_len_sq = Inf;
|
||||||
|
for (P_Ent *victim = P_FirstEnt(frame); !P_IsEntNil(victim); victim = P_NextEnt(victim))
|
||||||
|
{
|
||||||
|
if (victim->is_player && !P_MatchKey(victim->key, bullet->bullet_firer))
|
||||||
|
{
|
||||||
|
P_Shape victim_world_shape = P_WorldShapeFromEnt(victim);
|
||||||
|
|
||||||
|
P_RaycastResult entrance_raycast = P_RaycastShape(victim_world_shape, ray_start, ray_dir);
|
||||||
|
Vec2 entrance = entrance_raycast.p;
|
||||||
|
if (entrance_raycast.is_intersecting)
|
||||||
|
{
|
||||||
|
P_RaycastResult exit_raycast = P_RaycastShape(victim_world_shape, ray_start, NegVec2(ray_dir));
|
||||||
|
Vec2 exit = exit_raycast.p;
|
||||||
|
f32 da = DotVec2(ray_dir, SubVec2(entrance, ray_start));
|
||||||
|
f32 db = DotVec2(ray_dir, SubVec2(exit, ray_start));
|
||||||
|
if (db > 0 && (da <= Vec2LenSq(ray_dir) || da <= 0))
|
||||||
|
{
|
||||||
|
f32 len_sq = Vec2LenSq(SubVec2(entrance_raycast.p, ray_start));
|
||||||
|
if (len_sq < closest_len_sq)
|
||||||
|
{
|
||||||
|
closest_len_sq = len_sq;
|
||||||
|
closest_victim = victim;
|
||||||
|
victim_raycast = entrance_raycast;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!P_IsEntNil(closest_victim))
|
||||||
|
{
|
||||||
|
bullet->has_hit = 1;
|
||||||
|
bullet->hit_entry = victim_raycast.p;
|
||||||
|
bullet->hit_entry_normal = victim_raycast.normal;
|
||||||
|
// bullet->bullet_end = bullet->hit_entry;
|
||||||
|
bullet->exists = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rng2 bounds = Zi;
|
||||||
|
bounds.p0 = VEC2(-P_WorldPitch / 2, -P_WorldPitch / 2);
|
||||||
|
bounds.p1 = VEC2(P_WorldPitch / 2, P_WorldPitch / 2);
|
||||||
|
if (
|
||||||
|
bullet->bullet_start.x < bounds.p0.x || bullet->bullet_start.y < bounds.p0.y ||
|
||||||
|
bullet->bullet_start.x > bounds.p1.x || bullet->bullet_start.y > bounds.p1.y
|
||||||
|
)
|
||||||
|
{
|
||||||
|
bullet->exists = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Debug draw entities
|
||||||
|
|
||||||
|
if (P_tl.debug_draw_enabled)
|
||||||
|
{
|
||||||
|
for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
|
||||||
|
{
|
||||||
|
P_Shape world_shape = P_WorldShapeFromEnt(ent);
|
||||||
|
|
||||||
|
// Draw aabb
|
||||||
|
{
|
||||||
|
Vec4 color = VEC4(0.4, 0.2, 0.2, 1);
|
||||||
|
Rng2 bb = P_BoundingBoxFromShape(world_shape);
|
||||||
|
P_DebugDrawRect(bb, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw shape
|
||||||
|
{
|
||||||
|
// Vec4 color = Color_Cyan;
|
||||||
|
Vec4 color = VEC4(0.2, 0.4, 0.2, 1);
|
||||||
|
P_DebugDrawShape(world_shape, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw rot
|
||||||
|
{
|
||||||
|
Vec4 color = VEC4(0.8, 0.8, 0.8, 1);
|
||||||
|
Vec2 p0 = world_shape.centroid;
|
||||||
|
Vec2 p1 = P_EdgePointFromShape(world_shape, UpFromXform(ent->xf));
|
||||||
|
P_DebugDrawLine(p0, p1, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw look
|
||||||
|
{
|
||||||
|
Vec4 color = VEC4(0.4, 0.8, 0.4, 1);
|
||||||
|
Vec2 p0 = world_shape.centroid;
|
||||||
|
Vec2 p1 = P_EdgePointFromShape(world_shape, ent->look);
|
||||||
|
P_DebugDrawLine(p0, p1, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- End frame
|
||||||
|
|
||||||
|
frame->time_ns += sim_dt_ns;
|
||||||
|
|
||||||
|
EndScratch(scratch);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,6 +84,7 @@ Struct(P_Ent)
|
|||||||
//- Persistent data
|
//- Persistent data
|
||||||
|
|
||||||
P_Key key;
|
P_Key key;
|
||||||
|
u64 rand_seq;
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
//- Build data
|
//- Build data
|
||||||
@ -183,6 +184,7 @@ Struct(P_World)
|
|||||||
Arena *statics_arena;
|
Arena *statics_arena;
|
||||||
|
|
||||||
u64 seed;
|
u64 seed;
|
||||||
|
RandState rand;
|
||||||
|
|
||||||
P_Ent *first_free_ent;
|
P_Ent *first_free_ent;
|
||||||
|
|
||||||
@ -468,6 +470,7 @@ b32 P_IsFrameNil(P_Frame *frame);
|
|||||||
|
|
||||||
b32 P_MatchKey(P_Key a, P_Key b);
|
b32 P_MatchKey(P_Key a, P_Key b);
|
||||||
P_Key P_RandKey(void);
|
P_Key P_RandKey(void);
|
||||||
|
u64 P_RandU64FromEnt(P_Ent *ent);
|
||||||
|
|
||||||
#define P_FmtKey(key) FmtHandle((key).v)
|
#define P_FmtKey(key) FmtHandle((key).v)
|
||||||
|
|
||||||
@ -539,4 +542,4 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick);
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
//~ Step
|
//~ Step
|
||||||
|
|
||||||
P_Frame *P_StepWorld(P_World *world, P_Frame *prev_frame, P_CmdList cmds);
|
void P_StepFrame(P_Frame *frame, P_CmdList cmds);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -2639,7 +2639,8 @@ void V_TickForever(WaveLaneCtx *lane)
|
|||||||
|
|
||||||
frame->predict_to = sim_world->last_frame->tick + MaxF64(CeilF64(ping * SIM_TICKS_PER_SECOND), 1.0);
|
frame->predict_to = sim_world->last_frame->tick + MaxF64(CeilF64(ping * SIM_TICKS_PER_SECOND), 1.0);
|
||||||
|
|
||||||
if (frame->predict_to != prev_frame->predict_to)
|
b32 should_send_sim_cmds = frame->predict_to != prev_frame->predict_to;
|
||||||
|
if (should_send_sim_cmds)
|
||||||
{
|
{
|
||||||
// Push control cmd
|
// Push control cmd
|
||||||
{
|
{
|
||||||
@ -2665,20 +2666,7 @@ void V_TickForever(WaveLaneCtx *lane)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
UnlockTicketMutex(&P.sim_input_back_tm);
|
UnlockTicketMutex(&P.sim_input_back_tm);
|
||||||
if (V.sim_cmds.first)
|
|
||||||
{
|
|
||||||
V.sim_cmds.last->next = V.first_free_sim_cmd_node;
|
|
||||||
V.first_free_sim_cmd_node = V.sim_cmds.first;
|
|
||||||
}
|
}
|
||||||
V.sim_cmds.first = 0;
|
|
||||||
V.sim_cmds.last = 0;
|
|
||||||
V.sim_cmds.count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
//- Predict
|
//- Predict
|
||||||
@ -2771,6 +2759,22 @@ void V_TickForever(WaveLaneCtx *lane)
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
//- Reset queued sim cmds
|
||||||
|
|
||||||
|
if (should_send_sim_cmds)
|
||||||
|
{
|
||||||
|
if (V.sim_cmds.first)
|
||||||
|
{
|
||||||
|
V.sim_cmds.last->next = V.first_free_sim_cmd_node;
|
||||||
|
V.first_free_sim_cmd_node = V.sim_cmds.first;
|
||||||
|
}
|
||||||
|
V.sim_cmds.first = 0;
|
||||||
|
V.sim_cmds.last = 0;
|
||||||
|
V.sim_cmds.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -3528,6 +3532,7 @@ void V_TickForever(WaveLaneCtx *lane)
|
|||||||
SllStackPush(sim_world->first_free_ent, ent);
|
SllStackPush(sim_world->first_free_ent, ent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
//- End frame
|
//- End frame
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user