prediction testing

This commit is contained in:
jacob 2026-01-19 01:08:59 -06:00
parent a529ea8c5b
commit cdc876e71f
4 changed files with 449 additions and 164 deletions

View File

@ -1464,6 +1464,13 @@ void P_StepFrame(P_Frame *frame)
} }
} }
//////////////////////////////
//- Query ents
b32 is_predicting = P_tl.is_predicting;
P_Ent *local_player = P_EntFromKey(frame, P_tl.local_player);
P_Ent *local_guy = P_EntFromKey(frame, local_player->guy);
////////////////////////////// //////////////////////////////
//- Update double-buffered entity data //- Update double-buffered entity data
@ -1518,6 +1525,7 @@ void P_StepFrame(P_Frame *frame)
for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy)) for (P_Ent *guy = P_FirstEnt(frame); !P_IsEntNil(guy); guy = P_NextEnt(guy))
{ {
// if (guy->is_guy && (!is_predicting || guy == local_guy))
if (guy->is_guy) if (guy->is_guy)
{ {
P_Control control = guy->control; P_Control control = guy->control;
@ -1570,22 +1578,205 @@ void P_StepFrame(P_Frame *frame)
} }
////////////////////////////// //////////////////////////////
//- Generate guy-on-guy constraints //- Copy previous frame's constraints
// TODO: Not like this
frame->constraints_count = MinI64(prev_frame->constraints_count, frame->max_constraints); frame->constraints_count = MinI64(prev_frame->constraints_count, frame->max_constraints);
CopyStructs(frame->constraints, prev_frame->constraints, frame->constraints_count); CopyStructs(frame->constraints, prev_frame->constraints, frame->constraints_count);
i32 solver_steps_count = 4;
f32 solver_dt = sim_dt / solver_steps_count;
// Solid params
SoftSpring solid_spring = MakeSpring(TweakFloat("Contact spring hz", 25, 5, 100), TweakFloat("Contact spring damp", 10, 5, 100), solver_dt);
f32 solid_pushout_velocity = TweakFloat("Contact spring pushout", 3, 0, 50);
// Gentle params
f32 gentle_pushout_factor = TweakFloat("Gentle pushout factor", 10, 0, 50);
// //////////////////////////////
// //- Generate guy-on-guy constraints
// // TODO: Not like this
// for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0))
// {
// if (ent0->is_guy)
// {
// P_Shape shape0 = P_WorldShapeFromEnt(ent0);
// for (P_Ent *ent1 = P_FirstEnt(frame); !P_IsEntNil(ent1); ent1 = P_NextEnt(ent1))
// {
// if (ent1->is_guy && ent1->key.v > ent0->key.v)
// {
// 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 < frame->constraints_count; ++constraint_idx)
// {
// constraint = &frame->constraints[constraint_idx];
// if (P_MatchKey(constraint->ent0, ent0->key) && P_MatchKey(constraint->ent1, ent1->key))
// {
// match = 1;
// break;
// }
// }
// if (!match)
// {
// if (frame->constraints_count < frame->max_constraints)
// {
// constraint = &frame->constraints[frame->constraints_count];
// frame->constraints_count += 1;
// ZeroStruct(constraint);
// }
// }
// }
// if (constraint)
// {
// constraint->flags = P_ConstraintFlag_Gentle | P_ConstraintFlag_NoWarmStart;
// 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;
// // Treat non-predicted guys as infinite-mass
// // if (is_predicting && ent0 != local_guy)
// // {
// // inv_m0 = 0;
// // }
// // if (is_predicting && ent1 != local_guy)
// // {
// // inv_m1 = 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);
// // }
// }
// }
// }
// }
// }
// }
// }
//////////////////////////////
//- Generate solid constraints
// TODO: Not like this
for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0)) for (P_Ent *ent0 = P_FirstEnt(frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0))
{ {
@ -1594,7 +1785,7 @@ void P_StepFrame(P_Frame *frame)
P_Shape shape0 = P_WorldShapeFromEnt(ent0); P_Shape shape0 = P_WorldShapeFromEnt(ent0);
for (P_Ent *ent1 = P_FirstEnt(frame); !P_IsEntNil(ent1); ent1 = P_NextEnt(ent1)) for (P_Ent *ent1 = P_FirstEnt(frame); !P_IsEntNil(ent1); ent1 = P_NextEnt(ent1))
{ {
if (ent1->is_guy && ent1 > ent0) if (ent1->is_guy && ent1->key.v > ent0->key.v)
{ {
P_Shape shape1 = P_WorldShapeFromEnt(ent1); P_Shape shape1 = P_WorldShapeFromEnt(ent1);
@ -1627,6 +1818,7 @@ void P_StepFrame(P_Frame *frame)
} }
if (constraint) if (constraint)
{ {
constraint->flags = P_ConstraintFlag_Solid;
constraint->last_touched_tick = frame->tick; constraint->last_touched_tick = frame->tick;
constraint->normal = collision.collision_normal; constraint->normal = collision.collision_normal;
// constraint->friction = SqrtF32(ent0->friction * ent1->friction); // constraint->friction = SqrtF32(ent0->friction * ent1->friction);
@ -1638,6 +1830,17 @@ void P_StepFrame(P_Frame *frame)
f32 inv_i0 = 0; f32 inv_i0 = 0;
f32 inv_i1 = 0; f32 inv_i1 = 0;
// Treat non-predicted guys as infinite-mass
// if (is_predicting && ent0 != local_guy)
// {
// inv_m0 = 0;
// }
// if (is_predicting && ent1 != local_guy)
// {
// inv_m1 = 0;
// }
constraint->ent0 = ent0->key; constraint->ent0 = ent0->key;
constraint->ent1 = ent1->key; constraint->ent1 = ent1->key;
// constraint->static_center1 = shape1.center_of_mass; // constraint->static_center1 = shape1.center_of_mass;
@ -1703,20 +1906,20 @@ void P_StepFrame(P_Frame *frame)
contact->vcp1 = vcp1; contact->vcp1 = vcp1;
contact->starting_separation = collision_point.separation; contact->starting_separation = collision_point.separation;
// // Debug draw // Debug draw
// { {
// // P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0); // P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
// // P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1); // P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
// Vec2 normal = constraint->normal; Vec2 normal = constraint->normal;
// Vec2 center0 = Zi; Vec2 center0 = Zi;
// Vec2 center1 = Zi; Vec2 center1 = Zi;
// if (!P_IsEntNil(ent0)) center0 = P_WorldShapeFromEnt(ent0).center_of_mass; if (!P_IsEntNil(ent0)) center0 = P_WorldShapeFromEnt(ent0).center_of_mass;
// if (!P_IsEntNil(ent1)) center1 = P_WorldShapeFromEnt(ent1).center_of_mass; if (!P_IsEntNil(ent1)) center1 = P_WorldShapeFromEnt(ent1).center_of_mass;
// Vec2 p0 = AddVec2(center0, vcp0); Vec2 p0 = AddVec2(center0, vcp0);
// Vec2 p1 = AddVec2(center1, vcp1); Vec2 p1 = AddVec2(center1, vcp1);
// P_DebugDrawPoint(p0, Color_Cyan); P_DebugDrawPoint(p0, Color_Cyan);
// P_DebugDrawLine(p0, AddVec2(p0, normal), Color_White); P_DebugDrawLine(p0, AddVec2(p0, normal), Color_White);
// } }
} }
} }
} }
@ -1725,6 +1928,9 @@ void P_StepFrame(P_Frame *frame)
} }
} }
////////////////////////////// //////////////////////////////
//- Prune constraints //- Prune constraints
@ -1761,10 +1967,6 @@ void P_StepFrame(P_Frame *frame)
////////////////////////////// //////////////////////////////
//- Run solver steps //- 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) for (i32 solver_step_idx = 0; solver_step_idx < solver_steps_count; ++solver_step_idx)
{ {
////////////////////////////// //////////////////////////////
@ -1810,41 +2012,44 @@ void P_StepFrame(P_Frame *frame)
for (i64 constraint_idx = 0; constraint_idx < frame->constraints_count; ++constraint_idx) for (i64 constraint_idx = 0; constraint_idx < frame->constraints_count; ++constraint_idx)
{ {
P_Constraint *constraint = &frame->constraints[constraint_idx]; P_Constraint *constraint = &frame->constraints[constraint_idx];
if (!(constraint->flags & P_ConstraintFlag_NoWarmStart))
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]; P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
Vec2 vcp0 = contact->vcp0; P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
Vec2 vcp1 = contact->vcp1;
Vec2 impulse = AddVec2(MulVec2(normal, contact->solved_normal_impulse), MulVec2(tangent, contact->solved_tangent_impulse)); Vec2 v0 = ent0->solved_v;
// impulse = MulVec2(impulse, inv_num_points); Vec2 v1 = ent1->solved_v;
f32 w0 = ent0->solved_w;
f32 w1 = ent1->solved_w;
v0 = SubVec2(v0, MulVec2(impulse, constraint->inv_m0)); Vec2 normal = constraint->normal;
v1 = AddVec2(v1, MulVec2(impulse, constraint->inv_m1)); Vec2 tangent = PerpVec2(normal);
w0 -= WedgeVec2(vcp0, impulse) * constraint->inv_i0;
w1 += WedgeVec2(vcp1, impulse) * constraint->inv_i1;
}
if (!P_IsEntNil(ent0)) for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
{ {
ent0->solved_v = v0; P_ContactPoint *contact = &constraint->points[contact_idx];
ent0->solved_w = w0; Vec2 vcp0 = contact->vcp0;
} Vec2 vcp1 = contact->vcp1;
if (!P_IsEntNil(ent1))
{ Vec2 impulse = AddVec2(MulVec2(normal, contact->solved_normal_impulse), MulVec2(tangent, contact->solved_tangent_impulse));
ent1->solved_v = v1; // impulse = MulVec2(impulse, inv_num_points);
ent1->solved_w = w1;
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;
}
} }
} }
@ -1858,6 +2063,9 @@ void P_StepFrame(P_Frame *frame)
P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0); P_Ent *ent0 = P_EntFromKey(frame, constraint->ent0);
P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1); P_Ent *ent1 = P_EntFromKey(frame, constraint->ent1);
Vec2 normal = constraint->normal;
Vec2 tangent = PerpVec2(normal);
f32 inv_m0 = constraint->inv_m0; f32 inv_m0 = constraint->inv_m0;
f32 inv_m1 = constraint->inv_m1; f32 inv_m1 = constraint->inv_m1;
f32 inv_i0 = constraint->inv_i0; f32 inv_i0 = constraint->inv_i0;
@ -1878,113 +2086,157 @@ void P_StepFrame(P_Frame *frame)
center1 = P_WorldShapeFromEnt(ent1).center_of_mass; center1 = P_WorldShapeFromEnt(ent1).center_of_mass;
} }
// Normal impulse //- Solve solid constraint
Vec2 normal = constraint->normal; if (constraint->flags & P_ConstraintFlag_Solid)
for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
{ {
P_ContactPoint *contact = &constraint->points[contact_idx]; // Normal impulse
Vec2 vcp0 = contact->vcp0; for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
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;
// TODO: Do a relaxation pass without bias
b32 apply_bias = 1;
if (separation > 0.0)
{ {
// Speculative P_ContactPoint *contact = &constraint->points[contact_idx];
velocity_bias = separation / solver_dt; Vec2 vcp0 = contact->vcp0;
} Vec2 vcp1 = contact->vcp1;
else if (apply_bias) Vec2 p0 = AddVec2(center0, vcp0);
{ Vec2 p1 = AddVec2(center1, vcp1);
// Soft constraint f32 separation = DotVec2(SubVec2(p1, p0), normal) + contact->starting_separation;
SoftSpring softness = MakeSpring(contact_spring_hz, contact_spring_damp, solver_dt);
// f32 pushout_velocity = constraint->pushout_velocity; f32 velocity_bias = 0.0;
f32 pushout_velocity = 3.0; f32 mass_scale = 1.0;
mass_scale = softness.mass_scale; f32 impulse_scale = 0.0;
impulse_scale = softness.impulse_scale;
velocity_bias = MaxF32(softness.bias_rate * separation, -pushout_velocity); // TODO: 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 = solid_spring;
f32 pushout_velocity = solid_pushout_velocity;
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 normal
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;
} }
Vec2 vel0 = AddVec2(v0, MulPerpVec2(vcp0, w0)); // Tangent impulse
Vec2 vel1 = AddVec2(v1, MulPerpVec2(vcp1, w1)); for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
Vec2 vrel = SubVec2(vel0, vel1); {
P_ContactPoint *contact = &constraint->points[contact_idx];
Vec2 vcp0 = contact->vcp0;
Vec2 vcp1 = contact->vcp1;
f32 k = contact->inv_normal_mass; Vec2 vel0 = AddVec2(v0, MulPerpVec2(vcp0, w0));
Vec2 vel1 = AddVec2(v1, MulPerpVec2(vcp1, w1));
Vec2 vrel = SubVec2(vel0, vel1);
// (to be applied along n) f32 k = contact->inv_tangent_mass;
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; // To be applied along tangent
f32 new_impulse = MaxF32(old_impulse + j, 0); f32 vt = DotVec2(vrel, tangent);
f32 delta = new_impulse - old_impulse; f32 j = vt * k;
contact->solved_normal_impulse = new_impulse;
Vec2 impulse = MulVec2(normal, delta); f32 max_friction = constraint->friction * contact->solved_normal_impulse;
v0 = SubVec2(v0, MulVec2(impulse, inv_m0)); f32 old_impulse = contact->solved_tangent_impulse;
v1 = AddVec2(v1, MulVec2(impulse, inv_m1)); f32 new_impulse = ClampF32(old_impulse + j, -max_friction, max_friction);
w0 -= WedgeVec2(vcp0, impulse) * inv_i0; f32 delta = new_impulse - old_impulse;
w1 += WedgeVec2(vcp1, impulse) * inv_i1; 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;
}
} }
// Tangent impulse //- Solve gentle constraint
Vec2 tangent = PerpVec2(normal); if (constraint->flags & P_ConstraintFlag_Gentle)
for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
{ {
P_ContactPoint *contact = &constraint->points[contact_idx]; // Normal impulse
Vec2 vcp0 = contact->vcp0; for (i32 contact_idx = 0; contact_idx < constraint->points_count; ++contact_idx)
Vec2 vcp1 = contact->vcp1; {
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;
Vec2 vel0 = AddVec2(v0, MulPerpVec2(vcp0, w0)); f32 j = -separation * solver_dt * gentle_pushout_factor;
Vec2 vel1 = AddVec2(v1, MulPerpVec2(vcp1, w1));
Vec2 vrel = SubVec2(vel0, vel1);
f32 k = contact->inv_tangent_mass; 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;
// (to be applied along t) Vec2 impulse = MulVec2(normal, delta);
f32 vt = DotVec2(vrel, tangent); v0 = SubVec2(v0, MulVec2(impulse, inv_m0));
f32 j = vt * k; v1 = AddVec2(v1, MulVec2(impulse, inv_m1));
w0 -= WedgeVec2(vcp0, impulse) * inv_i0;
w1 += WedgeVec2(vcp1, impulse) * inv_i1;
}
f32 max_friction = constraint->friction * contact->solved_normal_impulse; if (!P_IsEntNil(ent0))
f32 old_impulse = contact->solved_tangent_impulse; {
f32 new_impulse = ClampF32(old_impulse + j, -max_friction, max_friction); ent0->solved_v = v0;
f32 delta = new_impulse - old_impulse; ent0->solved_w = w0;
contact->solved_tangent_impulse = new_impulse; }
if (!P_IsEntNil(ent1))
Vec2 impulse = MulVec2(tangent, delta); {
v0 = SubVec2(v0, MulVec2(impulse, inv_m0)); ent1->solved_v = v1;
v1 = AddVec2(v1, MulVec2(impulse, inv_m1)); ent1->solved_w = w1;
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 //- Integrate velocities
for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) for (P_Ent *ent = P_FirstEnt(frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
{ {
Xform xf = ent->xf; // if (!is_predicting || ent == local_guy)
xf.og = AddVec2(xf.og, MulVec2(ent->solved_v, solver_dt)); {
xf = RotateXform(xf, ent->solved_w * solver_dt); Xform xf = ent->xf;
ent->xf = xf; xf.og = AddVec2(xf.og, MulVec2(ent->solved_v, solver_dt));
xf = RotateXform(xf, ent->solved_w * solver_dt);
ent->xf = xf;
}
} }
} }

View File

@ -164,6 +164,14 @@ Struct(P_EntBin)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Constraint types //~ Constraint types
Enum(P_ConstraintFlag)
{
P_ConstraintFlag_None = 0,
P_ConstraintFlag_Solid = (1 << 0),
P_ConstraintFlag_Gentle = (1 << 1),
P_ConstraintFlag_NoWarmStart = (1 << 2),
};
Struct(P_ContactPoint) Struct(P_ContactPoint)
{ {
Vec2 vcp0; Vec2 vcp0;
@ -179,6 +187,8 @@ Struct(P_ContactPoint)
Struct(P_Constraint) Struct(P_Constraint)
{ {
P_ConstraintFlag flags;
i64 last_touched_tick; i64 last_touched_tick;
P_Key ent0; P_Key ent0;
P_Key ent1; P_Key ent1;
@ -388,6 +398,9 @@ Struct(P_Ctx)
Struct(P_ThreadLocalCtx) Struct(P_ThreadLocalCtx)
{ {
b32 is_predicting;
P_Key local_player;
//- Per-thread debug info //- Per-thread debug info
Arena *debug_arena; Arena *debug_arena;
b32 debug_draw_enabled; b32 debug_draw_enabled;

View File

@ -431,14 +431,23 @@ void S_TickForever(WaveLaneCtx *lane)
////////////////////////////// //////////////////////////////
//- Apply dummy controls //- Apply dummy controls
for (P_Ent *dummy = P_FirstEnt(world_frame); !P_IsEntNil(dummy); dummy = P_NextEnt(dummy))
{ {
if (dummy->is_dummy) b32 dummy_movement_enabled = TweakBool("Dummy movement enabled", 0);
for (P_Ent *dummy = P_FirstEnt(world_frame); !P_IsEntNil(dummy); dummy = P_NextEnt(dummy))
{ {
i64 alive_time_ns = time_ns - dummy->created_at_ns; if (dummy->is_dummy)
i64 frequency_ns = NsFromSeconds(0.1); {
if (dummy_movement_enabled)
dummy->control.move.x = SinF32((f64)alive_time_ns / (f64)frequency_ns); {
i64 alive_time_ns = time_ns - dummy->created_at_ns;
i64 frequency_ns = NsFromSeconds(0.1);
dummy->control.move.x = SinF32((f64)alive_time_ns / (f64)frequency_ns);
}
else
{
ZeroStruct(&dummy->control.move);
}
}
} }
} }

View File

@ -337,8 +337,9 @@ void V_TickForever(WaveLaneCtx *lane)
Arena *perm = PermArena(); Arena *perm = PermArena();
G_ArenaHandle gpu_perm = G_PermArena(); G_ArenaHandle gpu_perm = G_PermArena();
P_tl.debug_arena = AcquireArena(Gibi(64)); P_tl.debug_arena = AcquireArena(Gibi(64));
P_tl.debug_tint = VEC4(0, 0, 1, 0.75); P_tl.debug_tint = VEC4(0, 0.4, 0.9, 0.75);
P_tl.out_msgs_arena = AcquireArena(Gibi(64)); P_tl.out_msgs_arena = AcquireArena(Gibi(64));
P_tl.is_predicting = 1;
const i32 world_pitch = P_WorldPitch; const i32 world_pitch = P_WorldPitch;
const f32 zoom_rate = 1.50; const f32 zoom_rate = 1.50;
@ -366,11 +367,13 @@ void V_TickForever(WaveLaneCtx *lane)
i64 max_local_controls = SIM_MAX_PING * SIM_TICKS_PER_SECOND; i64 max_local_controls = SIM_MAX_PING * SIM_TICKS_PER_SECOND;
P_Control *local_controls = PushStructs(perm, P_Control, max_local_controls); P_Control *local_controls = PushStructs(perm, P_Control, max_local_controls);
i64 known_sim_tick = 0;
i64 remote_ack_received_at_ns = 0; i64 remote_ack_received_at_ns = 0;
i64 remote_ack = 0; i64 remote_ack = 0;
i64 prev_snapshot_sent_at_ns = 0; i64 prev_snapshot_sent_at_ns = 0;
i64 prev_known_sim_tick = 0;
i64 known_sim_tick = 0;
Vec2I32 tiles_dims = VEC2I32(P_TilesPitch, P_TilesPitch); Vec2I32 tiles_dims = VEC2I32(P_TilesPitch, P_TilesPitch);
Vec2I32 cells_dims = VEC2I32(V_CellsPerMeter * P_WorldPitch, V_CellsPerMeter * P_WorldPitch); Vec2I32 cells_dims = VEC2I32(V_CellsPerMeter * P_WorldPitch, V_CellsPerMeter * P_WorldPitch);
@ -2887,6 +2890,7 @@ void V_TickForever(WaveLaneCtx *lane)
if (src_frame->tick == src_tick) if (src_frame->tick == src_tick)
{ {
V.player_key = player_key; V.player_key = player_key;
P_tl.local_player = player_key;
P_Frame *dst_frame = P_FrameFromTick(sim_world, dst_tick); P_Frame *dst_frame = P_FrameFromTick(sim_world, dst_tick);
if (P_IsFrameNil(dst_frame)) if (P_IsFrameNil(dst_frame))
@ -3223,6 +3227,15 @@ void V_TickForever(WaveLaneCtx *lane)
i64 first_predict_tick = 0;
i64 last_predict_tick = 0;
{
// FIXME: Not like this
i64 max_predict_ticks = SIM_TICKS_PER_SECOND;
last_predict_tick = frame->predict_to;
first_predict_tick = known_sim_tick - 2;
first_predict_tick = MaxI64(first_predict_tick, last_predict_tick - max_predict_ticks);
}
// Predict // Predict
P_Frame *predict_frame = &P_NilFrame; P_Frame *predict_frame = &P_NilFrame;
@ -3234,22 +3247,12 @@ void V_TickForever(WaveLaneCtx *lane)
} }
predict_world->seed = sim_world->seed; predict_world->seed = sim_world->seed;
// FIXME: Not like this
i64 max_predict_ticks = SIM_TICKS_PER_SECOND;
i64 last_predict_tick = frame->predict_to;
i64 first_predict_tick = known_sim_tick - 2;
first_predict_tick = MaxI64(first_predict_tick, last_predict_tick - max_predict_ticks);
P_ClearFrames(predict_world, I64Min, first_predict_tick - 1); P_ClearFrames(predict_world, I64Min, first_predict_tick - 1);
predict_frame = P_PushFrame(predict_world, P_FrameFromTick(sim_world, first_predict_tick), first_predict_tick); predict_frame = P_PushFrame(predict_world, P_FrameFromTick(sim_world, first_predict_tick), first_predict_tick);
for (i64 predict_tick = first_predict_tick + 1; predict_tick <= last_predict_tick; ++predict_tick) for (i64 predict_tick = first_predict_tick + 1; predict_tick <= last_predict_tick; ++predict_tick)
{ {
predict_frame = P_PushFrame(predict_world, predict_world->last_frame, predict_tick); predict_frame = P_PushFrame(predict_world, predict_world->last_frame, predict_tick);
P_Ent *predict_player = P_EntFromKey(predict_frame, local_player->key); P_Ent *predict_player = P_EntFromKey(predict_frame, local_player->key);
if (!P_IsEntNil(predict_player)) if (!P_IsEntNil(predict_player))
{ {
@ -3259,10 +3262,18 @@ void V_TickForever(WaveLaneCtx *lane)
predict_player->control = *predict_control; predict_player->control = *predict_control;
} }
} }
// for (P_Ent *ent = P_FirstEnt(predict_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent))
// {
// if (ent != predict_player)
// {
// ZeroStruct(&ent->control);
// }
// }
P_tl.debug_draw_enabled = 0; b32 old_debug_draw = P_tl.debug_draw_enabled;
P_tl.debug_draw_enabled = TweakBool("Debug draw intermediate prediction steps", 0);
P_StepFrame(predict_frame); P_StepFrame(predict_frame);
P_tl.debug_draw_enabled = 1; P_tl.debug_draw_enabled = old_debug_draw;
} }
predict_frame = predict_world->last_frame; predict_frame = predict_world->last_frame;