diff --git a/src/pp/pp.c b/src/pp/pp.c index 80d5a77b..70522e35 100644 --- a/src/pp/pp.c +++ b/src/pp/pp.c @@ -63,6 +63,16 @@ P_Key P_RandKey(void) 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 @@ -83,7 +93,6 @@ String P_NameFromTileKind(P_TileKind kind) return result; } - //////////////////////////////////////////////////////////// //~ 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)) { - // FIXME: Pull from freelist P_Ent *dst = world->first_free_ent; if (dst) { @@ -1340,8 +1348,737 @@ P_Frame *P_PushFrame(P_World *world, P_Frame *src_frame, i64 tick) //////////////////////////////////////////////////////////// //~ 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; - return result; + TempArena scratch = BeginScratchNoConflict(); + 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); } diff --git a/src/pp/pp.h b/src/pp/pp.h index 260b6471..99739435 100644 --- a/src/pp/pp.h +++ b/src/pp/pp.h @@ -84,6 +84,7 @@ Struct(P_Ent) //- Persistent data P_Key key; + u64 rand_seq; ////////////////////////////// //- Build data @@ -183,6 +184,7 @@ Struct(P_World) Arena *statics_arena; u64 seed; + RandState rand; P_Ent *first_free_ent; @@ -468,6 +470,7 @@ b32 P_IsFrameNil(P_Frame *frame); b32 P_MatchKey(P_Key a, P_Key b); P_Key P_RandKey(void); +u64 P_RandU64FromEnt(P_Ent *ent); #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 -P_Frame *P_StepWorld(P_World *world, P_Frame *prev_frame, P_CmdList cmds); +void P_StepFrame(P_Frame *frame, P_CmdList cmds); diff --git a/src/pp/pp_sim/pp_sim_core.c b/src/pp/pp_sim/pp_sim_core.c index de9fc616..7be8d0ea 100644 --- a/src/pp/pp_sim/pp_sim_core.c +++ b/src/pp/pp_sim/pp_sim_core.c @@ -71,8 +71,6 @@ void S_TickForever(WaveLaneCtx *lane) P_ClearFrames(world, I64Min, prev_world_frame->tick - 1); i64 frame_begin_ns = TimeNs(); - i64 sim_dt_ns = NsFromSeconds(1) / SIM_TICKS_PER_SECOND; - f64 sim_dt = SecondsFromNs(sim_dt_ns); ////////////////////////////// //- Pop commands @@ -208,35 +206,6 @@ void S_TickForever(WaveLaneCtx *lane) } } - ////////////////////////////// - //- Update double-buffered entity data - - for (P_Ent *ent = P_FirstEnt(world_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) - { - ent->prev_xf = ent->xf; - } - - ////////////////////////////// - //- Process save commands - - // FIXME: Only accept save command from local user - - // b32 should_save = 0; - // for (P_CmdNode *cmd_node = user_cmds.first; cmd_node; cmd_node = cmd_node->next) - // { - // P_Cmd *cmd = &cmd_node->cmd; - // if (cmd->kind == P_CmdKind_SaveWorld) - // { - // should_save = 1; - // break; - // } - // } - // if (should_save) - // { - // String path = Lit("..."); - // LogInfoF("Saving world to %F", FmtString(path)); - // } - ////////////////////////////// //- Process user edit commands @@ -245,10 +214,16 @@ void S_TickForever(WaveLaneCtx *lane) // FIXME: Only apply relevant cmds based on tick { + b32 should_save = 0; for (P_CmdNode *cmd_node = user_cmds.first; cmd_node; cmd_node = cmd_node->next) { P_Cmd *cmd = &cmd_node->cmd; b32 allow = 0; + if (cmd->kind == P_CmdKind_SaveWorld) + { + // FIXME: Only accept save from local user + should_save = 1; + } if (cmd->kind == P_CmdKind_ResetWorld) { // TODO: Real reset @@ -288,1029 +263,19 @@ void S_TickForever(WaveLaneCtx *lane) } } } - } - - ////////////////////////////// - //- Update ent controls - - // FIXME: Only apply relevant cmds based on tick - - for (P_Ent *ent = P_FirstEnt(world_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) - { - ent->fire_presses = 0; - } - - for (P_CmdNode *cmd_node = user_cmds.first; cmd_node; cmd_node = cmd_node->next) - { - P_Cmd cmd = cmd_node->cmd; - if (cmd.kind == P_CmdKind_Control) + // Save world + if (should_save) { - P_Ent *target = P_EntFromKey(world_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; - } + String path = Lit("..."); + LogInfoF("Saving world to %F", FmtString(path)); } } ////////////////////////////// - //- Spawn entities - - // { - // ////////////////////////////// - // //- Push bullets - - // for (P_Ent *ent = P_FirstEnt(world_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(world_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(frame_arena, P_Constraint, max_constraints); - - PERSIST i64 max_constraints = 4096; - PERSIST i64 constraints_count = 0; - PERSIST P_Constraint *constraints = 0; - if (!constraints) - { - constraints = PushStructs(frame_arena, P_Constraint, max_constraints); - } - - for (P_Ent *ent0 = P_FirstEnt(world_frame); !P_IsEntNil(ent0); ent0 = P_NextEnt(ent0)) - { - P_Shape shape0 = P_WorldShapeFromEnt(ent0); - for (P_Ent *ent1 = P_FirstEnt(world_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 = world_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(world_frame, constraint->ent0); - // // P_Ent *ent1 = P_EntFromKey(world_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); - // } - } - } - } - } - } - } - - - - - - - - - - // // TODO: Not like this - - // // i64 max_constraints = 4096; - // // i64 constraints_count = 0; - // // P_Constraint *constraints = PushStructs(frame_arena, P_Constraint, max_constraints); - - // PERSIST i64 max_constraints = 4096; - // PERSIST i64 constraints_count = 0; - // PERSIST P_Constraint *constraints = 0; - // if (!constraints) - // { - // constraints = PushStructs(frame_arena, P_Constraint, max_constraints); - // } - - // for (P_Ent *ent = P_FirstEnt(world_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) - // { - // if (ent->is_player) - // { - // Xform prev_xf = ent->prev_xf; - // Xform xf = ent->xf; - - // P_Shape local_shape = P_LocalShapeFromEnt(ent); - // P_Shape last_world_shape = P_MulXformShape(prev_xf, local_shape); - // P_Shape shape0 = P_WorldShapeFromEnt(ent); - - // // TODO: Real constraint data - - // Rng2 test_rect = Zi; - // test_rect.p0 = VEC2(-1, -1); - // test_rect.p1 = VEC2(1, 1); - // P_Shape shape1 = P_ShapeFromDesc( - // // .radius = 0.5, - // .radius = 2, - // .count = 4, - // .points[0] = VEC2(test_rect.p0.x, test_rect.p0.y), - // .points[1] = VEC2(test_rect.p1.x, test_rect.p0.y), - // .points[2] = VEC2(test_rect.p1.x, test_rect.p1.y), - // .points[3] = VEC2(test_rect.p0.x, test_rect.p1.y), - // ); - - // P_DebugDrawShape(shape1, Color_Orange); - - // // 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, ent->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 = world_frame->tick; - // constraint->normal = collision.collision_normal; - - // // TODO: Real masses - // f32 inv_m0 = 0.5; - // f32 inv_m1 = 0; - // f32 inv_i0 = 0.5; - // f32 inv_i1 = 0; - - // constraint->ent0 = ent->key; - // constraint->static_center1 = shape1.center_of_mass; - - // constraint->inv_m0 = inv_m0; - // constraint->inv_m1 = 0; - // constraint->inv_i0 = 0; - // constraint->inv_i1 = 0; - - // // 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(world_frame, constraint->ent0); - // P_Ent *ent1 = P_EntFromKey(world_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 + //- Step frame { - i64 constraint_idx = 0; - while (constraint_idx < constraints_count) - { - P_Constraint *constraint = &constraints[constraint_idx]; - b32 prune = 1; - if (constraint->last_touched_tick == world_frame->tick) - { - P_Ent *ent0 = P_EntFromKey(world_frame, constraint->ent0); - P_Ent *ent1 = P_EntFromKey(world_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(world_frame, constraint->ent0); - P_Ent *ent1 = P_EntFromKey(world_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(world_frame, constraint->ent0); - P_Ent *ent1 = P_EntFromKey(world_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(world_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(world_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(world_frame); !P_IsEntNil(firer); firer = P_NextEnt(firer)) - { - if (firer->fire_held) - // if (firer->fire_presses) - { - // i64 fire_delta_ns = world_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)) <= world_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(frame_arena, &bullets_to_spawn); - bullet->is_bullet = 1; - bullet->key = P_RandKey(); - - - // FIXME: Remove this - PERSIST RandState rand = Zi; - f32 rand_speed = RandF64FromState(&rand, -0.5, 0.5); - f32 rand_angle = RandF64FromState(&rand, -0.5, 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 = world_frame->time_ns; - } - } - } - P_SpawnEntsFromList(world_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(world_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(world_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; - } - } - } - - - - - - // { - // Struct(P_Bullet) - // { - // Vec2 start; - // Vec2 dir; - - // f32 speed; - // }; - - // PERSIST i64 bullets_count = 1; - // PERSIST P_Bullet *bullets = 0; - // if (!bullets) - // { - // bullets = PushStruct(PermArena(), P_Bullet); - // P_Bullet *bullet = &bullets[0]; - // bullet->start = VEC2(1, 0); - // bullet->dir = NormVec2(VEC2(1, -1)); - // bullet->speed = 1; - // } - - // for (i64 bullet_idx = 0; bullet_idx < bullets_count; ++bullet_idx) - // { - // P_Bullet *bullet = &bullets[bullet_idx]; - - // // Raycast - // for (P_Ent *ent = P_FirstEnt(world_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) - // { - // Xform xf = ent->xf; - // P_Shape world_shape = P_MulXformShape(xf, ent->local_shape); - - // if (ent == P_FirstEnt(world_frame)) - // { - // bullet->start = AddVec2(world_shape.centroid, Vec2WithLen(ent->look, world_shape.radius)); - // bullet->dir = NormVec2(ent->look); - // } - // else - // { - // P_RaycastResult raycast = P_RaycastShape(world_shape, bullet->start, bullet->dir); - // Vec2 isect = raycast.p; - - // if (raycast.is_intersecting) - // { - // P_DebugDrawPoint(isect, Color_Green); - // P_DebugDrawLine(isect, AddVec2(isect, MulVec2(raycast.normal, 0.5)), Color_White); - // } - // else - // { - // P_DebugDrawPoint(isect, Color_Purple); - // } - // } - // } - - // P_DebugDrawLine(bullet->start, AddVec2(bullet->start, Vec2WithLen(bullet->dir, 0.5)), Color_Red); - // } - // } - - - - - - // // Struct(P_Bullet) - // // { - // // Vec2 start; - // // Vec2 dir; - - // // f32 speed; - // // }; - - // // PERSIST i64 bullets_count = 1; - // // PERSIST P_Bullet *bullets = 0; - // // if (!bullets) - // // { - // // bullets = PushStruct(PermArena(), P_Bullet); - // // P_Bullet *bullet = &bullets[0]; - // // bullet->start = VEC2(1, 0); - // // bullet->dir = NormVec2(VEC2(1, -1)); - // // bullet->speed = 1; - // // } - - // // for (i64 bullet_idx = 0; bullet_idx < bullets_count; ++bullet_idx) - // // { - // // P_Bullet *bullet = &bullets[bullet_idx]; - - // // // Raycast - // // for (P_Ent *ent = P_FirstEnt(world_frame); !P_IsEntNil(ent); ent = P_NextEnt(ent)) - // // { - // // Xform xf = ent->xf; - // // P_Shape world_shape = P_MulXformShape(xf, ent->local_shape); - - // // if (ent == P_FirstEnt(world_frame)) - // // { - // // bullet->start = AddVec2(world_shape.centroid, Vec2WithLen(ent->look, world_shape.radius)); - // // bullet->dir = NormVec2(ent->look); - // // } - // // else - // // { - // // P_RaycastResult raycast = P_RaycastShape(world_shape, bullet->start, bullet->dir); - // // Vec2 isect = raycast.p; - - // // if (raycast.is_intersecting) - // // { - // // P_DebugDrawPoint(isect, Color_Green); - // // P_DebugDrawLine(isect, AddVec2(isect, MulVec2(raycast.normal, 0.5)), Color_White); - // // } - // // else - // // { - // // P_DebugDrawPoint(isect, Color_Purple); - // // } - // // } - // // } - - // // P_DebugDrawLine(bullet->start, AddVec2(bullet->start, Vec2WithLen(bullet->dir, 0.5)), Color_Red); - // // } - // } - - - - - - - ////////////////////////////// - //- Debug draw entities - - if (P_tl.debug_draw_enabled) - { - for (P_Ent *ent = P_FirstEnt(world_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); - } - } + P_StepFrame(world_frame, user_cmds); } ////////////////////////////// @@ -1427,7 +392,6 @@ void S_TickForever(WaveLaneCtx *lane) } i64 frame_end_ns = TimeNs(); - world_frame->time_ns += sim_dt_ns; ////////////////////////////// //- Sleep diff --git a/src/pp/pp_vis/pp_vis_core.c b/src/pp/pp_vis/pp_vis_core.c index 216a4fc3..4cde3a08 100644 --- a/src/pp/pp_vis/pp_vis_core.c +++ b/src/pp/pp_vis/pp_vis_core.c @@ -1574,7 +1574,7 @@ void V_TickForever(WaveLaneCtx *lane) V.dragging_window = 0; } - // TODO: Add window to free list + // TODO: Add window to freelist // TODO: Remove panel if windowless DllQueueRemoveNP(panel->first_window, panel->last_window, window, next_in_panel, prev_in_panel); panel->windows_count -= 1; @@ -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); - 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 { @@ -2665,21 +2666,8 @@ void V_TickForever(WaveLaneCtx *lane) } } 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 @@ -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); } } + ////////////////////////////// //- End frame