diff --git a/common/systems/src/phys.rs b/common/systems/src/phys.rs index 92f71509ff..8c85fb2558 100644 --- a/common/systems/src/phys.rs +++ b/common/systems/src/phys.rs @@ -791,9 +791,8 @@ impl<'a> PhysicsData<'a> { .terrain .contains_key(read.terrain.pos_key(pos.0.map(|e| e.floor() as i32))); - // Don't move if we're not in a loaded chunk - let pos_delta = if in_loaded_chunk { - vel.0 * read.dt.0 + let move_dir = if vel.0.x != 0.0 || vel.0.y != 0.0 { + Vec3::new(vel.0.x, vel.0.y, 0.0).normalized() } else { Vec3::zero() }; @@ -815,7 +814,19 @@ impl<'a> PhysicsData<'a> { // resolves these sort of things well anyway. // At the very least, we don't do things that result in glitchy // velocities or entirely broken position snapping. - let mut tgt_pos = pos.0 + pos_delta; + // Don't move if we're not in a loaded chunk + let (tgt_vel, mut tgt_pos) = if in_loaded_chunk { + acc_with_frict_tick(move_dir, vel.0, pos.0, read.dt.0 as f64, FricParams::default()) + } else { + (vel.0, pos.0) + }; + + vel.0 = tgt_vel; + + let pos_delta = tgt_pos - pos.0; + + + let was_on_ground = physics_state.on_ground.is_some(); let block_snap = @@ -1769,20 +1780,6 @@ fn box_voxel_collision + ReadVol>( physics_state.skating_active = true; vel.0 = Vec3::new(new_ground_speed.x, new_ground_speed.y, 0.0); } else { - let ground_fric = physics_state - .on_ground - .map(|b| b.get_friction()) - .unwrap_or(0.0); - let wall_fric = if physics_state.on_wall.is_some() && climbing { - FRIC_GROUND - } else { - 0.0 - }; - let fric = ground_fric.max(wall_fric); - if fric > 0.0 { - vel.0 *= (1.0 - fric.min(1.0) * fric_mod).powf(dt.0 * 60.0); - physics_state.ground_vel = ground_vel; - } physics_state.skating_active = false; } @@ -1813,6 +1810,161 @@ fn box_voxel_collision + ReadVol>( }); } + + +#[derive(Clone, Copy)] +struct FricParams { + friction_co: f32, + projected_area: f32, + density: f32, + mass: f32, + max_acc: f32, +} +impl Default for FricParams { + fn default() -> Self { + Self { + friction_co: 0.98_f32, + projected_area: 0.75_f32, + density: 1.225_f32, + mass: 1.0_f32, + max_acc: 9.2_f32, /* on ground the maximum speed you can get by walking is the speed + * of gravity */ + } + } +} +impl FricParams { + // https://en.wikipedia.org/wiki/Drag_(physics) + // Todo: old impl was (mass * acc.abs() / (friction_co * projected_area * 0.5 * + // density * mass )).sqrt(); + fn v_term(&self, acc: f32) -> f32 { + ((2.0 * self.mass * acc.abs()) / (self.density * self.projected_area * self.friction_co)) + .sqrt() + } +} +trait MathHelp { + fn coth(&self) -> Self; + fn acoth(&self) -> Self; +} +impl MathHelp for f32 { + // coth(x) = 1/tanh(x)` + fn coth(&self) -> Self { 1.0 / self.tanh() } + + // `acoth(x) = atanh(1/x)` + fn acoth(&self) -> Self { (1.0 / self).atanh() } +} + +/// ROLLING_FRICTION_FORCE + AIR_FRICTION_FORCE + TILT_FRICT_FORCE + ACCEL_FORCE +/// = TOTAL_FORCE +/// +/// TILT_FRICT_FORCE = 0.0 +/// TOTAL_FORCE = depends on char = const +/// ACCEL_FORCE = TOTAL_FORCE - ROLLING_FRICTION_FORCE - AIR_FRICTION_FORCE +/// ACCEL = ACCEL_FORCE / MASS +/// +/// ROLLING_FRICTION_FORCE => Indepent of vel +/// AIR_FRICTION_FORCE => propotional to velĀ² +/// +/// https://www.energie-lexikon.info/fahrwiderstand.html +/// https://www.energie-lexikon.info/reibung.html +/// https://sciencing.com/calculate-force-friction-6454395.html +/// https://www.leifiphysik.de/mechanik/reibung-und-fortbewegung +fn acc_with_frict_tick( + move_dir: Vec3, + vel: Vec3, + pos: Vec3, + dt: f64, + params: FricParams, +) -> (Vec3, Vec3) { + let acc = move_dir * params.max_acc; // btw: cant accelerate faster than gravity on foot + + // controller + // I know what you think, wtf, yep: https://math.stackexchange.com/questions/1929436/line-integral-of-force-of-air-resistanc + // basically an integral of the air resistance formula which scales with v^2 + // transformed with an ODE. + + // terminal velocity equals the maximum velocity that can be reached by acc + // alone + let vel_t = acc.map(|xyz| xyz.signum() * params.v_term(xyz)); + + // thanks to kilpkonn for figuring this out + // https://en.wikipedia.org/wiki/Drag_(physics) + // + // upper and lower are upper and lower bound for integral + let revert_fak = vel / vel_t; + + let (mut pos, mut vel) = (pos, vel); + for i in 0..2 { + let dt = dt as f32; + let (v, p) = { + let acc = acc[i]; + let vel = vel[i]; + let pos = pos[i]; + let vel_t = vel_t[i]; + let revert_fak = revert_fak[i]; + if acc.abs() < f32::EPSILON { + // https://www.wolframalpha.com/input?i=m%2F%28Cx+%2B+m%2FV%29+dx+integrate + let c = params.density * params.projected_area * params.friction_co; + let lower = params.mass * params.mass.ln() / c; + let upper = params.mass * (c * vel * dt + params.mass).ln() / c; + let pos = pos + (upper - lower); + let vel = params.mass + / (params.density * params.projected_area * params.friction_co * dt + + params.mass / vel); + (vel, pos) + } else if revert_fak <= 0.0 { + // Handle passing through 0 differently as the function changes + // https://www.wolframalpha.com/input?i=V*tan%28x*g%2FV+%2B+atan%28v%2FV%29%29+%3D+0+solve+for+x + let dt_to_zero = vel_t * (vel / vel_t).atan() / acc.abs(); + if dt_to_zero < dt { + // Step with only part of dt that is left after reaching 0 vel + let lower = vel_t.powi(2) * (vel / vel_t).atan().cos().ln() / acc; + let upper = -vel_t.powi(2) + * (acc * dt_to_zero / vel_t + (vel / vel_t).atan()).cos().ln() + / acc; + let pos = pos + (upper - lower); + let dt = dt - dt_to_zero; + // https://www.wolframalpha.com/input?i=V+*+tanh%28xg%2FV%29+dx+integrate + // lower bound is 0 + let pos = pos + vel_t.powi(2) * (acc * dt / vel_t).cosh().ln() / acc; + let vel = vel_t * (dt * acc / vel_t).tanh(); + (vel, pos) + } else { + // https://www.wolframalpha.com/input?i=V+*+tan%28xg%2FV+%2B+atan%28v%2FV%29%29+dx+integrate + let lower = -vel_t.powi(2) * (vel / vel_t).atan().cos().ln() / acc; + let upper = + -vel_t.powi(2) * (acc * dt / vel_t + (vel / vel_t).atan()).cos().ln() / acc; + let pos = pos + (upper - lower); + let vel = vel_t * (dt * acc / vel_t + (vel / vel_t).atan()).tan(); + (vel, pos) + } + } else if revert_fak >= 1.0 { + // https://www.wolframalpha.com/input?i=V+*+coth%28xg%2FV+%2B+acoth%28v%2FV%29%29+dx+integrate + let lower = (vel_t.powi(2) * (vel / vel_t).acoth().cosh().ln() + + (vel / vel_t).acoth().tanh().ln()) + / acc; + let upper = (vel_t.powi(2) + * (acc * dt / vel_t + (vel / vel_t).acoth()).cosh().ln() + + (acc * dt / vel_t + (vel / vel_t).acoth()).tanh().ln()) + / acc; + let pos = pos + (upper - lower); + let vel = vel_t * (dt * acc / vel_t + (vel / vel_t).acoth()).coth(); + (vel, pos) + } else { + // https://www.wolframalpha.com/input?i=V+*+tanh%28xg%2FV+%2B+atanh%28v%2FV%29%29+dx+integrate + let lower = vel_t.powi(2) * ((vel / vel_t).atanh()).cosh().ln() / acc; + let upper = + vel_t.powi(2) * (acc * dt / vel_t + (vel / vel_t).atanh()).cosh().ln() / acc; + let pos = pos + (upper - lower); + let vel = vel_t * (dt * acc / vel_t + (vel / vel_t).atanh()).tanh(); + (vel, pos) + } + }; + vel[i] = v; + pos[i] = p; + } + (vel, pos) +} + fn voxel_collider_bounding_sphere( voxel_collider: &VoxelCollider, pos: &Pos,