diff --git a/pomme-client/src/app/core.rs b/pomme-client/src/app/core.rs index 5b01314..8ff7d71 100644 --- a/pomme-client/src/app/core.rs +++ b/pomme-client/src/app/core.rs @@ -1,9 +1,11 @@ +use std::ops::Add; use std::sync::Arc; use std::time::Instant; use azalea_protocol::packets::game::{ ServerboundClientCommand, ServerboundGamePacket, s_client_command, }; +use glam::dvec3; use winit::keyboard::KeyCode; use winit::window::{CursorGrabMode, Window}; @@ -14,6 +16,7 @@ use crate::app::{POSITION_SEND_INTERVAL, POSITION_THRESHOLD_SQ}; use crate::assets::AssetIndex; use crate::dirs::DataDirs; use crate::discord::DiscordPresence; +use crate::entity::components::{LookDirection, Position, Velocity}; use crate::net::NetworkEvent; use crate::net::connection::ConnectionHandle; use crate::physics::movement; @@ -272,91 +275,77 @@ impl AppCore { .set_center(azalea_core::position::ChunkPos::new(x, z)); } NetworkEvent::PlayerPosition { change, relative } => { - let apply = |rel: bool, base: f64, c: f64| if rel { base + c } else { c }; - let apply_delta = |rel, base: f32, c: f64| apply(rel, base as f64, c) as f32; - let apply_rot = |rel, base_rad: f32, c_deg: f32| { - apply(rel, base_rad.to_degrees() as f64, c_deg as f64) as f32 - }; - let chunk_coord = |v: f64| (v.floor() as i32).div_euclid(16); - let to_az = |v: glam::Vec3| azalea_core::position::Vec3 { - x: v.x as f64, - y: v.y as f64, - z: v.z as f64, - }; - let to_glam = |v: azalea_core::position::Vec3| { - glam::Vec3::new(v.x as f32, v.y as f32, v.z as f32) - }; + fn apply_change>(base: T, condition: bool, change: T) -> T { + if condition { base + change } else { change } + } - let new_pos = azalea_core::position::Vec3 { - x: apply(relative.x, game.player.position.x as f64, change.pos.x), - y: apply(relative.y, game.player.position.y as f64, change.pos.y), - z: apply(relative.z, game.player.position.z as f64, change.pos.z), - }; - let new_look = azalea_entity::LookDirection::new( - apply_rot( + let new_position = Position::new( + apply_change(game.player.position.x, relative.x, change.pos.x), + apply_change(game.player.position.y, relative.y, change.pos.y), + apply_change(game.player.position.z, relative.z, change.pos.z), + ); + + let new_look_dir = LookDirection::new( + apply_change( + game.player.look_dir.y_rot_deg(), relative.y_rot, - game.player.yaw, change.look_direction.y_rot(), ), - apply_rot( + apply_change( + game.player.look_dir.x_rot_deg(), relative.x_rot, - game.player.pitch, change.look_direction.x_rot(), ), ); - let base_vel = if relative.rotate_delta { - let y_rot_delta = - (game.player.yaw.to_degrees() - new_look.y_rot()).to_radians(); - let x_rot_delta = - (game.player.pitch.to_degrees() - new_look.x_rot()).to_radians(); - to_glam( - to_az(game.player.velocity) - .x_rot(x_rot_delta) - .y_rot(y_rot_delta), + + let new_velocity = { + let mut new_velocity = game.player.velocity; + if relative.rotate_delta { + let x_rot_delta = + game.player.look_dir.x_rot_deg() - new_look_dir.x_rot_deg(); + let y_rot_delta = + game.player.look_dir.y_rot_deg() - new_look_dir.y_rot_deg(); + + new_velocity = new_velocity + .x_rot(x_rot_delta.to_radians()) + .y_rot(y_rot_delta.to_radians()); + } + Velocity::new( + apply_change(new_velocity.x, relative.delta_x, change.delta.x), + apply_change(new_velocity.y, relative.delta_y, change.delta.y), + apply_change(new_velocity.z, relative.delta_z, change.delta.z), ) - } else { - game.player.velocity }; - let new_vel = glam::Vec3::new( - apply_delta(relative.delta_x, base_vel.x, change.delta.x), - apply_delta(relative.delta_y, base_vel.y, change.delta.y), - apply_delta(relative.delta_z, base_vel.z, change.delta.z), - ); - game.player.position = to_glam(new_pos); - game.player.velocity = new_vel; - game.player.yaw = new_look.y_rot().to_radians(); - game.player.pitch = new_look.x_rot().to_radians(); - game.prev_player_pos = game.player.position; + game.player.position = new_position; + game.player.prev_position = game.player.position; + game.player.velocity = new_velocity; + game.player.look_dir = new_look_dir; + game.player.prev_look_dir = game.player.look_dir; + let to_chunk_coord = |v: f64| (v.floor() as i32).div_euclid(16); game.chunk_store .set_center(azalea_core::position::ChunkPos::new( - chunk_coord(new_pos.x), - chunk_coord(new_pos.z), + to_chunk_coord(new_position.x), + to_chunk_coord(new_position.z), )); - renderer.set_camera_position( - new_pos.x, - new_pos.y, - new_pos.z, - new_look.y_rot(), - new_look.x_rot(), - ); + renderer.reset_camera(new_position, new_look_dir); if !game.position_set { game.position_set = true; tracing::info!( "Player position set to ({:.1}, {:.1}, {:.1})", - new_pos.x, - new_pos.y, - new_pos.z + new_position.x, + new_position.y, + new_position.z ); } connection.packet_tx.send(ServerboundGamePacket::MovePlayerPosRot( azalea_protocol::packets::game::s_move_player_pos_rot::ServerboundMovePlayerPosRot { - pos: new_pos, - look_direction: new_look, + pos: new_position.into(), + look_direction: new_look_dir.into(), flags: azalea_protocol::common::movements::MoveFlags { on_ground: false, horizontal_collision: false, @@ -454,28 +443,24 @@ impl AppCore { NetworkEvent::EntitySpawned { id, entity_type, - x, - y, - z, - yaw, - pitch, - head_yaw, + position, velocity, + y_rot_deg, + x_rot_deg, + head_y_rot_deg, } => { if crate::entity::is_living_mob(&entity_type) { game.entity_store.spawn_living( id, entity_type, - glam::DVec3::new(x, y, z), - yaw, - pitch, - head_yaw, + position, + LookDirection::new(head_y_rot_deg, x_rot_deg), + y_rot_deg, + head_y_rot_deg, ); } if entity_type == azalea_registry::builtin::EntityKind::Item { - let pos = glam::DVec3::new(x, y, z); - let vel = glam::DVec3::new(velocity[0], velocity[1], velocity[2]); - game.item_entity_store.spawn_item(id, pos, vel); + game.item_entity_store.spawn_item(id, position, velocity); } } NetworkEvent::EntityMoved { id, dx, dy, dz } => { @@ -487,25 +472,24 @@ impl AppCore { dx, dy, dz, - yaw, - pitch, + y_rot_deg, + x_rot_deg, } => { game.entity_store.move_living_delta(id, dx, dy, dz); - game.entity_store.update_living_rotation(id, yaw, pitch); + game.entity_store + .update_living_rotation(id, y_rot_deg, x_rot_deg); game.item_entity_store.move_delta(id, dx, dy, dz); } NetworkEvent::EntityTeleported { id, - x, - y, - z, - yaw, - pitch, + position, + y_rot_deg, + x_rot_deg, } => { - game.entity_store.teleport_living(id, x, y, z); - game.entity_store.update_living_rotation(id, yaw, pitch); - game.item_entity_store - .teleport(id, glam::DVec3::new(x, y, z)); + game.entity_store.teleport_living(id, position); + game.entity_store + .update_living_rotation(id, y_rot_deg, x_rot_deg); + game.item_entity_store.teleport(id, position); } NetworkEvent::EntitiesRemoved { ids } => { for id in &ids { @@ -513,8 +497,11 @@ impl AppCore { } game.item_entity_store.remove(&ids); } - NetworkEvent::EntityHeadRotation { id, head_yaw } => { - game.entity_store.update_head_rotation(id, head_yaw); + NetworkEvent::EntityHeadRotation { + id, + head_y_rot_deg: head_y_rot, + } => { + game.entity_store.update_head_rotation(id, head_y_rot); } NetworkEvent::EntityItemData { id, @@ -549,12 +536,12 @@ impl AppCore { .entity_store .living .get(&collector_id) - .map(|e| e.position + glam::DVec3::new(0.0, 0.81, 0.0)) + .map(|e| e.position + dvec3(0.0, 0.81, 0.0)) .unwrap_or_else(|| { - glam::DVec3::new( - game.player.position.x as f64, - game.player.position.y as f64 + 0.81, - game.player.position.z as f64, + Position::new( + game.player.position.x, + game.player.position.y + 0.81, + game.player.position.z, ) }); game.item_entity_store.pickup(item_id, target_pos); @@ -701,19 +688,15 @@ impl AppCore { return; } - game.player.yaw = if renderer.is_first_person() { - renderer.camera_yaw() - } else { - renderer.camera_yaw() + std::f32::consts::PI - }; - game.player.pitch = renderer.camera_pitch(); + game.player.prev_look_dir = game.player.look_dir; + game.player.look_dir = renderer.camera_look_dir(); - game.prev_player_pos = game.player.position; + game.player.prev_position = game.player.position; movement::tick(&mut game.player, &self.input, &game.chunk_store); game.entity_store.tick_living(); - let dx = (game.player.position.x - game.prev_player_pos.x) as f64; - let dz = (game.player.position.z - game.prev_player_pos.z) as f64; + let dx = game.player.position.x - game.player.prev_position.x; + let dz = game.player.position.z - game.player.prev_position.z; crate::entity::update_walk_animation( dx, dz, @@ -723,20 +706,16 @@ impl AppCore { ); renderer.set_base_fov(self.menu.fov as f32); - renderer.update_fov(compute_fov_modifier(&game.player)); + renderer.update_fov_mod(compute_fov_modifier(&game.player)); self.send_input_packet(connection, game); self.send_sprint_command(connection, game); self.send_position_packet(connection, game); if !game.paused && !game.inventory_open && !game.chat.is_open() { - let eye_pos = game.player.position + glam::Vec3::new(0.0, 1.62, 0.0); - game.interaction.update_target( - eye_pos, - game.player.yaw, - game.player.pitch, - &game.chunk_store, - ); + let eye_pos = game.player.eye_pos(); + game.interaction + .update_target(eye_pos, game.player.look_dir, &game.chunk_store); let dirty = game.interaction.tick( &self.input, @@ -807,49 +786,41 @@ impl AppCore { use azalea_protocol::packets::game::*; let pos = game.player.position; - let yaw = game.player.yaw.to_degrees(); - let pitch = game.player.pitch.to_degrees(); + let look_dir = game.player.look_dir; - let dx = (pos.x - game.last_sent_pos.x) as f64; - let dy = (pos.y - game.last_sent_pos.y) as f64; - let dz = (pos.z - game.last_sent_pos.z) as f64; + let dx = pos.x - game.last_sent_pos.x; + let dy = pos.y - game.last_sent_pos.y; + let dz = pos.z - game.last_sent_pos.z; game.position_send_counter += 1; let pos_changed = dx * dx + dy * dy + dz * dz > POSITION_THRESHOLD_SQ || game.position_send_counter >= POSITION_SEND_INTERVAL; - let rot_changed = - (yaw - game.last_sent_yaw) != 0.0 || (pitch - game.last_sent_pitch) != 0.0; + let rot_changed = (look_dir.y_rot_deg() - game.last_sent_look_dir.y_rot_deg()) != 0.0 + || (look_dir.x_rot_deg() - game.last_sent_look_dir.x_rot_deg()) != 0.0; let flags = MoveFlags { on_ground: game.player.on_ground, horizontal_collision: game.player.horizontal_collision, }; - let net_pos = azalea_core::position::Vec3 { - x: pos.x as f64, - y: pos.y as f64, - z: pos.z as f64, - }; - let look = azalea_entity::LookDirection::new(yaw, pitch); - if pos_changed && rot_changed { sender.send(ServerboundGamePacket::MovePlayerPosRot( ServerboundMovePlayerPosRot { - pos: net_pos, - look_direction: look, + pos: pos.into(), + look_direction: look_dir.into(), flags, }, )); } else if pos_changed { sender.send(ServerboundGamePacket::MovePlayerPos( ServerboundMovePlayerPos { - pos: net_pos, + pos: pos.into(), flags, }, )); } else if rot_changed { sender.send(ServerboundGamePacket::MovePlayerRot( ServerboundMovePlayerRot { - look_direction: look, + look_direction: look_dir.into(), flags, }, )); @@ -866,8 +837,7 @@ impl AppCore { game.position_send_counter = 0; } if rot_changed { - game.last_sent_yaw = yaw; - game.last_sent_pitch = pitch; + game.last_sent_look_dir = look_dir; } game.last_sent_on_ground = game.player.on_ground; game.last_sent_horizontal_collision = game.player.horizontal_collision; diff --git a/pomme-client/src/app/phases/in_game.rs b/pomme-client/src/app/phases/in_game.rs index b792d2c..545fb7f 100644 --- a/pomme-client/src/app/phases/in_game.rs +++ b/pomme-client/src/app/phases/in_game.rs @@ -5,12 +5,14 @@ use std::time::Instant; use azalea_core::position::ChunkPos; use azalea_protocol::packets::game::{ServerboundClientInformation, ServerboundGamePacket}; use azalea_registry::builtin::EntityKind; +use glam::FloatExt as _; use crate::app::core::{AppCore, PlayerInputState}; use crate::app::phases::Gfx; use crate::app::{DEFAULT_RENDER_DISTANCE, TICK_RATE}; use crate::benchmark::{Benchmark, BenchmarkResult}; -use crate::entity::{EntityStore, ItemEntityStore}; +use crate::entity::components::{LookDirection, Position}; +use crate::entity::{EntityStore, ItemEntityStore, lerp_angle}; use crate::net::connection::ConnectionHandle; use crate::player::LocalPlayer; use crate::player::interaction::InteractionState; @@ -34,7 +36,6 @@ pub struct GameState { pub position_set: bool, pub player_loaded_sent: bool, pub player: LocalPlayer, - pub prev_player_pos: glam::Vec3, pub biome_climate: Arc>, pub player_walk_pos: f32, pub player_walk_speed: f32, @@ -55,9 +56,8 @@ pub struct GameState { pub show_debug: bool, pub show_chunk_borders: bool, pub last_sent_input: PlayerInputState, - pub last_sent_pos: glam::Vec3, - pub last_sent_yaw: f32, - pub last_sent_pitch: f32, + pub last_sent_pos: Position, + pub last_sent_look_dir: LookDirection, pub last_sent_on_ground: bool, pub last_sent_horizontal_collision: bool, pub was_sprinting: bool, @@ -89,7 +89,6 @@ impl GameState { server_simulation_distance: 0, item_entity_store: ItemEntityStore::new(), player: LocalPlayer::new(), - prev_player_pos: glam::Vec3::ZERO, biome_climate: Arc::new(HashMap::new()), player_walk_pos: 0.0, player_walk_speed: 0.0, @@ -110,9 +109,8 @@ impl GameState { show_debug: false, show_chunk_borders: false, last_sent_input: PlayerInputState::default(), - last_sent_pos: glam::Vec3::ZERO, - last_sent_yaw: 0.0, - last_sent_pitch: 0.0, + last_sent_pos: Position::default(), + last_sent_look_dir: LookDirection::default(), last_sent_on_ground: false, last_sent_horizontal_collision: false, was_sprinting: false, @@ -204,17 +202,14 @@ pub fn update_game( } } - let alpha = core.tick_accumulator / TICK_RATE; - let interp_pos = game.prev_player_pos.lerp(game.player.position, alpha); - let eye_pos = interp_pos + glam::Vec3::new(0.0, 1.62, 0.0); - let eye_pos_f64 = glam::DVec3::new(eye_pos.x as f64, eye_pos.y as f64, eye_pos.z as f64); + let partial_tick = core.tick_accumulator / TICK_RATE; if !game.paused && !game.inventory_open && !game.chat.is_open() { - let yaw = gfx.renderer.camera_yaw(); - let pitch = gfx.renderer.camera_pitch(); - - game.interaction - .update_target(eye_pos, yaw, pitch, &game.chunk_store); + game.interaction.update_target( + game.player.eye_pos(), + gfx.renderer.camera_look_dir(), + &game.chunk_store, + ); } let typed = core.input.drain_typed_chars(); @@ -229,11 +224,17 @@ pub fn update_game( let mut pause_action = PauseAction::None; let mut death_action = DeathAction::None; - let yaw = gfx.renderer.camera_yaw(); - let pitch = gfx.renderer.camera_pitch(); - gfx.renderer.sync_camera_to_player(eye_pos_f64, yaw, pitch); - gfx.renderer - .update_third_person_distance(eye_pos, &game.chunk_store); + gfx.renderer.sync_camera_pos( + game.player + .prev_eye_pos() + .lerp(game.player.eye_pos(), partial_tick as f64), + ); + gfx.renderer.update_third_person_distance( + game.player + .prev_eye_pos() + .lerp(game.player.eye_pos(), partial_tick as f64), + &game.chunk_store, + ); let sw = gfx.renderer.screen_width() as f32; let sh = gfx.renderer.screen_height() as f32; @@ -249,9 +250,9 @@ pub fn update_game( let debug = if game.show_debug { Some(hud::DebugInfo { fps: gfx.fps_counter.display_fps(), - position: game.player.position, - yaw: game.player.yaw, - pitch: game.player.pitch, + position: *game.player.position, + y_rot_deg: gfx.renderer.camera_look_dir().y_rot_deg(), + x_rot_deg: gfx.renderer.camera_look_dir().x_rot_deg(), target_block: game.interaction.target.map(|t| { let state = game.chunk_store @@ -468,65 +469,69 @@ pub fn update_game( gfx.renderer.menu_text_width(t, s) }); - let swing_progress = game - .interaction - .get_swing_progress(core.tick_accumulator / TICK_RATE); + let swing_progress = game.interaction.get_swing_progress(partial_tick); let destroy_info = game.interaction.destroy_stage(); - let alpha = core.tick_accumulator / TICK_RATE; let mut entity_renders: Vec = game .entity_store .living .iter() .map(|(&entity_id, e)| { - let pos = e.prev_position.lerp(e.position, alpha as f64); - let body_yaw = e.prev_body_yaw + (e.body_yaw - e.prev_body_yaw) * alpha; - let head_yaw = e.prev_head_yaw + (e.head_yaw - e.prev_head_yaw) * alpha; - let extras = entity_extras(entity_id, e, alpha); + let interp_pos = e.prev_position.lerp(e.position, partial_tick as f64); + let extras = entity_extras(entity_id, e, partial_tick); EntityRenderInfo { - x: pos.x, - y: pos.y, - z: pos.z, - yaw: body_yaw, - pitch: e.prev_pitch + (e.pitch - e.prev_pitch) * alpha, - head_yaw, + position: interp_pos, + head_y_rot_deg: lerp_angle(e.prev_head_y_rot_deg, e.head_y_rot_deg, partial_tick), + head_x_rot_deg: e + .prev_look_dir + .x_rot_deg() + .lerp(e.look_dir.x_rot_deg(), partial_tick), + body_y_rot_deg: lerp_angle(e.prev_body_y_rot_deg, e.body_y_rot_deg, partial_tick), is_baby: e.is_baby, walk_anim_pos: { let scale = if e.is_baby { 3.0 } else { 1.0 }; - (e.walk_anim_pos - e.walk_anim_speed * (1.0 - alpha)) * scale + (e.walk_anim_pos - e.walk_anim_speed * (1.0 - partial_tick)) * scale }, walk_anim_speed: (e.prev_walk_anim_speed - + (e.walk_anim_speed - e.prev_walk_anim_speed) * alpha) + + (e.walk_anim_speed - e.prev_walk_anim_speed) * partial_tick) .min(1.0), entity_kind: e.entity_type, variant_index: extras.variant_index, overlay_tints: extras.overlay_tints, head_y_offset: extras.head_y_offset, - head_x_rot_override: extras.head_x_rot_override, + head_x_rot_deg_override: extras.head_x_rot_deg_override, } }) .collect(); if !gfx.renderer.is_first_person() { - let cam_yaw_deg = -gfx.renderer.camera_yaw().to_degrees(); + let interp_pos = game + .player + .prev_position + .lerp(game.player.position, partial_tick as f64); + + let interp_y_rot_deg = lerp_angle( + game.player.prev_look_dir.y_rot_deg(), + game.player.look_dir.y_rot_deg(), + partial_tick, + ); + entity_renders.push(EntityRenderInfo { - x: interp_pos.x as f64, - y: interp_pos.y as f64, - z: interp_pos.z as f64, - yaw: cam_yaw_deg, - pitch: gfx.renderer.camera_pitch().to_degrees(), - head_yaw: cam_yaw_deg, + position: interp_pos, + head_y_rot_deg: interp_y_rot_deg, + head_x_rot_deg: gfx.renderer.camera_look_dir().x_rot_deg(), + body_y_rot_deg: interp_y_rot_deg, // TODO: proper body rotation affected by collisions is_baby: false, - walk_anim_pos: game.player_walk_pos - game.player_walk_speed * (1.0 - alpha), + walk_anim_pos: game.player_walk_pos - game.player_walk_speed * (1.0 - partial_tick), walk_anim_speed: (game.player_prev_walk_speed - + (game.player_walk_speed - game.player_prev_walk_speed) * alpha) + + (game.player_walk_speed - game.player_prev_walk_speed) * partial_tick) .min(1.0), entity_kind: EntityKind::Player, variant_index: 0, overlay_tints: [None, None], head_y_offset: 0.0, - head_x_rot_override: None, + head_x_rot_deg_override: None, }); } @@ -544,16 +549,11 @@ pub fn update_game( ); } - let cam_pos = glam::DVec3::new( - game.player.position.x as f64, - game.player.position.y as f64, - game.player.position.z as f64, - ); let partial_tick = core.tick_accumulator / TICK_RATE; let item_renders = build_item_render_infos( &game.item_entity_store, &game.chunk_store, - cam_pos, + *gfx.renderer.camera_pivot_position(), partial_tick, ); @@ -662,7 +662,7 @@ fn seeded_rand(state: &mut u32) -> f32 { ((*state >> 16) & 0x7FFF) as f32 / 0x7FFF as f32 } -fn get_entity_light(chunk_store: &ChunkStore, pos: glam::DVec3) -> f32 { +fn get_entity_light(chunk_store: &ChunkStore, pos: Position) -> f32 { use crate::renderer::chunk::mesher::LIGHT_TABLE; let bx = pos.x.floor() as i32; let by = pos.y.floor() as i32; @@ -766,14 +766,14 @@ struct EntityExtras { variant_index: u32, overlay_tints: [Option<[f32; 4]>; 2], head_y_offset: f32, - head_x_rot_override: Option, + head_x_rot_deg_override: Option, } const EMPTY_EXTRAS: EntityExtras = EntityExtras { variant_index: 0, overlay_tints: [None, None], head_y_offset: 0.0, - head_x_rot_override: None, + head_x_rot_deg_override: None, }; fn entity_extras(entity_id: i32, e: &crate::entity::LivingEntity, alpha: f32) -> EntityExtras { @@ -812,7 +812,7 @@ fn sheep_extras(entity_id: i32, e: &crate::entity::LivingEntity, alpha: f32) -> let (pos_scale, angle_scale) = sheep_eat_scales(e.eat_anim_tick, e.prev_eat_anim_tick, alpha); let age_scale = if e.is_baby { 0.5 } else { 1.0 }; let head_y_offset = pos_scale * 9.0 * age_scale; - let head_x_rot_override = if e.eat_anim_tick > 0 || e.prev_eat_anim_tick > 0 { + let head_x_rot_deg_override = if e.eat_anim_tick > 0 || e.prev_eat_anim_tick > 0 { Some(angle_scale) } else { None @@ -822,7 +822,7 @@ fn sheep_extras(entity_id: i32, e: &crate::entity::LivingEntity, alpha: f32) -> variant_index: 0, overlay_tints, head_y_offset, - head_x_rot_override, + head_x_rot_deg_override, } } diff --git a/pomme-client/src/entity/components.rs b/pomme-client/src/entity/components.rs new file mode 100644 index 0000000..d3e0dec --- /dev/null +++ b/pomme-client/src/entity/components.rs @@ -0,0 +1,221 @@ +use std::ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign}; + +use glam::{DVec3, Vec3, dvec3, vec3}; + +// -- POSITION -- + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Position(DVec3); + +impl Position { + pub fn new(x: f64, y: f64, z: f64) -> Self { + Self(dvec3(x, y, z)) + } + + /// Performs a linear interpolation between `self` and `rhs` based on the + /// value `s`. + /// + /// When `s` is `0.0`, the result will be equal to `self`. When `s` is + /// `1.0`, the result will be equal to `rhs`. When `s` is outside of + /// range `[0, 1]`, the result is linearly extrapolated. + #[doc(alias = "mix")] + #[inline] + #[must_use] + pub fn lerp(self, rhs: Self, s: f64) -> Self { + Self(self.0.lerp(rhs.0, s)) + } +} + +impl Deref for Position { + type Target = DVec3; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Position { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Default for Position { + fn default() -> Self { + Self(DVec3::ZERO) + } +} + +impl Add for Position { + type Output = Self; + + fn add(self, rhs: DVec3) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl AddAssign for Position { + fn add_assign(&mut self, rhs: DVec3) { + self.0 += rhs; + } +} + +impl Sub for Position { + type Output = DVec3; + fn sub(self, rhs: Position) -> DVec3 { + self.0 - rhs.0 + } +} + +impl SubAssign for Position { + fn sub_assign(&mut self, rhs: DVec3) { + self.0 -= rhs + } +} + +impl From for Position { + fn from(value: DVec3) -> Self { + Self(value) + } +} + +impl From for DVec3 { + fn from(value: Position) -> Self { + value.0 + } +} + +impl From for azalea_core::position::Vec3 { + fn from(value: Position) -> Self { + Self::new(value.x, value.y, value.z) + } +} + +impl From for Position { + fn from(value: azalea_core::position::Vec3) -> Self { + Self(DVec3::new(value.x, value.y, value.z)) + } +} + +// -- VELOCITY -- + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Velocity(DVec3); + +impl Velocity { + pub fn new(x: f64, y: f64, z: f64) -> Self { + Self(dvec3(x, y, z)) + } + + pub fn x_rot(self, radians: f32) -> Self { + let (sin, cos) = (radians as f64).sin_cos(); + Self::new( + self.x, + self.y * cos - self.z * sin, + self.y * sin + self.z * cos, + ) + } + + pub fn y_rot(self, radians: f32) -> Self { + let (sin, cos) = (radians as f64).sin_cos(); + Self::new( + self.x * cos + self.z * sin, + self.y, + -self.x * sin + self.z * cos, + ) + } +} + +impl Deref for Velocity { + type Target = DVec3; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Velocity { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Default for Velocity { + fn default() -> Self { + Self(DVec3::ZERO) + } +} + +impl From for Velocity { + fn from(value: DVec3) -> Self { + Self(value) + } +} + +impl From for DVec3 { + fn from(value: Velocity) -> Self { + value.0 + } +} + +impl From for azalea_core::position::Vec3 { + fn from(value: Velocity) -> Self { + Self::new(value.x, value.y, value.z) + } +} + +impl From for Velocity { + fn from(value: azalea_core::position::Vec3) -> Self { + Self(DVec3::new(value.x, value.y, value.z)) + } +} + +// -- LOOK DIRECTION -- + +/// y_rot: n*360° + 0º: +Z (South), n*360° + 90°: -X (West) +/// +/// x_rot: -90°: UP, 90°: DOWN +#[derive(Default, Clone, Copy, Debug, PartialEq)] +pub struct LookDirection { + y_rot_deg: f32, + x_rot_deg: f32, +} + +impl LookDirection { + pub fn new(y_rot_deg: f32, x_rot_deg: f32) -> Self { + Self { + y_rot_deg, + x_rot_deg: x_rot_deg.clamp(-89.999, 89.999), + } + } + + pub fn y_rot_deg(&self) -> f32 { + self.y_rot_deg + } + + pub fn x_rot_deg(&self) -> f32 { + self.x_rot_deg + } + + pub fn y_rot_rad(&self) -> f32 { + self.y_rot_deg.to_radians() + } + + pub fn x_rot_rad(&self) -> f32 { + self.x_rot_deg.to_radians() + } + + pub fn as_vec(self) -> Vec3 { + let y_rot_rad = self.y_rot_rad(); + let x_rot_rad = self.x_rot_rad(); + let (sin_y_rot, cos_y_rot) = y_rot_rad.sin_cos(); + let (sin_x_rot, cos_x_rot) = x_rot_rad.sin_cos(); + vec3(-sin_y_rot * cos_x_rot, -sin_x_rot, cos_y_rot * cos_x_rot) + } +} + +impl From for azalea_entity::LookDirection { + fn from(value: LookDirection) -> Self { + Self::new(value.y_rot_deg, value.x_rot_deg) + } +} diff --git a/pomme-client/src/entity.rs b/pomme-client/src/entity/mod.rs similarity index 79% rename from pomme-client/src/entity.rs rename to pomme-client/src/entity/mod.rs index e755895..3829439 100644 --- a/pomme-client/src/entity.rs +++ b/pomme-client/src/entity/mod.rs @@ -1,15 +1,19 @@ +pub mod components; + use std::collections::HashMap; use azalea_registry::builtin::EntityKind; use glam::DVec3; +use crate::entity::components::{LookDirection, Position, Velocity}; + fn item_move( - pos: &mut DVec3, - vel: DVec3, + pos: &mut Position, + vel: Velocity, half_w: f64, height: f64, is_solid: &impl Fn(i32, i32, i32) -> bool, -) -> DVec3 { +) -> Velocity { let mut remaining = vel; if remaining.y != 0.0 { @@ -35,7 +39,7 @@ fn item_move( } fn sweep_axis_y( - pos: &mut DVec3, + pos: &mut Position, dy: f64, half_w: f64, height: f64, @@ -79,7 +83,7 @@ fn sweep_axis_y( } fn sweep_axis_x( - pos: &mut DVec3, + pos: &mut Position, dx: f64, half_w: f64, height: f64, @@ -114,7 +118,7 @@ fn sweep_axis_x( } fn sweep_axis_z( - pos: &mut DVec3, + pos: &mut Position, dz: f64, half_w: f64, height: f64, @@ -152,16 +156,14 @@ const INTERPOLATION_STEPS: i32 = 3; #[allow(dead_code)] pub struct LivingEntity { - pub position: DVec3, - pub prev_position: DVec3, - pub yaw: f32, - pub prev_yaw: f32, - pub pitch: f32, - pub prev_pitch: f32, - pub body_yaw: f32, - pub prev_body_yaw: f32, - pub head_yaw: f32, - pub prev_head_yaw: f32, + pub position: Position, + pub prev_position: Position, + pub look_dir: LookDirection, + pub prev_look_dir: LookDirection, + pub head_y_rot_deg: f32, + pub prev_head_y_rot_deg: f32, + pub body_y_rot_deg: f32, + pub prev_body_y_rot_deg: f32, pub entity_type: EntityKind, pub walk_anim_pos: f32, pub walk_anim_speed: f32, @@ -175,33 +177,30 @@ pub struct LivingEntity { pub prev_eat_anim_tick: u8, pub age_in_ticks: u32, pub custom_name: Option, - interp_target: DVec3, - interp_yaw: f32, - interp_pitch: f32, + interp_target: Position, + interp_look_dir: LookDirection, interp_steps: i32, - interp_head_yaw: f32, - interp_head_steps: i32, + interp_head_y_rot_deg: f32, + interp_head_y_rot_steps: i32, } impl LivingEntity { pub fn new( entity_type: EntityKind, - position: DVec3, - yaw: f32, - pitch: f32, - head_yaw: f32, + position: Position, + look_dir: LookDirection, + head_y_rot_deg: f32, + body_y_rot_deg: f32, ) -> Self { Self { position, prev_position: position, - yaw, - prev_yaw: yaw, - pitch, - prev_pitch: pitch, - body_yaw: yaw, - prev_body_yaw: yaw, - head_yaw, - prev_head_yaw: head_yaw, + look_dir, + prev_look_dir: look_dir, + head_y_rot_deg, + prev_head_y_rot_deg: head_y_rot_deg, + body_y_rot_deg, + prev_body_y_rot_deg: body_y_rot_deg, entity_type, walk_anim_pos: 0.0, walk_anim_speed: 0.0, @@ -216,70 +215,78 @@ impl LivingEntity { age_in_ticks: 0, custom_name: None, interp_target: position, - interp_yaw: yaw, - interp_pitch: pitch, + interp_look_dir: look_dir, interp_steps: 0, - interp_head_yaw: head_yaw, - interp_head_steps: 0, + interp_head_y_rot_deg: head_y_rot_deg, + interp_head_y_rot_steps: 0, } } - fn interpolate_to_pos(&mut self, pos: DVec3) { + fn interpolate_to_pos(&mut self, pos: Position) { self.interp_target = pos; self.interp_steps = INTERPOLATION_STEPS; } pub fn tick_interpolation(&mut self) { self.prev_position = self.position; - self.prev_yaw = self.yaw; - self.prev_pitch = self.pitch; + self.prev_look_dir = self.look_dir; if self.interp_steps > 0 { let alpha = 1.0 / self.interp_steps as f64; self.position = self.position.lerp(self.interp_target, alpha); - self.yaw = lerp_angle(self.yaw, self.interp_yaw, 1.0 / self.interp_steps as f32); - self.pitch += (self.interp_pitch - self.pitch) / self.interp_steps as f32; + let y_rot = lerp_angle( + self.look_dir.y_rot_deg(), + self.interp_look_dir.y_rot_deg(), + 1.0 / self.interp_steps as f32, + ); + let x_rot = self.look_dir.x_rot_deg() + + (self.interp_look_dir.x_rot_deg() - self.look_dir.x_rot_deg()) + / self.interp_steps as f32; + self.look_dir = LookDirection::new(y_rot, x_rot); self.interp_steps -= 1; } - self.prev_head_yaw = self.head_yaw; - if self.interp_head_steps > 0 { - self.head_yaw = lerp_angle( - self.head_yaw, - self.interp_head_yaw, - 1.0 / self.interp_head_steps as f32, + self.prev_head_y_rot_deg = self.head_y_rot_deg; + if self.interp_head_y_rot_steps > 0 { + self.head_y_rot_deg = lerp_angle( + self.head_y_rot_deg, + self.interp_head_y_rot_deg, + 1.0 / self.interp_head_y_rot_steps as f32, ); - self.interp_head_steps -= 1; + self.interp_head_y_rot_steps -= 1; } + + self.prev_body_y_rot_deg = self.body_y_rot_deg; } pub fn tick_body_rotation(&mut self) { - self.prev_body_yaw = self.body_yaw; - let dx = self.position.x - self.prev_position.x; let dz = self.position.z - self.prev_position.z; let dist_sq = (dx * dx + dz * dz) as f32; - let body_target = if dist_sq > 0.0025 { - -(dx as f32).atan2(dz as f32).to_degrees() - } else { - self.yaw - }; - - let diff = wrap_degrees(body_target - self.body_yaw); - self.body_yaw += diff * 0.3; + if dist_sq > 0.0025 { + let walk_dir = -(dx as f32).atan2(dz as f32).to_degrees(); + let diff_from_look = wrap_degrees(self.look_dir.y_rot_deg() - walk_dir).abs(); + let body_target = if diff_from_look > 95.0 && diff_from_look < 265.0 { + walk_dir - 180.0 + } else { + walk_dir + }; + let diff = wrap_degrees(body_target - self.body_y_rot_deg); + self.body_y_rot_deg += diff * 0.3; + } - let head_diff = wrap_degrees(self.yaw - self.body_yaw); + let head_diff = wrap_degrees(self.head_y_rot_deg - self.body_y_rot_deg); if head_diff.abs() > 50.0 { - self.body_yaw += head_diff - head_diff.signum() * 50.0; + self.body_y_rot_deg += head_diff - head_diff.signum() * 50.0; } } } pub struct ItemEntity { - pub position: DVec3, - pub prev_position: DVec3, - pub velocity: DVec3, + pub position: Position, + pub prev_position: Position, + pub velocity: Velocity, pub on_ground: bool, pub item_name: String, pub count: i32, @@ -290,8 +297,8 @@ pub struct ItemEntity { struct PickupAnimation { item_name: String, - start_pos: DVec3, - target_pos: DVec3, + start_pos: Position, + target_pos: Position, bob_offset: f32, age: u32, life: u32, @@ -300,7 +307,7 @@ struct PickupAnimation { pub struct PickupRenderInfo { pub item_name: String, - pub position: DVec3, + pub position: Position, pub bob_offset: f32, pub age: u32, pub is_block_model: bool, @@ -321,7 +328,7 @@ impl ItemEntityStore { } } - pub fn spawn_item(&mut self, id: i32, position: DVec3, velocity: DVec3) { + pub fn spawn_item(&mut self, id: i32, position: Position, velocity: Velocity) { let bob_offset = ((id as u32).wrapping_mul(2654435761)) as f32 / u32::MAX as f32 * std::f32::consts::TAU; self.items.insert( @@ -357,14 +364,14 @@ impl ItemEntityStore { } } - pub fn teleport(&mut self, id: i32, position: DVec3) { + pub fn teleport(&mut self, id: i32, position: Position) { if let Some(entity) = self.items.get_mut(&id) { entity.prev_position = entity.position; entity.position = position; } } - pub fn pickup(&mut self, item_id: i32, target_pos: DVec3) { + pub fn pickup(&mut self, item_id: i32, target_pos: Position) { if let Some(entity) = self.items.remove(&item_id) && !entity.item_name.is_empty() { @@ -485,14 +492,20 @@ impl EntityStore { &mut self, id: i32, entity_type: EntityKind, - position: DVec3, - yaw: f32, - pitch: f32, - head_yaw: f32, + position: Position, + look_dir: LookDirection, + body_y_rot_deg: f32, + head_y_rot_deg: f32, ) { self.living.insert( id, - LivingEntity::new(entity_type, position, yaw, pitch, head_yaw), + LivingEntity::new( + entity_type, + position, + look_dir, + head_y_rot_deg, + body_y_rot_deg, + ), ); } @@ -503,10 +516,9 @@ impl EntityStore { } } - pub fn teleport_living(&mut self, id: i32, x: f64, y: f64, z: f64) { + pub fn teleport_living(&mut self, id: i32, position: Position) { if let Some(entity) = self.living.get_mut(&id) { - let pos = DVec3::new(x, y, z); - entity.interpolate_to_pos(pos); + entity.interpolate_to_pos(position); } } @@ -548,18 +560,17 @@ impl EntityStore { } } - pub fn update_living_rotation(&mut self, id: i32, yaw: f32, pitch: f32) { + pub fn update_living_rotation(&mut self, id: i32, y_rot_deg: f32, x_rot_deg: f32) { if let Some(entity) = self.living.get_mut(&id) { - entity.interp_yaw = yaw; - entity.interp_pitch = pitch; + entity.interp_look_dir = LookDirection::new(y_rot_deg, x_rot_deg); entity.interp_steps = entity.interp_steps.max(INTERPOLATION_STEPS); } } - pub fn update_head_rotation(&mut self, id: i32, head_yaw: f32) { + pub fn update_head_rotation(&mut self, id: i32, head_y_rot_deg: f32) { if let Some(entity) = self.living.get_mut(&id) { - entity.interp_head_yaw = head_yaw; - entity.interp_head_steps = INTERPOLATION_STEPS; + entity.interp_head_y_rot_deg = head_y_rot_deg; + entity.interp_head_y_rot_steps = INTERPOLATION_STEPS; } } @@ -614,7 +625,7 @@ pub fn wrap_degrees(deg: f32) -> f32 { d } -fn lerp_angle(from: f32, to: f32, alpha: f32) -> f32 { +pub fn lerp_angle(from: f32, to: f32, alpha: f32) -> f32 { from + wrap_degrees(to - from) * alpha } diff --git a/pomme-client/src/net/handler.rs b/pomme-client/src/net/handler.rs index d578ada..2a55735 100644 --- a/pomme-client/src/net/handler.rs +++ b/pomme-client/src/net/handler.rs @@ -194,27 +194,25 @@ pub fn handle_game_packet( }); } ClientboundGamePacket::AddEntity(p) => { - let yaw = (p.y_rot as f32) * 360.0 / 256.0; - let pitch = (p.x_rot as f32) * 360.0 / 256.0; - let head_yaw = (p.y_head_rot as f32) * 360.0 / 256.0; - let vel = p.movement.to_vec3(); + let y_rot_deg = (p.y_rot as f32) * 360.0 / 256.0; + let x_rot_deg = (p.x_rot as f32) * 360.0 / 256.0; + let head_y_rot_deg = (p.y_head_rot as f32) * 360.0 / 256.0; + let velocity = p.movement.to_vec3(); let _ = event_tx.try_send(NetworkEvent::EntitySpawned { id: p.id.0, entity_type: p.entity_type, - x: p.position.x, - y: p.position.y, - z: p.position.z, - yaw, - pitch, - head_yaw, - velocity: [vel.x, vel.y, vel.z], + position: p.position.into(), + velocity: velocity.into(), + y_rot_deg, + x_rot_deg, + head_y_rot_deg, }); } ClientboundGamePacket::RotateHead(p) => { - let head_yaw = (p.y_head_rot as f32) * 360.0 / 256.0; + let head_y_rot_deg = (p.y_head_rot as f32) * 360.0 / 256.0; let _ = event_tx.try_send(NetworkEvent::EntityHeadRotation { id: p.entity_id.0, - head_yaw, + head_y_rot_deg, }); } ClientboundGamePacket::MoveEntityPos(p) => { @@ -228,28 +226,24 @@ pub fn handle_game_packet( dx: p.delta.x(), dy: p.delta.y(), dz: p.delta.z(), - yaw: look.y_rot(), - pitch: look.x_rot(), + y_rot_deg: look.y_rot(), + x_rot_deg: look.x_rot(), }); } ClientboundGamePacket::TeleportEntity(p) => { let _ = event_tx.try_send(NetworkEvent::EntityTeleported { id: p.id.0, - x: p.change.pos.x, - y: p.change.pos.y, - z: p.change.pos.z, - yaw: p.change.look_direction.y_rot(), - pitch: p.change.look_direction.x_rot(), + position: p.change.pos.into(), + y_rot_deg: p.change.look_direction.y_rot(), + x_rot_deg: p.change.look_direction.x_rot(), }); } ClientboundGamePacket::EntityPositionSync(p) => { let _ = event_tx.try_send(NetworkEvent::EntityTeleported { id: p.id.0, - x: p.values.pos.x, - y: p.values.pos.y, - z: p.values.pos.z, - yaw: p.values.look_direction.y_rot(), - pitch: p.values.look_direction.x_rot(), + position: p.values.pos.into(), + y_rot_deg: p.values.look_direction.y_rot(), + x_rot_deg: p.values.look_direction.x_rot(), }); } ClientboundGamePacket::RemoveEntities(p) => { diff --git a/pomme-client/src/net/mod.rs b/pomme-client/src/net/mod.rs index 22035ea..ba7b53e 100644 --- a/pomme-client/src/net/mod.rs +++ b/pomme-client/src/net/mod.rs @@ -10,6 +10,8 @@ use azalea_core::position::{BlockPos, ChunkPos}; use azalea_inventory::ItemStack; use azalea_registry::builtin::EntityKind; +use crate::entity::components::{Position, Velocity}; + pub enum NetworkEvent { Connected, BiomeColors { @@ -88,13 +90,11 @@ pub enum NetworkEvent { EntitySpawned { id: i32, entity_type: EntityKind, - x: f64, - y: f64, - z: f64, - yaw: f32, - pitch: f32, - head_yaw: f32, - velocity: [f64; 3], + position: Position, + velocity: Velocity, + y_rot_deg: f32, + x_rot_deg: f32, + head_y_rot_deg: f32, }, EntityMoved { id: i32, @@ -107,16 +107,14 @@ pub enum NetworkEvent { dx: f64, dy: f64, dz: f64, - yaw: f32, - pitch: f32, + y_rot_deg: f32, + x_rot_deg: f32, }, EntityTeleported { id: i32, - x: f64, - y: f64, - z: f64, - yaw: f32, - pitch: f32, + position: Position, + y_rot_deg: f32, + x_rot_deg: f32, }, EntitiesRemoved { ids: Vec, @@ -128,7 +126,7 @@ pub enum NetworkEvent { }, EntityHeadRotation { id: i32, - head_yaw: f32, + head_y_rot_deg: f32, }, EntityBabyFlag { id: i32, diff --git a/pomme-client/src/physics/aabb.rs b/pomme-client/src/physics/aabb.rs index c35b66c..651da29 100644 --- a/pomme-client/src/physics/aabb.rs +++ b/pomme-client/src/physics/aabb.rs @@ -1,20 +1,20 @@ -use glam::Vec3; +use glam::{DVec3, dvec3}; #[derive(Debug, Clone, Copy)] pub struct Aabb { - pub min: Vec3, - pub max: Vec3, + pub min: DVec3, + pub max: DVec3, } impl Aabb { - pub fn new(min: Vec3, max: Vec3) -> Self { + pub fn new(min: DVec3, max: DVec3) -> Self { Self { min, max } } - pub fn from_center(center: Vec3, half_width: f32, half_height: f32) -> Self { + pub fn from_center(center: DVec3, half_width: f64, half_height: f64) -> Self { Self { - min: Vec3::new(center.x - half_width, center.y, center.z - half_width), - max: Vec3::new( + min: dvec3(center.x - half_width, center.y, center.z - half_width), + max: dvec3( center.x + half_width, center.y + half_height * 2.0, center.z + half_width, @@ -22,14 +22,14 @@ impl Aabb { } } - pub fn offset(self, offset: Vec3) -> Self { + pub fn offset(self, offset: DVec3) -> Self { Self { min: self.min + offset, max: self.max + offset, } } - pub fn expand(self, delta: Vec3) -> Self { + pub fn expand(self, delta: DVec3) -> Self { let mut min = self.min; let mut max = self.max; @@ -52,35 +52,39 @@ impl Aabb { Self { min, max } } - pub fn clip_x_collide(&self, other: &Aabb, dx: f32) -> f32 { + pub fn clip_x_collide(&self, other: &Aabb, dx: f64) -> f64 { self.clip_axis(other, dx, Axis::X) } - pub fn clip_y_collide(&self, other: &Aabb, dy: f32) -> f32 { + pub fn clip_y_collide(&self, other: &Aabb, dy: f64) -> f64 { self.clip_axis(other, dy, Axis::Y) } - pub fn clip_z_collide(&self, other: &Aabb, dz: f32) -> f32 { + pub fn clip_z_collide(&self, other: &Aabb, dz: f64) -> f64 { self.clip_axis(other, dz, Axis::Z) } - fn clip_axis(&self, other: &Aabb, mut delta: f32, axis: Axis) -> f32 { + fn clip_axis(&self, other: &Aabb, mut delta: f64, axis: Axis) -> f64 { let (c1, c2) = axis.cross_axes(); - if get(other.max, c1) <= get(self.min, c1) || get(other.min, c1) >= get(self.max, c1) { + if component(other.max, c1) <= component(self.min, c1) + || component(other.min, c1) >= component(self.max, c1) + { return delta; } - if get(other.max, c2) <= get(self.min, c2) || get(other.min, c2) >= get(self.max, c2) { + if component(other.max, c2) <= component(self.min, c2) + || component(other.min, c2) >= component(self.max, c2) + { return delta; } - if delta > 0.0 && get(other.max, axis) <= get(self.min, axis) { - let clip = get(self.min, axis) - get(other.max, axis); + if delta > 0.0 && component(other.max, axis) <= component(self.min, axis) { + let clip = component(self.min, axis) - component(other.max, axis); if clip < delta { delta = clip; } - } else if delta < 0.0 && get(other.min, axis) >= get(self.max, axis) { - let clip = get(self.max, axis) - get(other.min, axis); + } else if delta < 0.0 && component(other.min, axis) >= component(self.max, axis) { + let clip = component(self.max, axis) - component(other.min, axis); if clip > delta { delta = clip; } @@ -107,7 +111,7 @@ impl Axis { } } -fn get(v: Vec3, axis: Axis) -> f32 { +fn component(v: DVec3, axis: Axis) -> f64 { match axis { Axis::X => v.x, Axis::Y => v.y, diff --git a/pomme-client/src/physics/collision.rs b/pomme-client/src/physics/collision.rs index 1c274eb..a6ff957 100644 --- a/pomme-client/src/physics/collision.rs +++ b/pomme-client/src/physics/collision.rs @@ -2,9 +2,10 @@ use std::collections::HashSet; use std::sync::LazyLock; use azalea_registry::builtin::BlockKind; -use glam::Vec3; +use glam::{DVec3, dvec3}; use super::aabb::Aabb; +use crate::entity::components::Velocity; use crate::world::chunk::ChunkStore; static NO_COLLISION: LazyLock> = LazyLock::new(|| { @@ -272,8 +273,8 @@ pub fn collect_block_aabbs(chunk_store: &ChunkStore, region: &Aabb) -> Vec let state = chunk_store.get_block_state(bx, by, bz); if has_collision(state) { aabbs.push(Aabb::new( - Vec3::new(bx as f32, by as f32, bz as f32), - Vec3::new((bx + 1) as f32, (by + 1) as f32, (bz + 1) as f32), + dvec3(bx as f64, by as f64, bz as f64), + dvec3((bx + 1) as f64, (by + 1) as f64, (bz + 1) as f64), )); } } @@ -282,13 +283,18 @@ pub fn collect_block_aabbs(chunk_store: &ChunkStore, region: &Aabb) -> Vec aabbs } -fn collide_along_axes(block_aabbs: &[Aabb], player_aabb: Aabb, mut velocity: Vec3) -> (Vec3, bool) { + +fn collide_along_axes( + block_aabbs: &[Aabb], + player_aabb: Aabb, + mut velocity: Velocity, +) -> (DVec3, bool) { let original_y = velocity.y; for block in block_aabbs { velocity.y = block.clip_y_collide(&player_aabb, velocity.y); } - let mut resolved = player_aabb.offset(Vec3::new(0.0, velocity.y, 0.0)); + let mut resolved = player_aabb.offset(dvec3(0.0, velocity.y, 0.0)); let x_first = velocity.x.abs() >= velocity.z.abs(); @@ -296,7 +302,7 @@ fn collide_along_axes(block_aabbs: &[Aabb], player_aabb: Aabb, mut velocity: Vec for block in block_aabbs { velocity.x = block.clip_x_collide(&resolved, velocity.x); } - resolved = resolved.offset(Vec3::new(velocity.x, 0.0, 0.0)); + resolved = resolved.offset(dvec3(velocity.x, 0.0, 0.0)); for block in block_aabbs { velocity.z = block.clip_z_collide(&resolved, velocity.z); @@ -305,7 +311,7 @@ fn collide_along_axes(block_aabbs: &[Aabb], player_aabb: Aabb, mut velocity: Vec for block in block_aabbs { velocity.z = block.clip_z_collide(&resolved, velocity.z); } - resolved = resolved.offset(Vec3::new(0.0, 0.0, velocity.z)); + resolved = resolved.offset(dvec3(0.0, 0.0, velocity.z)); for block in block_aabbs { velocity.x = block.clip_x_collide(&resolved, velocity.x); @@ -314,44 +320,47 @@ fn collide_along_axes(block_aabbs: &[Aabb], player_aabb: Aabb, mut velocity: Vec let on_ground = original_y < 0.0 && velocity.y != original_y; - (velocity, on_ground) + (*velocity, on_ground) } pub fn resolve_collision( chunk_store: &ChunkStore, player_aabb: Aabb, - velocity: Vec3, - step_height: f32, -) -> (Vec3, bool) { - let expanded = player_aabb.expand(velocity); + velocity: Velocity, + step_height: f64, +) -> (DVec3, bool) { + let expanded = player_aabb.expand(*velocity); let block_aabbs = collect_block_aabbs(chunk_store, &expanded); let (resolved, on_ground) = collide_along_axes(&block_aabbs, player_aabb, velocity); let horizontal_blocked = resolved.x != velocity.x || resolved.z != velocity.z; if step_height > 0.0 && on_ground && horizontal_blocked { - let step_up = Vec3::new(velocity.x, step_height, velocity.z); + let step_up = dvec3(velocity.x, step_height, velocity.z); let step_expanded = player_aabb .expand(step_up) - .expand(Vec3::new(0.0, -step_height, 0.0)); + .expand(dvec3(0.0, -step_height, 0.0)); let step_aabbs = collect_block_aabbs(chunk_store, &step_expanded); let mut up_vel = step_height; for block in &step_aabbs { up_vel = block.clip_y_collide(&player_aabb, up_vel); } - let raised = player_aabb.offset(Vec3::new(0.0, up_vel, 0.0)); + let raised = player_aabb.offset(dvec3(0.0, up_vel, 0.0)); - let (step_resolved, _) = - collide_along_axes(&step_aabbs, raised, Vec3::new(velocity.x, 0.0, velocity.z)); + let (step_resolved, _) = collide_along_axes( + &step_aabbs, + raised, + Velocity::new(velocity.x, 0.0, velocity.z), + ); - let after_move = raised.offset(Vec3::new(step_resolved.x, 0.0, step_resolved.z)); + let after_move = raised.offset(dvec3(step_resolved.x, 0.0, step_resolved.z)); let mut down_vel = -(up_vel - velocity.y); for block in &step_aabbs { down_vel = block.clip_y_collide(&after_move, down_vel); } - let step_total = Vec3::new(step_resolved.x, up_vel + down_vel, step_resolved.z); + let step_total = dvec3(step_resolved.x, up_vel + down_vel, step_resolved.z); let step_h_dist = step_total.x * step_total.x + step_total.z * step_total.z; let orig_h_dist = resolved.x * resolved.x + resolved.z * resolved.z; diff --git a/pomme-client/src/physics/movement.rs b/pomme-client/src/physics/movement.rs index c813f00..0584dd9 100644 --- a/pomme-client/src/physics/movement.rs +++ b/pomme-client/src/physics/movement.rs @@ -1,6 +1,7 @@ // TODO: fall damage - track fall distance, reset on water entry, apply damage // on ground impact +use glam::DVec3; use winit::keyboard::KeyCode; use super::aabb::Aabb; @@ -9,30 +10,30 @@ use crate::app::input::InputState; use crate::player::LocalPlayer; use crate::world::chunk::ChunkStore; -const GRAVITY: f32 = 0.08; -const JUMP_VELOCITY: f32 = 0.42; +const GRAVITY: f64 = 0.08; +const JUMP_VELOCITY: f64 = 0.42; const VERTICAL_DRAG: f32 = 0.98; -const HORIZONTAL_DRAG: f32 = 0.91; -const BLOCK_FRICTION: f32 = 0.6; -const GROUND_FRICTION: f32 = BLOCK_FRICTION * HORIZONTAL_DRAG; -const GROUND_ACCEL_FACTOR: f32 = 0.216; -const MOVEMENT_SPEED: f32 = 0.1; -const SPRINT_SPEED_MODIFIER: f32 = 0.3; -const AIR_ACCELERATION: f32 = 0.02; +const HORIZONTAL_DRAG: f64 = 0.91; +const BLOCK_FRICTION: f64 = 0.6; +const GROUND_FRICTION: f64 = BLOCK_FRICTION * HORIZONTAL_DRAG; +const GROUND_ACCEL_FACTOR: f64 = 0.216; +const MOVEMENT_SPEED: f64 = 0.1; +const SPRINT_SPEED_MODIFIER: f64 = 0.3; +const AIR_ACCELERATION: f64 = 0.02; // TODO: WATER_MOVEMENT_EFFICIENCY attribute - scales drag toward 0.546 and // accel toward land speed -const WATER_ACCELERATION: f32 = 0.02; -const WATER_HORIZONTAL_DRAG: f32 = 0.8; -const WATER_HORIZONTAL_DRAG_SPRINT: f32 = 0.9; -const WATER_VERTICAL_DRAG: f32 = 0.8; -const WATER_GRAVITY: f32 = 0.02; -const STEP_HEIGHT: f32 = 0.6; -const PLAYER_HALF_WIDTH: f32 = 0.3; -const PLAYER_HEIGHT: f32 = 1.8; -const SPRINT_JUMP_BOOST: f32 = 0.2; +const WATER_ACCELERATION: f64 = 0.02; +const WATER_HORIZONTAL_DRAG: f64 = 0.8; +const WATER_HORIZONTAL_DRAG_SPRINT: f64 = 0.9; +const WATER_VERTICAL_DRAG: f64 = 0.8; +const WATER_GRAVITY: f64 = 0.02; +const STEP_HEIGHT: f64 = 0.6; +const PLAYER_HALF_WIDTH: f64 = 0.3; +const PLAYER_HEIGHT: f64 = 1.8; +const SPRINT_JUMP_BOOST: f64 = 0.2; const SPRINT_HUNGER_THRESHOLD: u32 = 6; const DEFAULT_SPRINT_WINDOW: u32 = 7; -const MINOR_COLLISION_ANGLE: f32 = 0.13962634; +const MINOR_COLLISION_ANGLE: f64 = 0.13962634; pub fn tick(player: &mut LocalPlayer, input: &InputState, chunk_store: &ChunkStore) { player.update_water_state(chunk_store); @@ -42,7 +43,7 @@ pub fn tick(player: &mut LocalPlayer, input: &InputState, chunk_store: &ChunkSto update_sprint_state(player, input, forward, forward_pressed); - let (sin_yaw, cos_yaw) = player.yaw.sin_cos(); + let (sin_y_rot, cos_y_rot) = (player.look_dir.y_rot_rad() as f64).sin_cos(); if player.in_water { tick_water( @@ -51,8 +52,8 @@ pub fn tick(player: &mut LocalPlayer, input: &InputState, chunk_store: &ChunkSto chunk_store, forward, strafe, - sin_yaw, - cos_yaw, + sin_y_rot, + cos_y_rot, ); } else { tick_land( @@ -61,8 +62,8 @@ pub fn tick(player: &mut LocalPlayer, input: &InputState, chunk_store: &ChunkSto chunk_store, forward, strafe, - sin_yaw, - cos_yaw, + sin_y_rot, + cos_y_rot, ); } @@ -74,17 +75,17 @@ fn tick_land( player: &mut LocalPlayer, input: &InputState, chunk_store: &ChunkStore, - forward: f32, - strafe: f32, - sin_yaw: f32, - cos_yaw: f32, + forward: f64, + strafe: f64, + sin_y_rot: f64, + cos_y_rot: f64, ) { if player.on_ground && input.key_pressed(KeyCode::Space) { player.velocity.y = JUMP_VELOCITY; if player.sprinting { - player.velocity.x -= sin_yaw * SPRINT_JUMP_BOOST; - player.velocity.z -= cos_yaw * SPRINT_JUMP_BOOST; + player.velocity.x -= sin_y_rot * SPRINT_JUMP_BOOST; + player.velocity.z += cos_y_rot * SPRINT_JUMP_BOOST; } } @@ -100,14 +101,14 @@ fn tick_land( } else { AIR_ACCELERATION }; - let (move_x, move_z) = world_movement(forward, strafe, sin_yaw, cos_yaw); + let (move_x, move_z) = world_movement(forward, strafe, sin_y_rot, cos_y_rot); player.velocity.x += move_x * accel; player.velocity.z += move_z * accel; - apply_collision(player, chunk_store, forward, strafe, sin_yaw, cos_yaw); + apply_collision(player, chunk_store, forward, strafe, sin_y_rot, cos_y_rot); player.velocity.y -= GRAVITY; - player.velocity.y *= VERTICAL_DRAG; + player.velocity.y *= VERTICAL_DRAG as f64; let h_friction = if player.on_ground { GROUND_FRICTION @@ -126,10 +127,10 @@ fn tick_water( player: &mut LocalPlayer, input: &InputState, chunk_store: &ChunkStore, - forward: f32, - strafe: f32, - sin_yaw: f32, - cos_yaw: f32, + forward: f64, + strafe: f64, + sin_y_rot: f64, + cos_y_rot: f64, ) { if input.key_pressed(KeyCode::Space) { player.velocity.y += 0.04; @@ -138,17 +139,17 @@ fn tick_water( player.velocity.y -= 0.04; } - let (move_x, move_z) = world_movement(forward, strafe, sin_yaw, cos_yaw); + let (move_x, move_z) = world_movement(forward, strafe, sin_y_rot, cos_y_rot); player.velocity.x += move_x * WATER_ACCELERATION; player.velocity.z += move_z * WATER_ACCELERATION; if player.swimming { - let pitch_y = player.pitch.sin() as f64; - let boost = if pitch_y < -0.2 { 0.085 } else { 0.06 }; - player.velocity.y += ((pitch_y - player.velocity.y as f64) * boost) as f32; + let x_rot_y = player.look_dir.x_rot_rad().sin() as f64; + let boost = if x_rot_y < -0.2 { 0.085 } else { 0.06 }; + player.velocity.y -= (x_rot_y - player.velocity.y) * boost; } - apply_collision(player, chunk_store, forward, strafe, sin_yaw, cos_yaw); + apply_collision(player, chunk_store, forward, strafe, sin_y_rot, cos_y_rot); let h_drag = if player.sprinting { WATER_HORIZONTAL_DRAG_SPRINT @@ -170,12 +171,16 @@ fn tick_water( fn apply_collision( player: &mut LocalPlayer, chunk_store: &ChunkStore, - forward: f32, - strafe: f32, - sin_yaw: f32, - cos_yaw: f32, + forward: f64, + strafe: f64, + sin_y_rot: f64, + cos_y_rot: f64, ) { - let aabb = Aabb::from_center(player.position, PLAYER_HALF_WIDTH, PLAYER_HEIGHT / 2.0); + let aabb = Aabb::from_center( + player.position.into(), + PLAYER_HALF_WIDTH, + PLAYER_HEIGHT / 2.0, + ); let step_height = if player.on_ground { STEP_HEIGHT } else { 0.0 }; let (resolved, on_ground) = resolve_collision(chunk_store, aabb, player.velocity, step_height); @@ -189,7 +194,7 @@ fn apply_collision( if player.sprinting && horizontal_collision && forward > 0.0 - && !is_minor_horizontal_collision(forward, strafe, sin_yaw, cos_yaw, &resolved) + && !is_minor_horizontal_collision(forward, strafe, sin_y_rot, cos_y_rot, resolved) { player.sprinting = false; } @@ -198,7 +203,7 @@ fn apply_collision( fn update_sprint_state( player: &mut LocalPlayer, input: &InputState, - forward: f32, + forward: f64, forward_pressed: bool, ) { if player.sprint_toggle_timer > 0 { @@ -223,35 +228,34 @@ fn update_sprint_state( } } -fn world_movement(forward: f32, strafe: f32, sin_yaw: f32, cos_yaw: f32) -> (f32, f32) { +fn world_movement(forward: f64, strafe: f64, sin_y_rot: f64, cos_y_rot: f64) -> (f64, f64) { ( - forward * -sin_yaw + strafe * cos_yaw, - forward * -cos_yaw + strafe * -sin_yaw, + forward * -sin_y_rot + strafe * -cos_y_rot, + forward * cos_y_rot + strafe * -sin_y_rot, ) } fn is_minor_horizontal_collision( - forward: f32, - strafe: f32, - sin_yaw: f32, - cos_yaw: f32, - resolved: &glam::Vec3, + forward: f64, + strafe: f64, + sin_y_rot: f64, + cos_y_rot: f64, + resolved: DVec3, ) -> bool { - let (intent_x, intent_z) = world_movement(forward, strafe, sin_yaw, cos_yaw); - let (ix, iz) = (intent_x as f64, intent_z as f64); - let intent_len_sq = ix * ix + iz * iz; - let resolved_len_sq = (resolved.x as f64).powi(2) + (resolved.z as f64).powi(2); + let (intent_x, intent_z) = world_movement(forward, strafe, sin_y_rot, cos_y_rot); + let intent_len_sq = intent_x * intent_x + intent_z * intent_z; + let resolved_len_sq = resolved.x.powi(2) + resolved.z.powi(2); if intent_len_sq < 1.0e-5 || resolved_len_sq < 1.0e-5 { return false; } - let dot = ix * resolved.x as f64 + iz * resolved.z as f64; + let dot = intent_x * resolved.x + intent_z * resolved.z; let angle = (dot / (intent_len_sq * resolved_len_sq).sqrt()).acos(); - angle < MINOR_COLLISION_ANGLE as f64 + angle < MINOR_COLLISION_ANGLE } -fn movement_input(input: &InputState) -> (f32, f32) { - let mut forward: f32 = 0.0; - let mut strafe: f32 = 0.0; +fn movement_input(input: &InputState) -> (f64, f64) { + let mut forward = 0.0f64; + let mut strafe = 0.0f64; if input.key_pressed(KeyCode::KeyW) { forward += 1.0; diff --git a/pomme-client/src/player/interaction.rs b/pomme-client/src/player/interaction.rs index 784b0d5..ad33bde 100644 --- a/pomme-client/src/player/interaction.rs +++ b/pomme-client/src/player/interaction.rs @@ -7,14 +7,14 @@ use azalea_protocol::packets::game::ServerboundGamePacket; use azalea_protocol::packets::game::s_interact::InteractionHand; use azalea_protocol::packets::game::s_player_action::{Action, ServerboundPlayerAction}; use azalea_protocol::packets::game::s_use_item_on::{BlockHit, ServerboundUseItemOn}; -use glam::Vec3; +use glam::{DVec3, Vec3, dvec3}; use crate::app::input::InputState; +use crate::entity::components::{LookDirection, Position}; use crate::net::sender::PacketSender; use crate::world::chunk::ChunkStore; const REACH: f32 = 4.5; -const STEP: f32 = 0.01; const DESTROY_COOLDOWN: u32 = 5; const MISS_COOLDOWN: u32 = 10; const RIGHT_CLICK_DELAY: u32 = 4; @@ -24,7 +24,7 @@ const SWING_DURATION: i32 = 6; pub struct HitResult { pub block_pos: BlockPos, pub face: Direction, - pub hit_point: Vec3, + pub hit_point: DVec3, } pub struct InteractionState { @@ -114,9 +114,13 @@ impl InteractionState { self.attack_anim = self.swing_time as f32 / SWING_DURATION as f32; } - pub fn update_target(&mut self, eye: Vec3, yaw: f32, pitch: f32, chunks: &ChunkStore) { - let dir = look_direction(yaw, pitch); - self.target = raycast(eye, dir, REACH, chunks); + pub fn update_target( + &mut self, + eye_pos: Position, + look_dir: LookDirection, + chunks: &ChunkStore, + ) { + self.target = raycast(eye_pos.into(), look_dir.as_vec(), REACH, chunks); } pub fn tick( @@ -236,9 +240,9 @@ impl InteractionState { block_pos: hit.block_pos, direction: hit.face, location: azalea_core::position::Vec3 { - x: hit.hit_point.x as f64, - y: hit.hit_point.y as f64, - z: hit.hit_point.z as f64, + x: hit.hit_point.x, + y: hit.hit_point.y, + z: hit.hit_point.z, }, inside: false, world_border: false, @@ -447,59 +451,91 @@ fn mark_dirty(pos: &BlockPos, dirty: &mut Vec) } } -fn look_direction(yaw: f32, pitch: f32) -> Vec3 { - Vec3::new( - -yaw.sin() * pitch.cos(), - pitch.sin(), - -yaw.cos() * pitch.cos(), - ) -} +pub fn raycast(origin: DVec3, dir: Vec3, max_dist: f32, chunks: &ChunkStore) -> Option { + let dir = dir.as_dvec3(); + let mut bx = origin.x.floor() as i32; + let mut by = origin.y.floor() as i32; + let mut bz = origin.z.floor() as i32; + + let step_x = if dir.x > 0.0 { 1 } else { -1 }; + let step_y = if dir.y > 0.0 { 1 } else { -1 }; + let step_z = if dir.z > 0.0 { 1 } else { -1 }; -fn raycast(origin: Vec3, dir: Vec3, max_dist: f32, chunks: &ChunkStore) -> Option { - let mut t = 0.0; - let mut prev_block = BlockPos { - x: i32::MAX, - y: i32::MAX, - z: i32::MAX, + let t_delta_x = if dir.x != 0.0 { + (1.0 / dir.x).abs() + } else { + f64::INFINITY + }; + let t_delta_y = if dir.y != 0.0 { + (1.0 / dir.y).abs() + } else { + f64::INFINITY + }; + let t_delta_z = if dir.z != 0.0 { + (1.0 / dir.z).abs() + } else { + f64::INFINITY }; - while t <= max_dist { - let point = origin + dir * t; - let bx = point.x.floor() as i32; - let by = point.y.floor() as i32; - let bz = point.z.floor() as i32; - let block_pos = BlockPos { - x: bx, - y: by, - z: bz, - }; + let mut t_max_x = if dir.x > 0.0 { + (bx as f64 + 1.0 - origin.x) * t_delta_x + } else { + (origin.x - bx as f64) * t_delta_x + }; + let mut t_max_y = if dir.y > 0.0 { + (by as f64 + 1.0 - origin.y) * t_delta_y + } else { + (origin.y - by as f64) * t_delta_y + }; + let mut t_max_z = if dir.z > 0.0 { + (bz as f64 + 1.0 - origin.z) * t_delta_z + } else { + (origin.z - bz as f64) * t_delta_z + }; - if block_pos != prev_block { - let state = chunks.get_block_state(bx, by, bz); - if !state.is_air() { - let face = hit_face(origin, dir, &block_pos); - return Some(HitResult { - block_pos, - face, - hit_point: point, - }); - } - prev_block = block_pos; + let mut t = 0.0_f64; + while t <= max_dist as f64 { + let state = chunks.get_block_state(bx, by, bz); + if !state.is_air() { + let block_pos = BlockPos { + x: bx, + y: by, + z: bz, + }; + let hit_point = origin + dir * t; + let face = hit_face(origin, dir.as_vec3(), &block_pos); + return Some(HitResult { + block_pos, + face, + hit_point, + }); + } + if t_max_x < t_max_y && t_max_x < t_max_z { + t = t_max_x; + t_max_x += t_delta_x; + bx += step_x; + } else if t_max_y < t_max_z { + t = t_max_y; + t_max_y += t_delta_y; + by += step_y; + } else { + t = t_max_z; + t_max_z += t_delta_z; + bz += step_z; } - - t += STEP; } None } -fn hit_face(origin: Vec3, dir: Vec3, pos: &BlockPos) -> Direction { - let min = Vec3::new(pos.x as f32, pos.y as f32, pos.z as f32); - let max = min + Vec3::ONE; +fn hit_face(origin: DVec3, dir: Vec3, pos: &BlockPos) -> Direction { + let dir = dir.as_dvec3(); + let min = dvec3(pos.x as f64, pos.y as f64, pos.z as f64); + let max = min + DVec3::ONE; - let mut best_t = f32::MAX; + let mut best_t = f64::MAX; let mut best_face = Direction::Up; - let faces: [(f32, f32, f32, Direction); 6] = [ + let faces = [ (min.x, dir.x, origin.x, Direction::West), (max.x, dir.x, origin.x, Direction::East), (min.y, dir.y, origin.y, Direction::Down), diff --git a/pomme-client/src/player/mod.rs b/pomme-client/src/player/mod.rs index ff56e86..67c7ce2 100644 --- a/pomme-client/src/player/mod.rs +++ b/pomme-client/src/player/mod.rs @@ -2,9 +2,11 @@ pub mod interaction; pub mod inventory; pub mod tab_list; -use glam::Vec3; +use glam::dvec3; use inventory::Inventory; +use crate::entity::components::{LookDirection, Position, Velocity}; + pub const MAX_AIR_SUPPLY: i32 = 300; const DROWN_DAMAGE_THRESHOLD: i32 = -20; const DROWN_DAMAGE: f32 = 2.0; @@ -26,10 +28,11 @@ fn is_water_block(state: azalea_block::BlockState) -> bool { } pub struct LocalPlayer { - pub position: Vec3, - pub velocity: Vec3, - pub yaw: f32, - pub pitch: f32, + pub position: Position, + pub prev_position: Position, + pub velocity: Velocity, + pub look_dir: LookDirection, + pub prev_look_dir: LookDirection, pub on_ground: bool, pub health: f32, pub food: u32, @@ -54,10 +57,11 @@ pub struct LocalPlayer { impl LocalPlayer { pub fn new() -> Self { Self { - position: Vec3::ZERO, - velocity: Vec3::ZERO, - yaw: 0.0, - pitch: 0.0, + position: Position::default(), + prev_position: Position::default(), + velocity: Velocity::default(), + look_dir: LookDirection::default(), + prev_look_dir: LookDirection::default(), on_ground: false, health: 20.0, food: 20, @@ -80,6 +84,14 @@ impl LocalPlayer { } } + pub fn prev_eye_pos(&self) -> Position { + self.prev_position + dvec3(0.0, 1.62, 0.0) + } + + pub fn eye_pos(&self) -> Position { + self.position + dvec3(0.0, 1.62, 0.0) + } + // TODO: OXYGEN_BONUS attribute - chance to skip air loss per tick pub fn tick_air_supply(&mut self) { if self.eyes_in_water { @@ -94,9 +106,9 @@ impl LocalPlayer { } pub fn update_water_state(&mut self, chunks: &crate::world::chunk::ChunkStore) { - let half_w = 0.3f32; - let height = 1.8f32; - let eye_height = 1.62f32; + let half_w = 0.3; + let height = 1.8; + let eye_height = 1.62; let min_x = (self.position.x - half_w).floor() as i32; let max_x = (self.position.x + half_w).floor() as i32; diff --git a/pomme-client/src/renderer/camera.rs b/pomme-client/src/renderer/camera.rs index d951362..5e2ac11 100644 --- a/pomme-client/src/renderer/camera.rs +++ b/pomme-client/src/renderer/camera.rs @@ -1,6 +1,7 @@ -use glam::{DVec3, Mat4, Vec3}; +use glam::{FloatExt, Mat4, Vec3}; use crate::app::input::InputState; +use crate::entity::components::{LookDirection, Position}; const UP: Vec3 = Vec3::Y; pub const DEFAULT_FOV_DEGREES: f32 = 70.0; @@ -10,8 +11,7 @@ pub const MIN_FOV_DEGREES: f32 = 30.0; pub const MAX_FOV_DEGREES: f32 = 110.0; const NEAR: f32 = 0.1; const FAR: f32 = 1000.0; -const SENSITIVITY: f32 = 0.003; -const PITCH_LIMIT: f32 = std::f32::consts::FRAC_PI_2 - 0.01; +const SENSITIVITY: f32 = 0.15; pub const THIRD_PERSON_DISTANCE: f32 = 4.0; #[derive(Clone, Copy, PartialEq, Eq)] @@ -32,10 +32,8 @@ impl CameraMode { } pub struct Camera { - pub position: Vec3, - pub position_f64: DVec3, - pub yaw: f32, - pub pitch: f32, + pub position: Position, + pub look_dir: LookDirection, pub mode: CameraMode, pub third_person_dist: f32, aspect_ratio: f32, @@ -47,10 +45,8 @@ pub struct Camera { impl Camera { pub fn new(aspect_ratio: f32) -> Self { Self { - position: Vec3::new(0.0, 2.0, 5.0), - position_f64: DVec3::new(0.0, 2.0, 5.0), - yaw: 0.0, - pitch: 0.0, + position: Position::default(), + look_dir: LookDirection::default(), mode: CameraMode::FirstPerson, third_person_dist: THIRD_PERSON_DISTANCE, aspect_ratio, @@ -63,8 +59,11 @@ impl Camera { pub fn update_look(&mut self, input: &mut InputState) { if input.is_cursor_captured() { let (dx, dy) = input.consume_mouse_delta(); - self.yaw -= dx as f32 * SENSITIVITY; - self.pitch = (self.pitch - dy as f32 * SENSITIVITY).clamp(-PITCH_LIMIT, PITCH_LIMIT); + let y_rot_deg = ((self.look_dir.y_rot_deg() + dx as f32 * SENSITIVITY) + 180.0) + .rem_euclid(360.0) + - 180.0; + let x_rot_deg = self.look_dir.x_rot_deg() + dy as f32 * SENSITIVITY; + self.look_dir = LookDirection::new(y_rot_deg, x_rot_deg); } } @@ -72,21 +71,18 @@ impl Camera { self.aspect_ratio = aspect; } - pub fn set_position(&mut self, position: Vec3, yaw_degrees: f32, pitch_degrees: f32) { + pub fn reset(&mut self, position: Position, look_dir: LookDirection) { self.position = position; - self.position_f64 = DVec3::new(position.x as f64, position.y as f64, position.z as f64); - self.yaw = yaw_degrees.to_radians(); - self.pitch = pitch_degrees.to_radians(); + self.look_dir = look_dir; } - pub fn set_position_f64(&mut self, pos: DVec3) { - self.position_f64 = pos; - self.position = pos.as_vec3(); + pub fn sync_pos(&mut self, position: Position) { + self.position = position } - #[allow(dead_code)] - pub fn camera_relative_f32(&self, world_pos: DVec3) -> Vec3 { - (world_pos - self.position_f64).as_vec3() + #[allow(dead_code)] // TODO: camera relative rendering + pub fn camera_relative_f32(&self, world_pos: Position) -> Vec3 { + (world_pos - self.position).as_vec3() } pub fn update_fov_modifier(&mut self, target: f32) { @@ -96,8 +92,7 @@ impl Camera { } pub fn fov_radians(&self, partial_tick: f32) -> f32 { - let modifier = - self.old_fov_modifier + (self.fov_modifier - self.old_fov_modifier) * partial_tick; + let modifier = self.old_fov_modifier.lerp(self.fov_modifier, partial_tick); (self.base_fov_degrees * modifier).to_radians() } @@ -121,16 +116,8 @@ impl Camera { planes } - pub fn forward_vec(&self) -> Vec3 { - Vec3::new( - -self.yaw.sin() * self.pitch.cos(), - self.pitch.sin(), - -self.yaw.cos() * self.pitch.cos(), - ) - } - pub fn third_person_offset(&self) -> Vec3 { - let fwd = self.forward_vec(); + let fwd = self.look_dir.as_vec(); match self.mode { CameraMode::FirstPerson => Vec3::ZERO, CameraMode::ThirdPersonBack => -fwd * self.third_person_dist, @@ -143,29 +130,31 @@ impl Camera { } pub fn sky_view_projection(&self) -> Mat4 { - let forward = self.forward_vec(); - let look_dir = if self.mode == CameraMode::ThirdPersonFront { - -forward + let look_dir = self.look_dir.as_vec(); + let forward = if self.mode == CameraMode::ThirdPersonFront { + -look_dir } else { - forward + look_dir }; - let view = Mat4::look_to_rh(Vec3::ZERO, look_dir, UP); + + let view = Mat4::look_to_rh(Vec3::ZERO, forward, UP); let mut proj = Mat4::perspective_rh(self.fov_radians(1.0), self.aspect_ratio, NEAR, FAR); - proj.y_axis.y *= -1.0; + proj.y_axis.y *= -1.0; // Vulkan NDC has +Y down proj * view } pub fn view_projection_with_fov(&self, fov: f32) -> Mat4 { - let forward = self.forward_vec(); let offset = self.third_person_offset(); - let look_dir = if self.mode == CameraMode::ThirdPersonFront { - -forward + let look_dir = self.look_dir.as_vec(); + let forward = if self.mode == CameraMode::ThirdPersonFront { + -look_dir } else { - forward + look_dir }; - let view = Mat4::look_to_rh(offset, look_dir, UP); + + let view = Mat4::look_to_rh(offset, forward, UP); let mut proj = Mat4::perspective_rh(fov, self.aspect_ratio, NEAR, FAR); - proj.y_axis.y *= -1.0; + proj.y_axis.y *= -1.0; // Vulkan NDC has +Y down proj * view } } @@ -181,7 +170,7 @@ pub struct CameraUniform { impl CameraUniform { pub fn new(camera: &Camera, fog_color: [f32; 3]) -> Self { let offset = camera.third_person_offset(); - let pos = camera.position + offset; + let pos = camera.position.as_vec3() + offset; Self { view_proj: camera.view_projection().to_cols_array_2d(), camera_pos: [pos.x, pos.y, pos.z, 0.0], diff --git a/pomme-client/src/renderer/entity_model.rs b/pomme-client/src/renderer/entity_model.rs index 1fd7ace..58f98d4 100644 --- a/pomme-client/src/renderer/entity_model.rs +++ b/pomme-client/src/renderer/entity_model.rs @@ -1,4 +1,4 @@ -use glam::{Mat4, Vec3}; +use glam::{Mat4, Quat, Vec3}; use super::chunk::mesher::ChunkVertex; @@ -735,8 +735,8 @@ pub fn bake_baby_sheep_wool_model() -> BakedEntityModel { pub fn compute_humanoid_anim( model: &BakedEntityModel, - head_pitch: f32, - head_yaw: f32, + head_x_rot_deg: f32, + local_head_y_rot_deg: f32, walk_pos: f32, walk_speed: f32, ) -> PartAnim { @@ -744,7 +744,12 @@ pub fn compute_humanoid_anim( for (i, part) in model.parts.iter().enumerate() { let rot = match part.name.as_str() { - "head" => Vec3::new(head_pitch.to_radians(), head_yaw.to_radians(), 0.0), + "head" => { + let rot = Quat::from_rotation_y(local_head_y_rot_deg.to_radians()) + * Quat::from_rotation_x(head_x_rot_deg.to_radians()); + let (x, y, z) = rot.to_euler(glam::EulerRot::XYZ); + Vec3::new(x, y, z) + } "right_arm" => Vec3::new( (walk_pos * 0.6662 + std::f32::consts::PI).cos() * 2.0 * walk_speed * 0.5, 0.0, @@ -767,22 +772,27 @@ pub fn compute_humanoid_anim( pub fn compute_quadruped_anim( model: &BakedEntityModel, - head_pitch: f32, - head_yaw: f32, + head_x_rot_deg: f32, + local_head_y_rot_deg: f32, walk_pos: f32, walk_speed: f32, head_y_offset: f32, - head_x_rot_override: Option, + head_x_rot_deg_override: Option, ) -> PartAnim { let mut anim = PartAnim::default(); for (i, part) in model.parts.iter().enumerate() { let rot = match part.name.as_str() { - "head" => Vec3::new( - head_x_rot_override.unwrap_or_else(|| head_pitch.to_radians()), - head_yaw.to_radians(), - 0.0, - ), + "head" => { + let rot = Quat::from_rotation_y(local_head_y_rot_deg.to_radians()) + * Quat::from_rotation_x( + head_x_rot_deg_override + .unwrap_or(head_x_rot_deg) + .to_radians(), + ); + let (x, y, z) = rot.to_euler(glam::EulerRot::XYZ); + Vec3::new(x, y, z) + } "right_hind_leg" => Vec3::new((walk_pos * 0.6662).cos() * 1.4 * walk_speed, 0.0, 0.0), "left_hind_leg" => Vec3::new( (walk_pos * 0.6662 + std::f32::consts::PI).cos() * 1.4 * walk_speed, diff --git a/pomme-client/src/renderer/mod.rs b/pomme-client/src/renderer/mod.rs index b83b841..45f4956 100644 --- a/pomme-client/src/renderer/mod.rs +++ b/pomme-client/src/renderer/mod.rs @@ -19,6 +19,7 @@ use chunk::atlas::TextureAtlas; use chunk::buffer::ChunkBufferStore; use chunk::mesher::{ChunkMeshData, MeshDispatcher}; use context::VulkanContext; +use glam::dvec3; use pipelines::block_overlay::BlockOverlayPipeline; use pipelines::blur::BlurPipeline; use pipelines::chunk::ChunkPipeline; @@ -37,6 +38,7 @@ use winit::window::Window; use crate::app::input::InputState; use crate::assets::AssetIndex; +use crate::entity::components::{LookDirection, Position}; use crate::renderer::pipelines::chunk_borders::ChunkBorderPipeline; use crate::renderer::pipelines::item_entity::ItemEntityPipeline; use crate::world::block::registry::BlockRegistry; @@ -557,39 +559,42 @@ impl Renderer { self.camera.update_look(input); } - pub fn sync_camera_to_player(&mut self, eye_pos: glam::DVec3, yaw: f32, pitch: f32) { - self.camera.set_position_f64(eye_pos); - self.camera.yaw = yaw; - self.camera.pitch = pitch; + pub fn sync_camera_pos(&mut self, position: Position) { + self.camera.sync_pos(position); + } + + pub fn reset_camera(&mut self, position: Position, look_dir: LookDirection) { + self.camera.reset(position, look_dir); } pub fn update_third_person_distance( &mut self, - eye_pos: glam::Vec3, + eye_pos: Position, chunks: &crate::world::chunk::ChunkStore, ) { if self.camera.mode == camera::CameraMode::FirstPerson { return; } - let max = camera::THIRD_PERSON_DISTANCE; - let fwd = self.camera.forward_vec(); + let max = camera::THIRD_PERSON_DISTANCE as f64; + let fwd = self.camera.look_dir.as_vec().as_dvec3(); let dir = if self.camera.mode == camera::CameraMode::ThirdPersonFront { fwd } else { -fwd }; + let mut dist = max; let m = 0.4; let corners = [ - glam::Vec3::new(m, m, m), - glam::Vec3::new(m, m, -m), - glam::Vec3::new(m, -m, m), - glam::Vec3::new(m, -m, -m), - glam::Vec3::new(-m, m, m), - glam::Vec3::new(-m, m, -m), - glam::Vec3::new(-m, -m, m), - glam::Vec3::new(-m, -m, -m), + dvec3(m, m, m), + dvec3(m, m, -m), + dvec3(m, -m, m), + dvec3(m, -m, -m), + dvec3(-m, m, m), + dvec3(-m, m, -m), + dvec3(-m, -m, m), + dvec3(-m, -m, -m), ]; let step = 0.2; @@ -612,10 +617,10 @@ impl Renderer { t += step; } - self.camera.third_person_dist = dist.max(0.5); + self.camera.third_person_dist = dist.max(0.5) as f32; } - pub fn update_fov(&mut self, modifier: f32) { + pub fn update_fov_mod(&mut self, modifier: f32) { self.camera.update_fov_modifier(modifier); } @@ -623,12 +628,12 @@ impl Renderer { self.camera.base_fov_degrees = degrees; } - pub fn camera_yaw(&self) -> f32 { - self.camera.yaw + pub fn camera_look_dir(&self) -> LookDirection { + self.camera.look_dir } - pub fn camera_pitch(&self) -> f32 { - self.camera.pitch + pub fn camera_pivot_position(&self) -> Position { + self.camera.position } pub fn cycle_camera_mode(&mut self) { @@ -651,12 +656,6 @@ impl Renderer { self.chunk_buffers.chunk_count() } - pub fn set_camera_position(&mut self, x: f64, y: f64, z: f64, yaw: f32, pitch: f32) { - self.camera - .set_position(glam::Vec3::new(x as f32, y as f32, z as f32), yaw, pitch); - self.camera.position_f64 = glam::DVec3::new(x, y, z); - } - pub fn wait_for_all_frames(&self) { let _ = self .ctx @@ -705,9 +704,8 @@ impl Renderer { ) } - #[allow(clippy::too_many_arguments)] pub fn update_chunk_borders(&mut self, min_y: i32, max_y: i32) { - let cam = self.camera.position; + let cam = self.camera.position.as_vec3(); self.chunk_border_pipeline .update_lines(cam.x, cam.y, cam.z, min_y, max_y); } @@ -952,11 +950,7 @@ impl Renderer { if matches!(&mode, RenderMode::World { .. }) { let frustum = self.camera.frustum_planes(); - let cam_pos = [ - self.camera.position.x, - self.camera.position.y, - self.camera.position.z, - ]; + let cam_pos = self.camera.position.as_vec3().into(); self.chunk_buffers .dispatch_cull(cmd, frame, &frustum, cam_pos); } diff --git a/pomme-client/src/renderer/pipelines/entity_renderer.rs b/pomme-client/src/renderer/pipelines/entity_renderer.rs index a1382a0..0616fc7 100644 --- a/pomme-client/src/renderer/pipelines/entity_renderer.rs +++ b/pomme-client/src/renderer/pipelines/entity_renderer.rs @@ -8,6 +8,7 @@ use pomme_gpu_allocator::vulkan::{Allocation, Allocator}; use pyronyx::vk; use crate::assets::{AssetIndex, resolve_asset_path}; +use crate::entity::components::Position; use crate::renderer::camera::CameraUniform; use crate::renderer::chunk::mesher::ChunkVertex; use crate::renderer::entity_model::BakedEntityModel; @@ -16,12 +17,10 @@ use crate::renderer::{MAX_FRAMES_IN_FLIGHT, entity_model, shader, util}; pub const MAX_OVERLAYS: usize = 2; pub struct EntityRenderInfo { - pub x: f64, - pub y: f64, - pub z: f64, - pub yaw: f32, - pub pitch: f32, - pub head_yaw: f32, + pub position: Position, + pub head_x_rot_deg: f32, + pub head_y_rot_deg: f32, + pub body_y_rot_deg: f32, pub is_baby: bool, pub walk_anim_pos: f32, pub walk_anim_speed: f32, @@ -29,7 +28,7 @@ pub struct EntityRenderInfo { pub variant_index: u32, pub overlay_tints: [Option<[f32; 4]>; MAX_OVERLAYS], pub head_y_offset: f32, - pub head_x_rot_override: Option, + pub head_x_rot_deg_override: Option, } struct MobVariant { @@ -466,26 +465,23 @@ impl EntityRenderer { }; let variant = entry.base_variant(info.is_baby, info.variant_index); - let entity_mat = glam::Mat4::from_translation(glam::Vec3::new( - info.x as f32, - info.y as f32, - info.z as f32, - )) * glam::Mat4::from_rotation_y((180.0f32 - info.yaw).to_radians()); + let entity_mat = glam::Mat4::from_translation(info.position.as_vec3()) + * glam::Mat4::from_rotation_y((180.0 - info.body_y_rot_deg).to_radians()); let anim = match entry.anim { AnimationType::Quadruped => entity_model::compute_quadruped_anim( &variant.model, - info.pitch, - info.head_yaw - info.yaw, + info.head_x_rot_deg, + info.head_y_rot_deg - info.body_y_rot_deg, info.walk_anim_pos, info.walk_anim_speed, info.head_y_offset, - info.head_x_rot_override, + info.head_x_rot_deg_override, ), AnimationType::Humanoid => entity_model::compute_humanoid_anim( &variant.model, - info.pitch, - info.head_yaw - info.yaw, + info.head_x_rot_deg, + info.head_y_rot_deg - info.body_y_rot_deg, info.walk_anim_pos, info.walk_anim_speed, ), diff --git a/pomme-client/src/renderer/pipelines/skin_preview.rs b/pomme-client/src/renderer/pipelines/skin_preview.rs index 9f65383..84fb6f1 100644 --- a/pomme-client/src/renderer/pipelines/skin_preview.rs +++ b/pomme-client/src/renderer/pipelines/skin_preview.rs @@ -318,14 +318,14 @@ impl SkinPreviewPipeline { let _ = device; let center_px_x = screen_x * screen_w; let center_px_y = screen_y * screen_h; - let body_yaw_raw = ((mouse_px_x - center_px_x) / 40.0).atan(); - let head_pitch_raw = ((mouse_px_y - center_px_y) / 40.0).atan(); + let body_y_rot_raw = ((mouse_px_x - center_px_x) / 40.0).atan(); + let head_x_rot_raw = ((mouse_px_y - center_px_y) / 40.0).atan(); - let head_yaw_deg = body_yaw_raw * 40.0; - let body_yaw_deg = head_yaw_deg * 0.3; - let body_rot = std::f32::consts::PI + body_yaw_deg.to_radians(); - let head_yaw = head_yaw_deg.to_radians(); - let head_pitch = head_pitch_raw * 20.0f32.to_radians(); + let head_y_rot_deg = body_y_rot_raw * 40.0; + let body_y_rot_deg = head_y_rot_deg * 0.3; + let body_rot_rad = std::f32::consts::PI + body_y_rot_deg.to_radians(); + let head_y_rot_rad = head_y_rot_deg.to_radians(); + let head_x_rot_rad = head_x_rot_raw * 20.0f32.to_radians(); let fov = 0.6f32; let mut proj = Mat4::perspective_rh(fov, aspect, 0.1, 100.0); @@ -339,12 +339,12 @@ impl SkinPreviewPipeline { let target = Vec3::ZERO; let view = Mat4::look_at_rh(eye, target, Vec3::Y); - let body_rot_mat = Mat4::from_rotation_y(body_rot); + let body_rot_mat = Mat4::from_rotation_y(body_rot_rad); let vp = clip_offset * proj * view; let body_mvp = vp * body_rot_mat; - let head_rot_mat = - Mat4::from_rotation_y(body_rot + head_yaw) * Mat4::from_rotation_x(head_pitch); + let head_rot_mat = Mat4::from_rotation_y(body_rot_rad + head_y_rot_rad) + * Mat4::from_rotation_x(head_x_rot_rad); let head_mvp = vp * head_rot_mat; // Right arm swing animation diff --git a/pomme-client/src/ui/hud.rs b/pomme-client/src/ui/hud.rs index 2406a0b..d710664 100644 --- a/pomme-client/src/ui/hud.rs +++ b/pomme-client/src/ui/hud.rs @@ -1,5 +1,6 @@ use azalea_core::position::BlockPos; use azalea_inventory::ItemStack; +use glam::DVec3; use super::common::{FONT_SIZE, WHITE, push_item_count}; use crate::player::inventory::item_resource_name; @@ -16,9 +17,9 @@ pub struct FrameTimings { pub struct DebugInfo<'a> { pub fps: u32, - pub position: glam::Vec3, - pub yaw: f32, - pub pitch: f32, + pub position: DVec3, + pub y_rot_deg: f32, + pub x_rot_deg: f32, pub target_block: Option<(BlockPos, azalea_core::direction::Direction, String)>, pub chunk_count: u32, pub gpu_name: &'a str, @@ -365,9 +366,9 @@ fn build_debug_overlay(elements: &mut Vec, info: &DebugInfo<'_>, gs let bz = pos.z.floor() as i32; let cx = bx.div_euclid(16); let cz = bz.div_euclid(16); - let facing = facing_name(info.yaw); - let yaw_deg = info.yaw.to_degrees(); - let pitch_deg = info.pitch.to_degrees(); + let facing = facing_name(info.y_rot_deg); + let y_rot_deg = info.y_rot_deg; + let x_rot_deg = info.x_rot_deg; let mut left_lines: Vec = vec![ format!("Pomme ({}fps)", info.fps), @@ -381,7 +382,7 @@ fn build_debug_overlay(elements: &mut Vec, info: &DebugInfo<'_>, gs cx, cz ), - format!("Facing: {} ({:.1} / {:.1})", facing, yaw_deg, pitch_deg), + format!("Facing: {} ({:.1} / {:.1})", facing, y_rot_deg, x_rot_deg), String::new(), format!("Chunks: {} loaded", info.chunk_count), ]; @@ -447,10 +448,10 @@ fn push_debug_lines( } } -fn facing_name(yaw: f32) -> &'static str { - let deg = yaw.to_degrees().rem_euclid(360.0); - match deg as u32 { - 315..=360 | 0..=44 => "South (+Z)", +fn facing_name(y_rot_deg: f32) -> &'static str { + let deg = y_rot_deg.rem_euclid(360.0) as u32; + match deg { + 315..=359 | 0..=44 => "South (+Z)", 45..=134 => "West (-X)", 135..=224 => "North (-Z)", 225..=314 => "East (+X)",