From 8b9e84972ac6e165e1129dc1ca050a1ae679088b Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Thu, 11 Mar 2021 11:48:59 -0500 Subject: [PATCH 01/41] Draft of airships (spawn command, visuals, some physics refactoring, no collision yet). --- assets/common/npc_names.ron | 9 + assets/server/manifests/ship_manifest.ron | 16 + assets/server/voxel/Human_Airship.vox | 3 + assets/server/voxel/airship.vox | 3 + assets/server/voxel/propeller-l.vox | 3 + assets/server/voxel/propeller-r.vox | 3 + assets/voxygen/voxel/object/Human_Airship.vox | 3 + assets/voxygen/voxel/object/airship.vox | 1 + assets/voxygen/voxel/object/propeller-l.vox | 1 + assets/voxygen/voxel/object/propeller-r.vox | 1 + common/src/cmd.rs | 4 + common/src/comp/agent.rs | 1 + common/src/comp/body.rs | 12 +- common/src/comp/body/ship.rs | 69 + common/src/comp/mod.rs | 2 +- common/src/comp/phys.rs | 9 +- common/src/states/utils.rs | 6 +- common/src/util/find_dist.rs | 2 +- common/sys/src/phys.rs | 1230 ++++++++++------- server/src/cmd.rs | 34 + server/src/events/inventory_manip.rs | 2 +- server/src/state_ext.rs | 13 + server/src/sys/sentinel.rs | 2 +- voxygen/anim/src/lib.rs | 1 + voxygen/anim/src/ship/idle.rs | 34 + voxygen/anim/src/ship/mod.rs | 71 + voxygen/src/render/renderer.rs | 2 +- voxygen/src/scene/figure/load.rs | 110 +- voxygen/src/scene/figure/mod.rs | 108 +- voxygen/src/session.rs | 8 +- 30 files changed, 1211 insertions(+), 552 deletions(-) create mode 100644 assets/server/manifests/ship_manifest.ron create mode 100644 assets/server/voxel/Human_Airship.vox create mode 100644 assets/server/voxel/airship.vox create mode 100644 assets/server/voxel/propeller-l.vox create mode 100644 assets/server/voxel/propeller-r.vox create mode 100644 assets/voxygen/voxel/object/Human_Airship.vox create mode 120000 assets/voxygen/voxel/object/airship.vox create mode 120000 assets/voxygen/voxel/object/propeller-l.vox create mode 120000 assets/voxygen/voxel/object/propeller-r.vox create mode 100644 common/src/comp/body/ship.rs create mode 100644 voxygen/anim/src/ship/idle.rs create mode 100644 voxygen/anim/src/ship/mod.rs diff --git a/assets/common/npc_names.ron b/assets/common/npc_names.ron index 49ceb5c023..aeefb77fee 100644 --- a/assets/common/npc_names.ron +++ b/assets/common/npc_names.ron @@ -969,6 +969,15 @@ ), species: () ), + ship: ( + body: ( + keyword: "ship", + names_0: [ + "Boaty McBoatface", + ], + ), + species: (), + ), biped_small: ( body: ( keyword: "biped_small", diff --git a/assets/server/manifests/ship_manifest.ron b/assets/server/manifests/ship_manifest.ron new file mode 100644 index 0000000000..42555fc051 --- /dev/null +++ b/assets/server/manifests/ship_manifest.ron @@ -0,0 +1,16 @@ +({ + DefaultAirship: ( + bone0: ( + offset: (-20.0, -35.0, 1.0), + central: ("object.Human_Airship"), + ), + bone1: ( + offset: (0.0, 0.0, 0.0), + central: ("propeller-l"), + ), + bone2: ( + offset: (0.0, 0.0, 0.0), + central: ("propeller-r"), + ), + ), +}) diff --git a/assets/server/voxel/Human_Airship.vox b/assets/server/voxel/Human_Airship.vox new file mode 100644 index 0000000000..ab1ea75279 --- /dev/null +++ b/assets/server/voxel/Human_Airship.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6355ef90e28c448e11ad79dc6311388a71b4e46628bb2cf82b6e5fd5f38cd254 +size 88100 diff --git a/assets/server/voxel/airship.vox b/assets/server/voxel/airship.vox new file mode 100644 index 0000000000..06bebaa938 --- /dev/null +++ b/assets/server/voxel/airship.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86f317298900ea98f95c6a33192b25fbbcbd3ce5f105cad58ad3c595a7a7d9ee +size 70176 diff --git a/assets/server/voxel/propeller-l.vox b/assets/server/voxel/propeller-l.vox new file mode 100644 index 0000000000..a193fa89ee --- /dev/null +++ b/assets/server/voxel/propeller-l.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09ef4bad2557abcc5a2b938f21053babc7770ebe2333039aef9b98ba930b7ec7 +size 1584 diff --git a/assets/server/voxel/propeller-r.vox b/assets/server/voxel/propeller-r.vox new file mode 100644 index 0000000000..5b940751e6 --- /dev/null +++ b/assets/server/voxel/propeller-r.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e4947977524b88bc5adfa934d9061a3499e94b960abb3bcf0a3e2aca482096dc +size 1584 diff --git a/assets/voxygen/voxel/object/Human_Airship.vox b/assets/voxygen/voxel/object/Human_Airship.vox new file mode 100644 index 0000000000..5d32a3168a --- /dev/null +++ b/assets/voxygen/voxel/object/Human_Airship.vox @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:561dcec86218a94ae85268b3f1863cd3310e24c452dd2ba248e3c37b6aff45e5 +size 78024 diff --git a/assets/voxygen/voxel/object/airship.vox b/assets/voxygen/voxel/object/airship.vox new file mode 120000 index 0000000000..3479493953 --- /dev/null +++ b/assets/voxygen/voxel/object/airship.vox @@ -0,0 +1 @@ +../../../server/voxel/airship.vox \ No newline at end of file diff --git a/assets/voxygen/voxel/object/propeller-l.vox b/assets/voxygen/voxel/object/propeller-l.vox new file mode 120000 index 0000000000..a8105d8b1b --- /dev/null +++ b/assets/voxygen/voxel/object/propeller-l.vox @@ -0,0 +1 @@ +../../../server/voxel/propeller-l.vox \ No newline at end of file diff --git a/assets/voxygen/voxel/object/propeller-r.vox b/assets/voxygen/voxel/object/propeller-r.vox new file mode 120000 index 0000000000..647f3f66d0 --- /dev/null +++ b/assets/voxygen/voxel/object/propeller-r.vox @@ -0,0 +1 @@ +../../../server/voxel/propeller-r.vox \ No newline at end of file diff --git a/common/src/cmd.rs b/common/src/cmd.rs index f6771bdcd3..a74f0cab09 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -36,6 +36,7 @@ impl ChatCommandData { #[derive(Copy, Clone)] pub enum ChatCommand { Adminify, + Airship, Alias, Ban, Build, @@ -89,6 +90,7 @@ pub enum ChatCommand { // Thank you for keeping this sorted alphabetically :-) pub static CHAT_COMMANDS: &[ChatCommand] = &[ ChatCommand::Adminify, + ChatCommand::Airship, ChatCommand::Alias, ChatCommand::Ban, ChatCommand::Build, @@ -222,6 +224,7 @@ impl ChatCommand { "Temporarily gives a player admin permissions or removes them", Admin, ), + ChatCommand::Airship => cmd(vec![], "Spawns an airship", Admin), ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin), ChatCommand::Ban => cmd( vec![Any("username", Required), Message(Optional)], @@ -449,6 +452,7 @@ impl ChatCommand { pub fn keyword(&self) -> &'static str { match self { ChatCommand::Adminify => "adminify", + ChatCommand::Airship => "airship", ChatCommand::Alias => "alias", ChatCommand::Ban => "ban", ChatCommand::Build => "build", diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 7d6c5a57df..9134e40091 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -168,6 +168,7 @@ impl<'a> From<&'a Body> for Psyche { Body::Golem(_) => 1.0, Body::Theropod(_) => 1.0, Body::Dragon(_) => 1.0, + Body::Ship(_) => 1.0, }, } } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index bb331f3a54..1733eec1e1 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -11,6 +11,7 @@ pub mod object; pub mod quadruped_low; pub mod quadruped_medium; pub mod quadruped_small; +pub mod ship; pub mod theropod; use crate::{ @@ -44,6 +45,7 @@ make_case_elim!( Golem(body: golem::Body) = 11, Theropod(body: theropod::Body) = 12, QuadrupedLow(body: quadruped_low::Body) = 13, + Ship(body: ship::Body) = 14, } ); @@ -78,6 +80,7 @@ pub struct AllBodies { pub golem: BodyData>, pub theropod: BodyData>, pub quadruped_low: BodyData>, + pub ship: BodyData, } /// Can only retrieve body metadata by direct index. @@ -124,6 +127,7 @@ impl<'a, BodyMeta, SpeciesMeta> core::ops::Index<&'a Body> for AllBodies &self.golem.body, Body::Theropod(_) => &self.theropod.body, Body::QuadrupedLow(_) => &self.quadruped_low.body, + Body::Ship(_) => &self.ship.body, } } } @@ -218,6 +222,7 @@ impl Body { Body::Golem(_) => 2.5, Body::BipedSmall(_) => 0.75, Body::Object(_) => 0.4, + Body::Ship(_) => 1.0, } } @@ -294,6 +299,7 @@ impl Body { object::Body::Crossbow => 1.7, _ => 1.0, }, + Body::Ship(_) => 1.0, } } @@ -416,6 +422,7 @@ impl Body { quadruped_low::Species::Deadwood => 600, _ => 200, }, + Body::Ship(_) => 10000, } } @@ -508,12 +515,13 @@ impl Body { quadruped_low::Species::Deadwood => 30, _ => 20, }, + Body::Ship(_) => 500, } } pub fn immune_to(&self, buff: BuffKind) -> bool { match buff { - BuffKind::Bleeding => matches!(self, Body::Object(_) | Body::Golem(_)), + BuffKind::Bleeding => matches!(self, Body::Object(_) | Body::Golem(_) | Body::Ship(_)), _ => false, } } @@ -521,7 +529,7 @@ impl Body { /// Returns a multiplier representing increased difficulty not accounted for /// due to AI or not using an actual weapon // TODO: Match on species - pub fn combat_multiplier(&self) -> f32 { if let Body::Object(_) = self { 0.0 } else { 1.0 } } + pub fn combat_multiplier(&self) -> f32 { if let Body::Object(_) | Body::Ship(_) = self { 0.0 } else { 1.0 } } pub fn base_poise(&self) -> u32 { match self { diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs new file mode 100644 index 0000000000..ef2e93e113 --- /dev/null +++ b/common/src/comp/body/ship.rs @@ -0,0 +1,69 @@ +use crate::{ + make_case_elim +}; +use serde::{Deserialize, Serialize}; + +make_case_elim!( + body, + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] + #[repr(u32)] + pub enum Body { + DefaultAirship = 0, + } +); + +impl From for super::Body { + fn from(body: Body) -> Self { super::Body::Ship(body) } +} + +impl Body { + pub fn manifest_id(&self) -> &'static str { + match self { + Body::DefaultAirship => "server.manifests.ship_manifest", + } + } +} + +/// Duplicate of some of the things defined in `voxygen::scene::figure::load` to avoid having to +/// refactor all of that to `common` for using voxels as collider geometry +pub mod figuredata { + use crate::{ + assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron}, + volumes::dyna::Dyna, + }; + use serde::Deserialize; + use hashbrown::HashMap; + + #[derive(Deserialize)] + pub struct VoxSimple(pub String); + + #[derive(Deserialize)] + pub struct ShipCentralSpec(pub HashMap); + + #[derive(Deserialize)] + pub struct SidedShipCentralVoxSpec { + pub bone0: ShipCentralSubSpec, + pub bone1: ShipCentralSubSpec, + pub bone2: ShipCentralSubSpec, + } + + #[derive(Deserialize)] + pub struct ShipCentralSubSpec { + pub offset: [f32; 3], + pub central: VoxSimple, + } + + /// manual instead of through `make_vox_spec!` so that it can be in `common` + #[derive(Clone)] + pub struct ShipSpec { + pub central: AssetHandle>, + } + + impl assets::Compound for ShipSpec { + fn load(_: &assets::AssetCache, _: &str) -> Result { + Ok(ShipSpec { + central: AssetExt::load("server.manifests.ship_manifest")? + }) + } + } +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index e493a527c9..d97e265919 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -48,7 +48,7 @@ pub use self::{ beam::{Beam, BeamSegment}, body::{ biped_large, biped_small, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, - humanoid, object, quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, + humanoid, object, quadruped_low, quadruped_medium, quadruped_small, theropod, ship, AllBodies, Body, BodyData, }, buff::{ diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index fdb8d10a05..3e06418792 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -55,9 +55,12 @@ impl Component for Mass { type Storage = DerefFlaggedStorage>; } -// Mass -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +// Collider +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Collider { + // TODO: pass the map from ids -> voxel data to get_radius and get_z_limits to compute a + // bounding cylinder + Voxel { id: String }, Box { radius: f32, z_min: f32, z_max: f32 }, Point, } @@ -65,6 +68,7 @@ pub enum Collider { impl Collider { pub fn get_radius(&self) -> f32 { match self { + Collider::Voxel { .. } => 0.0, Collider::Box { radius, .. } => *radius, Collider::Point => 0.0, } @@ -72,6 +76,7 @@ impl Collider { pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) { match self { + Collider::Voxel { .. } => (0.0, 0.0), Collider::Box { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier), Collider::Point => (0.0, 0.0), } diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index bc43bb7643..ea780a1a38 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -5,7 +5,7 @@ use crate::{ item::{Hands, ItemKind, Tool, ToolKind}, quadruped_low, quadruped_medium, quadruped_small, skills::Skill, - theropod, Body, CharacterAbility, CharacterState, InputKind, InventoryAction, StateUpdate, + theropod, ship, Body, CharacterAbility, CharacterState, InputKind, InventoryAction, StateUpdate, }, consts::{FRIC_GROUND, GRAVITY}, event::{LocalEvent, ServerEvent}, @@ -117,6 +117,7 @@ impl Body { quadruped_low::Species::Basilisk => 120.0, quadruped_low::Species::Deadwood => 140.0, }, + Body::Ship(_) => 30.0, } } @@ -168,13 +169,14 @@ impl Body { quadruped_low::Species::Lavadrake => 4.0, _ => 6.0, }, + Body::Ship(_) => 10.0, } } pub fn can_fly(&self) -> bool { matches!( self, - Body::BirdMedium(_) | Body::Dragon(_) | Body::BirdSmall(_) + Body::BirdMedium(_) | Body::Dragon(_) | Body::BirdSmall(_) | Body::Ship(ship::Body::DefaultAirship) ) } diff --git a/common/src/util/find_dist.rs b/common/src/util/find_dist.rs index cb02c728fe..fc921bc758 100644 --- a/common/src/util/find_dist.rs +++ b/common/src/util/find_dist.rs @@ -39,7 +39,7 @@ impl Cylinder { char_state: Option<&crate::comp::CharacterState>, ) -> Self { let scale = scale.map_or(1.0, |s| s.0); - let radius = collider.map_or(0.5, |c| c.get_radius()) * scale; + let radius = collider.as_ref().map_or(0.5, |c| c.get_radius()) * scale; let z_limit_modifier = char_state .filter(|char_state| char_state.is_dodge()) .map_or(1.0, |_| 0.5) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index cb0ee3cd51..8545888c7e 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -8,12 +8,16 @@ use common::{ resources::DeltaTime, terrain::{Block, TerrainGrid}, uid::Uid, - vol::ReadVol, + vol::{BaseVol, ReadVol}, }; use common_base::{prof_span, span}; use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System}; +use hashbrown::HashMap; use rayon::iter::ParallelIterator; -use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage}; +use specs::{ + shred::{World, ResourceId}, + Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage, SystemData, +}; use std::ops::Range; use vek::*; @@ -62,112 +66,78 @@ fn calc_z_limit( #[derive(Default)] pub struct Sys; -impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] - type SystemData = ( - Entities<'a>, - ReadStorage<'a, Uid>, - ReadExpect<'a, TerrainGrid>, - Read<'a, DeltaTime>, - WriteExpect<'a, PhysicsMetrics>, - Read<'a, EventBus>, - ReadStorage<'a, Scale>, - ReadStorage<'a, Sticky>, - ReadStorage<'a, Mass>, - ReadStorage<'a, Collider>, - ReadStorage<'a, Gravity>, - WriteStorage<'a, PhysicsState>, - WriteStorage<'a, Pos>, - WriteStorage<'a, Vel>, - WriteStorage<'a, Ori>, - WriteStorage<'a, PreviousPhysCache>, - ReadStorage<'a, Mounting>, - ReadStorage<'a, Projectile>, - ReadStorage<'a, BeamSegment>, - ReadStorage<'a, Shockwave>, - ReadStorage<'a, CharacterState>, - ); +#[derive(SystemData)] +pub struct PhysicsSystemDataRead<'a> { + entities: Entities<'a>, + uids: ReadStorage<'a, Uid>, + terrain: ReadExpect<'a, TerrainGrid>, + dt: Read<'a, DeltaTime>, + event_bus: Read<'a, EventBus>, + scales: ReadStorage<'a, Scale>, + stickies: ReadStorage<'a, Sticky>, + masses: ReadStorage<'a, Mass>, + colliders: ReadStorage<'a, Collider>, + gravities: ReadStorage<'a, Gravity>, + mountings: ReadStorage<'a, Mounting>, + projectiles: ReadStorage<'a, Projectile>, + beams: ReadStorage<'a, BeamSegment>, + shockwaves: ReadStorage<'a, Shockwave>, + char_states: ReadStorage<'a, CharacterState>, +} - const NAME: &'static str = "phys"; - const ORIGIN: Origin = Origin::Common; - const PHASE: Phase = Phase::Create; +#[derive(SystemData)] +pub struct PhysicsSystemDataWrite<'a> { + physics_metrics: WriteExpect<'a, PhysicsMetrics>, + physics_states: WriteStorage<'a, PhysicsState>, + positions: WriteStorage<'a, Pos>, + velocities: WriteStorage<'a, Vel>, + orientations: WriteStorage<'a, Ori>, + previous_phys_cache: WriteStorage<'a, PreviousPhysCache>, +} - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 - #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 - fn run( - job: &mut Job, - ( - entities, - uids, - terrain, - dt, - mut physics_metrics, - event_bus, - scales, - stickies, - masses, - colliders, - gravities, - mut physics_states, - mut positions, - mut velocities, - mut orientations, - mut previous_phys_cache, - mountings, - projectiles, - beams, - shockwaves, - char_states, - ): Self::SystemData, - ) { - let mut event_emitter = event_bus.emitter(); +#[derive(SystemData)] +pub struct PhysicsSystemData<'a> { + r: PhysicsSystemDataRead<'a>, + w: PhysicsSystemDataWrite<'a>, +} - // Add/reset physics state components +impl<'a> PhysicsSystemData<'a> { + /// Add/reset physics state components + fn reset(&mut self) { span!(guard, "Add/reset physics state components"); for (entity, _, _, _, _) in ( - &entities, - &colliders, - &positions, - &velocities, - &orientations, + &self.r.entities, + &self.r.colliders, + &self.w.positions, + &self.w.velocities, + &self.w.orientations, ) .join() { - let _ = physics_states + let _ = self.w.physics_states .entry(entity) .map(|e| e.or_insert_with(Default::default)); } drop(guard); + } - // Apply pushback - // - // Note: We now do this first because we project velocity ahead. This is slighty - // imperfect and implies that we might get edge-cases where entities - // standing right next to the edge of a wall may get hit by projectiles - // fired into the wall very close to them. However, this sort of thing is - // already possible with poorly-defined hitboxes anyway so it's not too - // much of a concern. - // - // If this situation becomes a problem, this code should be integrated with the - // terrain collision code below, although that's not trivial to do since - // it means the step needs to take into account the speeds of both - // entities. + fn maintain_pushback_cache(&mut self) { span!(guard, "Maintain pushback cache"); //Add PreviousPhysCache for all relevant entities for entity in ( - &entities, - &velocities, - &positions, - !&previous_phys_cache, - !&mountings, - !&beams, - !&shockwaves, + &self.r.entities, + &self.w.velocities, + &self.w.positions, + !&self.w.previous_phys_cache, + !&self.r.mountings, + !&self.r.beams, + !&self.r.shockwaves, ) .join() .map(|(e, _, _, _, _, _, _)| e) .collect::>() { - let _ = previous_phys_cache.insert(entity, PreviousPhysCache { + let _ = self.w.previous_phys_cache.insert(entity, PreviousPhysCache { velocity_dt: Vec3::zero(), center: Vec3::zero(), collision_boundary: 0.0, @@ -178,16 +148,16 @@ impl<'a> System<'a> for Sys { //Update PreviousPhysCache for (_, vel, position, mut phys_cache, collider, scale, cs, _, _, _) in ( - &entities, - &velocities, - &positions, - &mut previous_phys_cache, - colliders.maybe(), - scales.maybe(), - char_states.maybe(), - !&mountings, - !&beams, - !&shockwaves, + &self.r.entities, + &self.w.velocities, + &self.w.positions, + &mut self.w.previous_phys_cache, + self.r.colliders.maybe(), + self.r.scales.maybe(), + self.r.char_states.maybe(), + !&self.r.mountings, + !&self.r.beams, + !&self.r.shockwaves, ) .join() { @@ -196,7 +166,7 @@ impl<'a> System<'a> for Sys { let z_limits = (z_limits.0 * scale, z_limits.1 * scale); let half_height = (z_limits.1 - z_limits.0) / 2.0; - phys_cache.velocity_dt = vel.0 * dt.0; + phys_cache.velocity_dt = vel.0 * self.r.dt.0; let entity_center = position.0 + Vec3::new(0.0, z_limits.0 + half_height, 0.0); let flat_radius = collider.map(|c| c.get_radius()).unwrap_or(0.5) * scale; let radius = (flat_radius.powi(2) + half_height.powi(2)).sqrt(); @@ -209,23 +179,26 @@ impl<'a> System<'a> for Sys { phys_cache.scaled_radius = flat_radius; } drop(guard); - + } + fn apply_pushback(&mut self, job: &mut Job) { span!(guard, "Apply pushback"); job.cpu_stats.measure(ParMode::Rayon); + let PhysicsSystemData { r: ref psdr, w: ref mut psdw } = self; + let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); let metrics = ( - &entities, - &positions, - &mut velocities, - &previous_phys_cache, - masses.maybe(), - colliders.maybe(), - !&mountings, - stickies.maybe(), - &mut physics_states, + &psdr.entities, + positions, + &mut psdw.velocities, + previous_phys_cache, + psdr.masses.maybe(), + psdr.colliders.maybe(), + !&psdr.mountings, + psdr.stickies.maybe(), + &mut psdw.physics_states, // TODO: if we need to avoid collisions for other things consider moving whether it // should interact into the collider component or into a separate component - projectiles.maybe(), - char_states.maybe(), + psdr.projectiles.maybe(), + psdr.char_states.maybe(), ) .par_join() .filter(|(_, _, _, _, _, _, _, sticky, physics, _, _)| { @@ -275,17 +248,17 @@ impl<'a> System<'a> for Sys { _, char_state_other_maybe, ) in ( - &entities, - &uids, - &positions, - &previous_phys_cache, - masses.maybe(), - colliders.maybe(), - !&projectiles, - !&mountings, - !&beams, - !&shockwaves, - char_states.maybe(), + &psdr.entities, + &psdr.uids, + positions, + previous_phys_cache, + psdr.masses.maybe(), + psdr.colliders.maybe(), + !&psdr.projectiles, + !&psdr.mountings, + !&psdr.beams, + !&psdr.shockwaves, + psdr.char_states.maybe(), ) .join() { @@ -358,7 +331,7 @@ impl<'a> System<'a> for Sys { } // Change velocity - vel.0 += vel_delta * dt.0; + vel.0 += vel_delta * psdr.dt.0; // Metrics PhysicsMetrics { @@ -373,435 +346,622 @@ impl<'a> System<'a> for Sys { entity_entity_collisions: old.entity_entity_collisions + new.entity_entity_collisions, }); - physics_metrics.entity_entity_collision_checks = metrics.entity_entity_collision_checks; - physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; + psdw.physics_metrics.entity_entity_collision_checks = metrics.entity_entity_collision_checks; + psdw.physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; drop(guard); + } + fn handle_movement_and_terrain(&mut self, job: &mut Job) { + let PhysicsSystemData { r: ref psdr, w: ref mut psdw } = self; // Apply movement inputs span!(guard, "Apply movement and terrain collision"); - let land_on_grounds = ( - &entities, - scales.maybe(), - stickies.maybe(), - &colliders, - &mut positions, - &mut velocities, - &mut orientations, - &mut physics_states, - !&mountings, - ) - .par_join() - .map_init( - || { - prof_span!(guard, "physics e<>t rayon job"); - guard - }, - |_guard, - (entity, _scale, sticky, collider, mut pos, mut vel, _ori, mut physics_state, _), - | { - let mut landed_on_ground = None; - - if sticky.is_some() && physics_state.on_surface().is_some() { - vel.0 = Vec3::zero(); - return landed_on_ground; - } - - // TODO: Use this - //let scale = scale.map(|s| s.0).unwrap_or(1.0); - - let old_vel = *vel; - // Integrate forces - // Friction is assumed to be a constant dependent on location - let friction = FRIC_AIR - .max(if physics_state.on_ground { - FRIC_GROUND - } else { - 0.0 - }) - .max(if physics_state.in_liquid.is_some() { - FRIC_FLUID - } else { - 0.0 - }); - let in_loaded_chunk = terrain - .get_key(terrain.pos_key(pos.0.map(|e| e.floor() as i32))) - .is_some(); - let downward_force = if !in_loaded_chunk { - 0.0 // No gravity in unloaded chunks - } else if physics_state - .in_liquid - .map(|depth| depth > 0.75) - .unwrap_or(false) - { - (1.0 - BOUYANCY) * GRAVITY - } else { - GRAVITY - } * gravities.get(entity).map(|g| g.0).unwrap_or_default(); - vel.0 = integrate_forces(dt.0, vel.0, downward_force, friction); - - // Don't move if we're not in a loaded chunk - let mut pos_delta = if in_loaded_chunk { - // this is an approximation that allows most framerates to - // behave in a similar manner. - let dt_lerp = 0.2; - (vel.0 * dt_lerp + old_vel.0 * (1.0 - dt_lerp)) * dt.0 - } else { - Vec3::zero() - }; - - match *collider { - Collider::Box { - radius, - z_min, - z_max, - } => { - // Scale collider - // TODO: Use scale & actual proportions when pathfinding is good enough to manage irregular entity - // sizes - let radius = radius.min(0.45); // * scale; - let z_min = z_min; // * scale; - let z_max = z_max.clamped(1.2, 1.95); // * scale; - - // Probe distances - let hdist = radius.ceil() as i32; - // Neighbouring blocks iterator - let near_iter = (-hdist..hdist + 1) - .map(move |i| { - (-hdist..hdist + 1).map(move |j| { - (1 - Block::MAX_HEIGHT.ceil() as i32 + z_min.floor() as i32 - ..z_max.ceil() as i32 + 1) - .map(move |k| (i, j, k)) - }) - }) - .flatten() - .flatten(); - - // Function for iterating over the blocks the player at a specific position - // collides with - fn collision_iter<'a>( - pos: Vec3, - terrain: &'a TerrainGrid, - hit: &'a impl Fn(&Block) -> bool, - height: &'a impl Fn(&Block) -> f32, - near_iter: impl Iterator + 'a, - radius: f32, - z_range: Range, - ) -> impl Iterator> + 'a { - near_iter.filter_map(move |(i, j, k)| { - let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k); - - if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) { - let player_aabb = Aabb { - min: pos + Vec3::new(-radius, -radius, z_range.start), - max: pos + Vec3::new(radius, radius, z_range.end), - }; - let block_aabb = Aabb { - min: block_pos.map(|e| e as f32), - max: block_pos.map(|e| e as f32) - + Vec3::new(1.0, 1.0, height(&block)), - }; - - if player_aabb.collides_with_aabb(block_aabb) { - return Some(block_aabb); - } - } - - None - }) - } - - let z_range = z_min..z_max; - // Function for determining whether the player at a specific position collides - // with blocks with the given criteria - fn collision_with<'a>( - pos: Vec3, - terrain: &'a TerrainGrid, - hit: impl Fn(&Block) -> bool, - near_iter: impl Iterator + 'a, - radius: f32, - z_range: Range, - ) -> bool { - collision_iter(pos, terrain, &|block| block.is_solid() && hit(block), &Block::solid_height, near_iter, radius, z_range).count() - > 0 - } - - let was_on_ground = physics_state.on_ground; - physics_state.on_ground = false; - - let mut on_ground = false; - let mut on_ceiling = false; - let mut attempts = 0; // Don't loop infinitely here - - // Don't jump too far at once - let increments = (pos_delta.map(|e| e.abs()).reduce_partial_max() / 0.3) - .ceil() - .max(1.0); - let old_pos = pos.0; - fn block_true(_: &Block) -> bool { true } - for _ in 0..increments as usize { - pos.0 += pos_delta / increments; - - const MAX_ATTEMPTS: usize = 16; - - // While the player is colliding with the terrain... - while collision_with(pos.0, &terrain, block_true, near_iter.clone(), radius, z_range.clone()) - && attempts < MAX_ATTEMPTS - { - // Calculate the player's AABB - let player_aabb = Aabb { - min: pos.0 + Vec3::new(-radius, -radius, z_min), - max: pos.0 + Vec3::new(radius, radius, z_max), - }; - - // Determine the block that we are colliding with most (based on minimum - // collision axis) - let (_block_pos, block_aabb, block_height) = near_iter - .clone() - // Calculate the block's position in world space - .map(|(i, j, k)| pos.0.map(|e| e.floor() as i32) + Vec3::new(i, j, k)) - // Make sure the block is actually solid - .filter_map(|block_pos| { - if let Some(block) = terrain - .get(block_pos) - .ok() - .filter(|block| block.is_solid()) - { - // Calculate block AABB - Some(( - block_pos, - Aabb { - min: block_pos.map(|e| e as f32), - max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.solid_height()), - }, - block.solid_height(), - )) - } else { - None - } - }) - // Determine whether the block's AABB collides with the player's AABB - .filter(|(_, block_aabb, _)| block_aabb.collides_with_aabb(player_aabb)) - // Find the maximum of the minimum collision axes (this bit is weird, trust me that it works) - .min_by_key(|(_, block_aabb, _)| { - ((block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5) - .map(|e| e.abs()) - .sum() - * 1_000_000.0) as i32 - }) - .expect("Collision detected, but no colliding blocks found!"); - - // Find the intrusion vector of the collision - let dir = player_aabb.collision_vector_with_aabb(block_aabb); - - // Determine an appropriate resolution vector (i.e: the minimum distance - // needed to push out of the block) - let max_axis = dir.map(|e| e.abs()).reduce_partial_min(); - let resolve_dir = -dir.map(|e| { - if e.abs().to_bits() == max_axis.to_bits() { - e - } else { - 0.0 - } - }); - - // When the resolution direction is pointing upwards, we must be on the - // ground - if resolve_dir.z > 0.0 && vel.0.z <= 0.0 { - on_ground = true; - - if !was_on_ground { - landed_on_ground = Some((entity, *vel)); - } - } else if resolve_dir.z < 0.0 && vel.0.z >= 0.0 { - on_ceiling = true; - } - - // When the resolution direction is non-vertical, we must be colliding - // with a wall If the space above is free... - if !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &terrain, block_true, near_iter.clone(), radius, z_range.clone()) - // ...and we're being pushed out horizontally... - && resolve_dir.z == 0.0 - // ...and the vertical resolution direction is sufficiently great... - && -dir.z > 0.1 - // ...and we're falling/standing OR there is a block *directly* beneath our current origin (note: not hitbox)... - && (vel.0.z <= 0.0 || terrain - .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) - .map(|block| block.is_solid()) - .unwrap_or(false)) - // ...and there is a collision with a block beneath our current hitbox... - && collision_with( - pos.0 + resolve_dir - Vec3::unit_z() * 1.05, - &terrain, - block_true, - near_iter.clone(), - radius, - z_range.clone(), - ) - { - // ...block-hop! - pos.0.z = (pos.0.z + 0.1).floor() + block_height; - vel.0.z = 0.0; - on_ground = true; - break; - } else { - // Correct the velocity - vel.0 = vel.0.map2(resolve_dir, |e, d| { - if d * e.signum() < 0.0 { 0.0 } else { e } - }); - pos_delta *= resolve_dir.map(|e| if e != 0.0 { 0.0 } else { 1.0 }); - } - - // Resolve the collision normally - pos.0 += resolve_dir; - - attempts += 1; - } - - if attempts == MAX_ATTEMPTS { + let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); + let (pos_writes, land_on_grounds) = + ( + &psdr.entities, + psdr.scales.maybe(), + psdr.stickies.maybe(), + &psdr.colliders, + positions, + &mut psdw.velocities, + &psdw.orientations, + &mut psdw.physics_states, + previous_phys_cache, + !&psdr.mountings, + ) + .par_join() + .fold( + || (Vec::new(), Vec::new()), + |(mut pos_writes, mut land_on_grounds), + ( + entity, + scale, + sticky, + collider, + pos, + mut vel, + _ori, + mut physics_state, + previous_cache, + _, + )| { + // defer the writes of positions to allow an inner loop over terrain-like + // entities + let old_pos = *pos; + let mut pos = *pos; + if sticky.is_some() && physics_state.on_surface().is_some() { vel.0 = Vec3::zero(); - pos.0 = old_pos; - break; + return (pos_writes, land_on_grounds); } - } - if on_ceiling { - physics_state.on_ceiling = true; - } - - if on_ground { - physics_state.on_ground = true; - // If the space below us is free, then "snap" to the ground - } else if collision_with( - pos.0 - Vec3::unit_z() * 1.05, - &terrain, - block_true, - near_iter.clone(), - radius, - z_range.clone(), - ) && vel.0.z < 0.0 - && vel.0.z > -1.5 - && was_on_ground - && !collision_with( - pos.0 - Vec3::unit_z() * 0.05, - &terrain, - |block| block.solid_height() >= (pos.0.z - 0.05).rem_euclid(1.0), - near_iter.clone(), - radius, - z_range.clone(), - ) - { - let snap_height = terrain - .get( - Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.05) - .map(|e| e.floor() as i32), - ) - .ok() - .filter(|block| block.is_solid()) - .map(|block| block.solid_height()) - .unwrap_or(0.0); - pos.0.z = (pos.0.z - 0.05).floor() + snap_height; - physics_state.on_ground = true; - } - - let dirs = [ - Vec3::unit_x(), - Vec3::unit_y(), - -Vec3::unit_x(), - -Vec3::unit_y(), - ]; - - if let (wall_dir, true) = - dirs.iter().fold((Vec3::zero(), false), |(a, hit), dir| { - if collision_with( - pos.0 + *dir * 0.01, - &terrain, - block_true, - near_iter.clone(), - radius, - z_range.clone(), - ) { - (a + dir, true) - } else { - (a, hit) - } - }) - { - physics_state.on_wall = Some(wall_dir); - } else { - physics_state.on_wall = None; - } - - // Figure out if we're in water - physics_state.in_liquid = collision_iter( - pos.0, - &terrain, - &|block| block.is_liquid(), - // The liquid part of a liquid block always extends 1 block high. - &|_block| 1.0, - near_iter.clone(), - radius, - z_min..z_max, - ) - .max_by_key(|block_aabb| (block_aabb.max.z * 100.0) as i32) - .map(|block_aabb| block_aabb.max.z - pos.0.z); - }, - Collider::Point => { - let (dist, block) = terrain.ray(pos.0, pos.0 + pos_delta) - .until(|block: &Block| block.is_filled()) - .ignore_error().cast(); - - pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist; - - // Can't fail since we do ignore_error above - if block.unwrap().is_some() { - let block_center = pos.0.map(|e| e.floor()) + 0.5; - let block_rpos = (pos.0 - block_center) - .try_normalized() - .unwrap_or(Vec3::zero()); - - // See whether we're on the top/bottom of a block, or the side - if block_rpos.z.abs() - > block_rpos.xy().map(|e| e.abs()).reduce_partial_max() - { - if block_rpos.z > 0.0 { - physics_state.on_ground = true; - } else { - physics_state.on_ceiling = true; - } - vel.0.z = 0.0; + let scale = if let Collider::Voxel { .. } = collider { + scale.map(|s| s.0).unwrap_or(1.0) } else { - physics_state.on_wall = - Some(if block_rpos.x.abs() > block_rpos.y.abs() { - vel.0.x = 0.0; - Vec3::unit_x() * -block_rpos.x.signum() - } else { - vel.0.y = 0.0; - Vec3::unit_y() * -block_rpos.y.signum() - }); + // TODO: Use scale & actual proportions when pathfinding is good + // enough to manage irregular entity sizes + 1.0 + }; + + let old_vel = *vel; + // Integrate forces + // Friction is assumed to be a constant dependent on location + let friction = FRIC_AIR + .max(if physics_state.on_ground { + FRIC_GROUND + } else { + 0.0 + }) + .max(if physics_state.in_liquid.is_some() { + FRIC_FLUID + } else { + 0.0 + }); + let in_loaded_chunk = psdr.terrain + .get_key(psdr.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) + .is_some(); + let downward_force = + if !in_loaded_chunk { + 0.0 // No gravity in unloaded chunks + } else if physics_state + .in_liquid + .map(|depth| depth > 0.75) + .unwrap_or(false) + { + (1.0 - BOUYANCY) * GRAVITY + } else { + GRAVITY + } * psdr.gravities.get(entity).map(|g| g.0).unwrap_or_default(); + vel.0 = integrate_forces(psdr.dt.0, vel.0, downward_force, friction); + + // Don't move if we're not in a loaded chunk + let pos_delta = if in_loaded_chunk { + // this is an approximation that allows most framerates to + // behave in a similar manner. + let dt_lerp = 0.2; + (vel.0 * dt_lerp + old_vel.0 * (1.0 - dt_lerp)) * psdr.dt.0 + } else { + Vec3::zero() + }; + + match &*collider { + Collider::Voxel { .. } => { + // for now, treat entities with voxel colliders as their bounding + // cylinders for the purposes of colliding them with terrain + let radius = collider.get_radius() * scale; + let (z_min, z_max) = collider.get_z_limits(scale); + + let cylinder = (radius, z_min, z_max); + cylinder_voxel_collision( + cylinder, + &*psdr.terrain, + entity, + &mut pos, + pos_delta, + vel, + &mut physics_state, + &mut land_on_grounds, + ); + }, + Collider::Box { + radius, + z_min, + z_max, + } => { + // Scale collider + let radius = radius.min(0.45) * scale; + let z_min = *z_min * scale; + let z_max = z_max.clamped(1.2, 1.95) * scale; + + let cylinder = (radius, z_min, z_max); + cylinder_voxel_collision( + cylinder, + &*psdr.terrain, + entity, + &mut pos, + pos_delta, + vel, + &mut physics_state, + &mut land_on_grounds, + ); + }, + Collider::Point => { + let (dist, block) = psdr.terrain + .ray(pos.0, pos.0 + pos_delta) + .until(|block: &Block| block.is_filled()) + .ignore_error() + .cast(); + + pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist; + + // Can't fail since we do ignore_error above + if block.unwrap().is_some() { + let block_center = pos.0.map(|e| e.floor()) + 0.5; + let block_rpos = (pos.0 - block_center) + .try_normalized() + .unwrap_or(Vec3::zero()); + + // See whether we're on the top/bottom of a block, or the side + if block_rpos.z.abs() + > block_rpos.xy().map(|e| e.abs()).reduce_partial_max() + { + if block_rpos.z > 0.0 { + physics_state.on_ground = true; + } else { + physics_state.on_ceiling = true; + } + vel.0.z = 0.0; + } else { + physics_state.on_wall = + Some(if block_rpos.x.abs() > block_rpos.y.abs() { + vel.0.x = 0.0; + Vec3::unit_x() * -block_rpos.x.signum() + } else { + vel.0.y = 0.0; + Vec3::unit_y() * -block_rpos.y.signum() + }); + } + } + + physics_state.in_liquid = psdr.terrain + .get(pos.0.map(|e| e.floor() as i32)) + .ok() + .and_then(|vox| vox.is_liquid().then_some(1.0)); + }, } - } - physics_state.in_liquid = terrain.get(pos.0.map(|e| e.floor() as i32)) - .ok() - .and_then(|vox| vox.is_liquid().then_some(1.0)); - }, - } + // Collide with terrain-like entities + for ( + entity_other, + other, + pos_other, + previous_cache_other, + mass_other, + collider_other, + _, + _, + _, + _, + char_state_other_maybe, + ) in ( + &psdr.entities, + &psdr.uids, + positions, + previous_phys_cache, + psdr.masses.maybe(), + &psdr.colliders, + !&psdr.projectiles, + !&psdr.mountings, + !&psdr.beams, + !&psdr.shockwaves, + psdr.char_states.maybe(), + ) + .join() + { + let collision_boundary = previous_cache.collision_boundary + + previous_cache_other.collision_boundary; + if previous_cache + .center + .distance_squared(previous_cache_other.center) + > collision_boundary.powi(2) + || entity == entity_other + { + continue; + } - landed_on_ground - }).fold(Vec::new, |mut lands_on_grounds, landed_on_ground| { - if let Some(land_on_ground) = landed_on_ground { - lands_on_grounds.push(land_on_ground); - } - lands_on_grounds - }).reduce(Vec::new, |mut land_on_grounds_a, mut land_on_grounds_b| { - land_on_grounds_a.append(&mut land_on_grounds_b); - land_on_grounds_a - }); + if let Collider::Voxel { id } = collider_other { + // use bounding cylinder regardless of our collider + // TODO: extract point-terrain collision above to its own function + let radius = collider.get_radius() * scale; + let (z_min, z_max) = collider.get_z_limits(scale); + + let cylinder = (radius, z_min, z_max); + // TODO: load .vox into a Dyna, and use it (appropriately rotated) + // as the terrain + /*cylinder_voxel_collision( + cylinder, + &*psdr.terrain, + entity, + &mut pos, + pos_delta, + vel, + &mut physics_state, + &mut land_on_grounds, + );*/ + } + } + if pos != old_pos { + pos_writes.push((entity, pos)); + } + + (pos_writes, land_on_grounds) + }, + ) + .reduce( + || (Vec::new(), Vec::new()), + |(mut pos_writes_a, mut land_on_grounds_a), + (mut pos_writes_b, mut land_on_grounds_b)| { + pos_writes_a.append(&mut pos_writes_b); + land_on_grounds_a.append(&mut land_on_grounds_b); + (pos_writes_a, land_on_grounds_a) + }, + ); drop(guard); job.cpu_stats.measure(ParMode::Single); + let pos_writes: HashMap = pos_writes.into_iter().collect(); + for (entity, pos) in (&psdr.entities, &mut psdw.positions).join() { + if let Some(new_pos) = pos_writes.get(&entity) { + *pos = *new_pos; + } + } + + let mut event_emitter = psdr.event_bus.emitter(); land_on_grounds.into_iter().for_each(|(entity, vel)| { event_emitter.emit(ServerEvent::LandOnGround { entity, vel: vel.0 }); }); } } + +impl<'a> System<'a> for Sys { + type SystemData = PhysicsSystemData<'a>; + + const NAME: &'static str = "phys"; + const ORIGIN: Origin = Origin::Common; + const PHASE: Phase = Phase::Create; + + #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 + #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 + fn run( + job: &mut Job, + mut psd: Self::SystemData, + ) { + psd.reset(); + + // Apply pushback + // + // Note: We now do this first because we project velocity ahead. This is slighty + // imperfect and implies that we might get edge-cases where entities + // standing right next to the edge of a wall may get hit by projectiles + // fired into the wall very close to them. However, this sort of thing is + // already possible with poorly-defined hitboxes anyway so it's not too + // much of a concern. + // + // If this situation becomes a problem, this code should be integrated with the + // terrain collision code below, although that's not trivial to do since + // it means the step needs to take into account the speeds of both + // entities. + psd.maintain_pushback_cache(); + psd.apply_pushback(job); + + + psd.handle_movement_and_terrain(job); + } +} + +fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( + cylinder: (f32, f32, f32), + terrain: &'a T, + entity: Entity, + pos: &mut Pos, + mut pos_delta: Vec3, + vel: &mut Vel, + physics_state: &mut PhysicsState, + land_on_grounds: &mut Vec<(Entity, Vel)>, +) { + let (radius, z_min, z_max) = cylinder; + + // Probe distances + let hdist = radius.ceil() as i32; + // Neighbouring blocks iterator + let near_iter = (-hdist..hdist + 1) + .map(move |i| { + (-hdist..hdist + 1).map(move |j| { + (1 - Block::MAX_HEIGHT.ceil() as i32 + z_min.floor() as i32 + ..z_max.ceil() as i32 + 1) + .map(move |k| (i, j, k)) + }) + }) + .flatten() + .flatten(); + + // Function for iterating over the blocks the player at a specific position + // collides with + fn collision_iter<'a, T: BaseVol + ReadVol>( + pos: Vec3, + terrain: &'a T, + hit: &'a impl Fn(&Block) -> bool, + height: &'a impl Fn(&Block) -> f32, + near_iter: impl Iterator + 'a, + radius: f32, + z_range: Range, + ) -> impl Iterator> + 'a { + near_iter.filter_map(move |(i, j, k)| { + let block_pos = pos.map(|e| e.floor() as i32) + Vec3::new(i, j, k); + + if let Some(block) = terrain.get(block_pos).ok().copied().filter(hit) { + let player_aabb = Aabb { + min: pos + Vec3::new(-radius, -radius, z_range.start), + max: pos + Vec3::new(radius, radius, z_range.end), + }; + let block_aabb = Aabb { + min: block_pos.map(|e| e as f32), + max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, height(&block)), + }; + + if player_aabb.collides_with_aabb(block_aabb) { + return Some(block_aabb); + } + } + + None + }) + } + + let z_range = z_min..z_max; + // Function for determining whether the player at a specific position collides + // with blocks with the given criteria + fn collision_with<'a, T: BaseVol + ReadVol>( + pos: Vec3, + terrain: &'a T, + hit: impl Fn(&Block) -> bool, + near_iter: impl Iterator + 'a, + radius: f32, + z_range: Range, + ) -> bool { + collision_iter( + pos, + terrain, + &|block| block.is_solid() && hit(block), + &Block::solid_height, + near_iter, + radius, + z_range, + ) + .count() + > 0 + } + + let was_on_ground = physics_state.on_ground; + physics_state.on_ground = false; + + let mut on_ground = false; + let mut on_ceiling = false; + let mut attempts = 0; // Don't loop infinitely here + + // Don't jump too far at once + let increments = (pos_delta.map(|e| e.abs()).reduce_partial_max() / 0.3) + .ceil() + .max(1.0); + let old_pos = pos.0; + fn block_true(_: &Block) -> bool { true } + for _ in 0..increments as usize { + pos.0 += pos_delta / increments; + + const MAX_ATTEMPTS: usize = 16; + + // While the player is colliding with the terrain... + while collision_with( + pos.0, + &terrain, + block_true, + near_iter.clone(), + radius, + z_range.clone(), + ) && attempts < MAX_ATTEMPTS + { + // Calculate the player's AABB + let player_aabb = Aabb { + min: pos.0 + Vec3::new(-radius, -radius, z_min), + max: pos.0 + Vec3::new(radius, radius, z_max), + }; + + // Determine the block that we are colliding with most (based on minimum + // collision axis) + let (_block_pos, block_aabb, block_height) = near_iter + .clone() + // Calculate the block's position in world space + .map(|(i, j, k)| pos.0.map(|e| e.floor() as i32) + Vec3::new(i, j, k)) + // Make sure the block is actually solid + .filter_map(|block_pos| { + if let Some(block) = terrain + .get(block_pos) + .ok() + .filter(|block| block.is_solid()) + { + // Calculate block AABB + Some(( + block_pos, + Aabb { + min: block_pos.map(|e| e as f32), + max: block_pos.map(|e| e as f32) + Vec3::new(1.0, 1.0, block.solid_height()), + }, + block.solid_height(), + )) + } else { + None + } + }) + // Determine whether the block's AABB collides with the player's AABB + .filter(|(_, block_aabb, _)| block_aabb.collides_with_aabb(player_aabb)) + // Find the maximum of the minimum collision axes (this bit is weird, trust me that it works) + .min_by_key(|(_, block_aabb, _)| { + ((block_aabb.center() - player_aabb.center() - Vec3::unit_z() * 0.5) + .map(|e| e.abs()) + .sum() + * 1_000_000.0) as i32 + }) + .expect("Collision detected, but no colliding blocks found!"); + + // Find the intrusion vector of the collision + let dir = player_aabb.collision_vector_with_aabb(block_aabb); + + // Determine an appropriate resolution vector (i.e: the minimum distance + // needed to push out of the block) + let max_axis = dir.map(|e| e.abs()).reduce_partial_min(); + let resolve_dir = -dir.map(|e| { + if e.abs().to_bits() == max_axis.to_bits() { + e + } else { + 0.0 + } + }); + + // When the resolution direction is pointing upwards, we must be on the + // ground + if resolve_dir.z > 0.0 && vel.0.z <= 0.0 { + on_ground = true; + + if !was_on_ground { + land_on_grounds.push((entity, *vel)); + } + } else if resolve_dir.z < 0.0 && vel.0.z >= 0.0 { + on_ceiling = true; + } + + // When the resolution direction is non-vertical, we must be colliding + // with a wall If the space above is free... + if !collision_with(Vec3::new(pos.0.x, pos.0.y, (pos.0.z + 0.1).ceil()), &terrain, block_true, near_iter.clone(), radius, z_range.clone()) + // ...and we're being pushed out horizontally... + && resolve_dir.z == 0.0 + // ...and the vertical resolution direction is sufficiently great... + && -dir.z > 0.1 + // ...and we're falling/standing OR there is a block *directly* beneath our current origin (note: not hitbox)... + && (vel.0.z <= 0.0 || terrain + .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) + .map(|block| block.is_solid()) + .unwrap_or(false)) + // ...and there is a collision with a block beneath our current hitbox... + && collision_with( + pos.0 + resolve_dir - Vec3::unit_z() * 1.05, + &terrain, + block_true, + near_iter.clone(), + radius, + z_range.clone(), + ) + { + // ...block-hop! + pos.0.z = (pos.0.z + 0.1).floor() + block_height; + vel.0.z = 0.0; + on_ground = true; + break; + } else { + // Correct the velocity + vel.0 = vel.0.map2( + resolve_dir, + |e, d| { + if d * e.signum() < 0.0 { 0.0 } else { e } + }, + ); + pos_delta *= resolve_dir.map(|e| if e != 0.0 { 0.0 } else { 1.0 }); + } + + // Resolve the collision normally + pos.0 += resolve_dir; + + attempts += 1; + } + + if attempts == MAX_ATTEMPTS { + vel.0 = Vec3::zero(); + pos.0 = old_pos; + break; + } + } + + if on_ceiling { + physics_state.on_ceiling = true; + } + + if on_ground { + physics_state.on_ground = true; + // If the space below us is free, then "snap" to the ground + } else if collision_with( + pos.0 - Vec3::unit_z() * 1.05, + &terrain, + block_true, + near_iter.clone(), + radius, + z_range.clone(), + ) && vel.0.z < 0.0 + && vel.0.z > -1.5 + && was_on_ground + && !collision_with( + pos.0 - Vec3::unit_z() * 0.05, + &terrain, + |block| block.solid_height() >= (pos.0.z - 0.05).rem_euclid(1.0), + near_iter.clone(), + radius, + z_range.clone(), + ) + { + let snap_height = terrain + .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.05).map(|e| e.floor() as i32)) + .ok() + .filter(|block| block.is_solid()) + .map(|block| block.solid_height()) + .unwrap_or(0.0); + pos.0.z = (pos.0.z - 0.05).floor() + snap_height; + physics_state.on_ground = true; + } + + let dirs = [ + Vec3::unit_x(), + Vec3::unit_y(), + -Vec3::unit_x(), + -Vec3::unit_y(), + ]; + + if let (wall_dir, true) = dirs.iter().fold((Vec3::zero(), false), |(a, hit), dir| { + if collision_with( + pos.0 + *dir * 0.01, + &terrain, + block_true, + near_iter.clone(), + radius, + z_range.clone(), + ) { + (a + dir, true) + } else { + (a, hit) + } + }) { + physics_state.on_wall = Some(wall_dir); + } else { + physics_state.on_wall = None; + } + + // Figure out if we're in water + physics_state.in_liquid = collision_iter( + pos.0, + &*terrain, + &|block| block.is_liquid(), + // The liquid part of a liquid block always extends 1 block high. + &|_block| 1.0, + near_iter.clone(), + radius, + z_min..z_max, + ) + .max_by_key(|block_aabb| (block_aabb.max.z * 100.0) as i32) + .map(|block_aabb| block_aabb.max.z - pos.0.z); +} diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 9043ef5fe5..7e6dc4c454 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -77,6 +77,7 @@ type CommandHandler = fn(&mut Server, EcsEntity, EcsEntity, String, &ChatCommand fn get_handler(cmd: &ChatCommand) -> CommandHandler { match cmd { ChatCommand::Adminify => handle_adminify, + ChatCommand::Airship => handle_spawn_airship, ChatCommand::Alias => handle_alias, ChatCommand::Ban => handle_ban, ChatCommand::Build => handle_build, @@ -984,6 +985,39 @@ fn handle_spawn_training_dummy( } } +fn handle_spawn_airship( + server: &mut Server, + client: EcsEntity, + target: EcsEntity, + _args: String, + _action: &ChatCommand, +) { + match server.state.read_component_copied::(target) { + Some(pos) => { + server + .state + .create_ship(pos, comp::ship::Body::DefaultAirship) + .with(comp::Scale(50.0)) + .with(LightEmitter { + col: Rgb::new(1.0, 0.65, 0.2), + strength: 2.0, + flicker: 1.0, + animated: true, + }) + .build(); + + server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandInfo, "Spawned an airship"), + ); + }, + None => server.notify_client( + client, + ServerGeneral::server_msg(ChatType::CommandError, "You have no position!"), + ), + } +} + fn handle_spawn_campfire( server: &mut Server, client: EcsEntity, diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index c1c9f8f450..4005c3131b 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -69,7 +69,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv find_dist::Cylinder::from_components( p.0, scales.get(entity).copied(), - colliders.get(entity).copied(), + colliders.get(entity).cloned(), char_states.get(entity), ) }) diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index a596b31cbb..770b6d28b0 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -41,6 +41,7 @@ pub trait StateExt { ) -> EcsEntityBuilder; /// Build a static object entity fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder; + fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body) -> EcsEntityBuilder; /// Build a projectile fn create_projectile( &mut self, @@ -215,6 +216,18 @@ impl StateExt for State { .with(comp::Gravity(1.0)) } + fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body) -> EcsEntityBuilder { + self.ecs_mut() + .create_entity_synced() + .with(pos) + .with(comp::Vel(Vec3::zero())) + .with(comp::Ori::default()) + .with(comp::Mass(50.0)) + .with(comp::Collider::Voxel { id: object.manifest_id().to_string() }) + .with(comp::Body::Ship(object)) + .with(comp::Gravity(1.0)) + } + fn create_projectile( &mut self, pos: comp::Pos, diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 105b417a6c..778892724c 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -140,7 +140,7 @@ impl<'a> TrackedComps<'a> { self.mass.get(entity).copied().map(|c| comps.push(c.into())); self.collider .get(entity) - .copied() + .cloned() .map(|c| comps.push(c.into())); self.sticky .get(entity) diff --git a/voxygen/anim/src/lib.rs b/voxygen/anim/src/lib.rs index 4988954a6f..d903d6572f 100644 --- a/voxygen/anim/src/lib.rs +++ b/voxygen/anim/src/lib.rs @@ -51,6 +51,7 @@ pub mod fish_small; pub mod fixture; pub mod golem; pub mod object; +pub mod ship; pub mod quadruped_low; pub mod quadruped_medium; pub mod quadruped_small; diff --git a/voxygen/anim/src/ship/idle.rs b/voxygen/anim/src/ship/idle.rs new file mode 100644 index 0000000000..b96c9fd64e --- /dev/null +++ b/voxygen/anim/src/ship/idle.rs @@ -0,0 +1,34 @@ +use super::{ + super::{vek::*, Animation}, + ShipSkeleton, SkeletonAttr, +}; +use common::comp::item::ToolKind; + +pub struct IdleAnimation; + +impl Animation for IdleAnimation { + type Dependency = (Option, Option, f32); + type Skeleton = ShipSkeleton; + + #[cfg(feature = "use-dyn-lib")] + const UPDATE_FN: &'static [u8] = b"ship_idle\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "ship_idle")] + #[allow(clippy::approx_constant)] // TODO: Pending review in #587 + fn update_skeleton_inner( + skeleton: &Self::Skeleton, + (_active_tool_kind, _second_tool_kind, _global_time): Self::Dependency, + _anim_time: f32, + _rate: &mut f32, + s_a: &SkeletonAttr, + ) -> Self::Skeleton { + let mut next = (*skeleton).clone(); + + next.bone0.position = Vec3::new(s_a.bone0.0, s_a.bone0.1, s_a.bone0.2) / 11.0; + + next.bone1.position = Vec3::new(s_a.bone1.0, s_a.bone1.1, s_a.bone1.2) / 11.0; + + next + } +} + diff --git a/voxygen/anim/src/ship/mod.rs b/voxygen/anim/src/ship/mod.rs new file mode 100644 index 0000000000..2e2783add3 --- /dev/null +++ b/voxygen/anim/src/ship/mod.rs @@ -0,0 +1,71 @@ +pub mod idle; + +// Reexports +pub use self::idle::IdleAnimation; + +use super::{make_bone, vek::*, FigureBoneData, Skeleton}; +use common::comp::{self}; +use core::convert::TryFrom; + +pub type Body = comp::ship::Body; + +skeleton_impls!(struct ShipSkeleton { + + bone0, + + bone1, +}); + +impl Skeleton for ShipSkeleton { + type Attr = SkeletonAttr; + type Body = Body; + + const BONE_COUNT: usize = 2; + #[cfg(feature = "use-dyn-lib")] + const COMPUTE_FN: &'static [u8] = b"ship_compute_mats\0"; + + #[cfg_attr(feature = "be-dyn-lib", export_name = "ship_compute_mats")] + fn compute_matrices_inner( + &self, + base_mat: Mat4, + buf: &mut [FigureBoneData; super::MAX_BONE_COUNT], + ) -> Vec3 { + let bone0_mat = base_mat * Mat4::::from(self.bone0); + + *(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [ + make_bone(bone0_mat * Mat4::scaling_3d(1.0 / 11.0)), + make_bone(Mat4::::from(self.bone1) * Mat4::scaling_3d(1.0 / 11.0)), /* Decorellated from ori */ + ]; + Vec3::unit_z() * 0.5 + } +} + +pub struct SkeletonAttr { + bone0: (f32, f32, f32), + bone1: (f32, f32, f32), +} + +impl<'a> std::convert::TryFrom<&'a comp::Body> for SkeletonAttr { + type Error = (); + + fn try_from(body: &'a comp::Body) -> Result { + match body { + comp::Body::Ship(body) => Ok(SkeletonAttr::from(body)), + _ => Err(()), + } + } +} + +impl Default for SkeletonAttr { + fn default() -> Self { + Self { + bone0: (0.0, 0.0, 0.0), + bone1: (0.0, 0.0, 0.0), + } + } +} + +impl<'a> From<&'a Body> for SkeletonAttr { + fn from(_: &'a Body) -> Self { + Self::default() + } +} + diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 6856dbbb4b..eec891e144 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -1972,7 +1972,7 @@ fn create_pipelines( &shaders.figure_vert.read().0, &shaders.figure_frag.read().0, &include_ctx, - gfx::state::CullFace::Back, + gfx::state::CullFace::Nothing, )?; // Construct a pipeline for rendering terrain diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 6aca80bb7f..a584812462 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -13,6 +13,7 @@ use common::{ humanoid::{self, Body, BodyType, EyeColor, Skin, Species}, item::{ItemDef, ModularComponentKind}, object, + ship::{self, figuredata::{ShipSpec, ShipCentralSubSpec}}, quadruped_low::{self, BodyType as QLBodyType, Species as QLSpecies}, quadruped_medium::{self, BodyType as QMBodyType, Species as QMSpecies}, quadruped_small::{self, BodyType as QSBodyType, Species as QSSpecies}, @@ -22,7 +23,7 @@ use common::{ }; use hashbrown::HashMap; use serde::Deserialize; -use std::sync::Arc; +use std::{fmt, hash::Hash, sync::Arc}; use tracing::{error, warn}; use vek::*; @@ -4102,6 +4103,17 @@ impl QuadrupedLowLateralSpec { #[derive(Deserialize)] struct ObjectCentralSpec(HashMap); +/* +#[derive(Deserialize)] +struct ShipCentralSpec(HashMap); + +#[derive(Deserialize)] +struct SidedShipCentralVoxSpec { + bone0: ObjectCentralSubSpec, + bone1: ObjectCentralSubSpec, + bone2: ObjectCentralSubSpec, +}*/ + #[derive(Deserialize)] struct SidedObjectCentralVoxSpec { bone0: ObjectCentralSubSpec, @@ -4171,3 +4183,99 @@ impl ObjectCentralSpec { (central, Vec3::from(spec.bone1.offset)) } } + +/*make_vox_spec!( + ship::Body, + struct ShipSpec { + central: ShipCentralSpec = "server.manifests.ship_manifest", + }, + |FigureKey { body, .. }, spec| { + [ + Some(spec.central.read().0.mesh_bone( + body, |spec| &spec.bone0, + )), + Some(spec.central.read().0.mesh_bone( + body, |spec| &spec.bone1 + )), + Some(spec.central.read().0.mesh_bone( + body, |spec| &spec.bone2 + )), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ] + }, +); + +impl ShipCentralSpec { + fn mesh_bone &ObjectCentralSubSpec>(&self, obj: &ship::Body, f: F) -> BoneMeshes { + let spec = match self.0.get(&obj) { + Some(spec) => spec, + None => { + error!("No specification exists for {:?}", obj); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let bone = f(spec); + let central = graceful_load_segment(&bone.central.0); + + (central, Vec3::from(bone.offset)) + } +}*/ +fn mesh_ship_bone &ShipCentralSubSpec>(map: &HashMap, obj: &K, f: F) -> BoneMeshes { + let spec = match map.get(&obj) { + Some(spec) => spec, + None => { + error!("No specification exists for {:?}", obj); + return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); + }, + }; + let bone = f(spec); + let central = graceful_load_segment(&bone.central.0); + + (central, Vec3::from(bone.offset)) +} + +impl BodySpec for ship::Body { + type Spec = ShipSpec; + + #[allow(unused_variables)] + fn load_spec() -> Result, assets::Error> { + Self::Spec::load("") + } + + fn bone_meshes( + FigureKey { body, .. }: &FigureKey, + spec: &Self::Spec, + ) -> [Option; anim::MAX_BONE_COUNT] { + let map = &(spec.central.read().0).0; + [ + Some(mesh_ship_bone(map, body, |spec| &spec.bone0,)), + Some(mesh_ship_bone(map, body, |spec| &spec.bone1,)), + Some(mesh_ship_bone(map, body, |spec| &spec.bone2,)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + ] + } +} diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index db5146c923..12281f4edf 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -21,7 +21,7 @@ use anim::{ biped_large::BipedLargeSkeleton, biped_small::BipedSmallSkeleton, bird_medium::BirdMediumSkeleton, bird_small::BirdSmallSkeleton, character::CharacterSkeleton, dragon::DragonSkeleton, fish_medium::FishMediumSkeleton, fish_small::FishSmallSkeleton, - golem::GolemSkeleton, object::ObjectSkeleton, quadruped_low::QuadrupedLowSkeleton, + golem::GolemSkeleton, object::ObjectSkeleton, ship::ShipSkeleton, quadruped_low::QuadrupedLowSkeleton, quadruped_medium::QuadrupedMediumSkeleton, quadruped_small::QuadrupedSmallSkeleton, theropod::TheropodSkeleton, Animation, Skeleton, }; @@ -101,6 +101,7 @@ struct FigureMgrStates { biped_small_states: HashMap>, golem_states: HashMap>, object_states: HashMap>, + ship_states: HashMap>, } impl FigureMgrStates { @@ -120,6 +121,7 @@ impl FigureMgrStates { biped_small_states: HashMap::new(), golem_states: HashMap::new(), object_states: HashMap::new(), + ship_states: HashMap::new(), } } @@ -180,6 +182,7 @@ impl FigureMgrStates { .map(DerefMut::deref_mut), Body::Golem(_) => self.golem_states.get_mut(&entity).map(DerefMut::deref_mut), Body::Object(_) => self.object_states.get_mut(&entity).map(DerefMut::deref_mut), + Body::Ship(_) => self.ship_states.get_mut(&entity).map(DerefMut::deref_mut), } } @@ -205,6 +208,7 @@ impl FigureMgrStates { Body::BipedSmall(_) => self.biped_small_states.remove(&entity).map(|e| e.meta), Body::Golem(_) => self.golem_states.remove(&entity).map(|e| e.meta), Body::Object(_) => self.object_states.remove(&entity).map(|e| e.meta), + Body::Ship(_) => self.ship_states.remove(&entity).map(|e| e.meta), } } @@ -224,6 +228,7 @@ impl FigureMgrStates { self.biped_small_states.retain(|k, v| f(k, &mut *v)); self.golem_states.retain(|k, v| f(k, &mut *v)); self.object_states.retain(|k, v| f(k, &mut *v)); + self.ship_states.retain(|k, v| f(k, &mut *v)); } fn count(&self) -> usize { @@ -242,6 +247,7 @@ impl FigureMgrStates { + self.biped_small_states.len() + self.golem_states.len() + self.object_states.len() + + self.ship_states.len() } fn count_visible(&self) -> usize { @@ -314,6 +320,11 @@ impl FigureMgrStates { .iter() .filter(|(_, c)| c.visible()) .count() + + self + .ship_states + .iter() + .filter(|(_, c)| c.visible()) + .count() } } @@ -332,6 +343,7 @@ pub struct FigureMgr { biped_large_model_cache: FigureModelCache, biped_small_model_cache: FigureModelCache, object_model_cache: FigureModelCache, + ship_model_cache: FigureModelCache, golem_model_cache: FigureModelCache, states: FigureMgrStates, } @@ -353,6 +365,7 @@ impl FigureMgr { biped_large_model_cache: FigureModelCache::new(), biped_small_model_cache: FigureModelCache::new(), object_model_cache: FigureModelCache::new(), + ship_model_cache: FigureModelCache::new(), golem_model_cache: FigureModelCache::new(), states: FigureMgrStates::default(), } @@ -384,6 +397,7 @@ impl FigureMgr { self.biped_small_model_cache .clean(&mut self.col_lights, tick); self.object_model_cache.clean(&mut self.col_lights, tick); + self.ship_model_cache.clean(&mut self.col_lights, tick); self.golem_model_cache.clean(&mut self.col_lights, tick); } @@ -4088,6 +4102,79 @@ impl FigureMgr { _ => target_base, }; + state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); + state.update( + renderer, + pos.0, + ori, + scale, + col, + dt, + state_animation_rate, + model, + lpindex, + true, + is_player, + camera, + &mut update_buf, + terrain, + ); + }, + Body::Ship(body) => { + let (model, skeleton_attr) = self.ship_model_cache.get_or_create_model( + renderer, + &mut self.col_lights, + *body, + inventory, + tick, + player_camera_mode, + player_character_state, + scene_data.runtime, + ); + + let state = + self.states.ship_states.entry(entity).or_insert_with(|| { + FigureState::new(renderer, ShipSkeleton::default()) + }); + + let (character, last_character) = match (character, last_character) { + (Some(c), Some(l)) => (c, l), + _ => (&CharacterState::Idle, &Last { + 0: CharacterState::Idle, + }), + }; + + if !character.same_variant(&last_character.0) { + state.state_time = 0.0; + } + + let target_base = match ( + physics.on_ground, + vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + physics.in_liquid.is_some(), // In water + ) { + // Standing + (true, false, false) => anim::ship::IdleAnimation::update_skeleton( + &ShipSkeleton::default(), + (active_tool_kind, second_tool_kind, time), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + _ => anim::ship::IdleAnimation::update_skeleton( + &ShipSkeleton::default(), + (active_tool_kind, second_tool_kind, time), + state.state_time, + &mut state_animation_rate, + skeleton_attr, + ), + }; + + let target_bones = match &character { + // TODO! + _ => target_base, + }; + state.skeleton = anim::vek::Lerp::lerp(&state.skeleton, &target_bones, dt_lerp); state.update( renderer, @@ -4313,6 +4400,7 @@ impl FigureMgr { biped_large_model_cache, biped_small_model_cache, object_model_cache, + ship_model_cache, golem_model_cache, states: FigureMgrStates { @@ -4330,6 +4418,7 @@ impl FigureMgr { biped_small_states, golem_states, object_states, + ship_states, }, } = self; let col_lights = &*col_lights_; @@ -4572,6 +4661,23 @@ impl FigureMgr { ), ) }), + Body::Ship(body) => ship_states + .get(&entity) + .filter(|state| filter_state(&*state)) + .map(move |state| { + ( + state.locals(), + state.bone_consts(), + ship_model_cache.get_model( + col_lights, + *body, + inventory, + tick, + player_camera_mode, + character_state, + ), + ) + }), } { let model_entry = model_entry?; diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 822b3d6156..8bcbffd4bf 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1562,7 +1562,7 @@ fn under_cursor( let player_cylinder = Cylinder::from_components( player_pos, scales.get(player_entity).copied(), - colliders.get(player_entity).copied(), + colliders.get(player_entity).cloned(), char_states.get(player_entity), ); let terrain = client.state().terrain(); @@ -1643,7 +1643,7 @@ fn under_cursor( let target_cylinder = Cylinder::from_components( p, scales.get(*e).copied(), - colliders.get(*e).copied(), + colliders.get(*e).cloned(), char_states.get(*e), ); @@ -1706,7 +1706,7 @@ fn select_interactable( let player_cylinder = Cylinder::from_components( player_pos, scales.get(player_entity).copied(), - colliders.get(player_entity).copied(), + colliders.get(player_entity).cloned(), char_states.get(player_entity), ); @@ -1720,7 +1720,7 @@ fn select_interactable( .join() .filter(|(e, _, _, _, _)| *e != player_entity) .map(|(e, p, s, c, cs)| { - let cylinder = Cylinder::from_components(p.0, s.copied(), c.copied(), cs); + let cylinder = Cylinder::from_components(p.0, s.copied(), c.cloned(), cs); (e, cylinder) }) // Roughly filter out entities farther than interaction distance From 5d85775dcf6bb71bca0712af490620433b00f675 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Thu, 11 Mar 2021 17:27:03 -0500 Subject: [PATCH 02/41] Airship progress: now possessable, and physics kind of works (desyncs from the visuals by a shift + scale, and rotation isn't done at all yet, but the contours are correct). --- assets/server/manifests/ship_manifest.ron | 11 +- assets/server/voxel/Human_Airship.vox | 4 +- assets/voxygen/voxel/object/Human_Airship.vox | 4 +- common/src/comp/body/ship.rs | 57 +- common/src/comp/phys.rs | 4 +- common/src/volumes/dyna.rs | 10 + common/sys/src/phys.rs | 525 +++++++++--------- server/src/cmd.rs | 7 +- server/src/state_ext.rs | 16 +- voxygen/src/scene/figure/mod.rs | 8 +- 10 files changed, 359 insertions(+), 287 deletions(-) mode change 100644 => 120000 assets/voxygen/voxel/object/Human_Airship.vox diff --git a/assets/server/manifests/ship_manifest.ron b/assets/server/manifests/ship_manifest.ron index 42555fc051..4ccbe5fcc6 100644 --- a/assets/server/manifests/ship_manifest.ron +++ b/assets/server/manifests/ship_manifest.ron @@ -1,16 +1,17 @@ ({ DefaultAirship: ( bone0: ( - offset: (-20.0, -35.0, 1.0), + //offset: (-20.0, -35.0, 1.0), + offset: (3.0, 7.0, 1.0), central: ("object.Human_Airship"), ), bone1: ( - offset: (0.0, 0.0, 0.0), - central: ("propeller-l"), + offset: (0.0, 40.0, -8.0), + central: ("object.propeller-l"), ), bone2: ( - offset: (0.0, 0.0, 0.0), - central: ("propeller-r"), + offset: (0.0, 0.0, -4.0), + central: ("object.propeller-r"), ), ), }) diff --git a/assets/server/voxel/Human_Airship.vox b/assets/server/voxel/Human_Airship.vox index ab1ea75279..36e3758d03 100644 --- a/assets/server/voxel/Human_Airship.vox +++ b/assets/server/voxel/Human_Airship.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6355ef90e28c448e11ad79dc6311388a71b4e46628bb2cf82b6e5fd5f38cd254 -size 88100 +oid sha256:ba02746d73ebf853c0511b673510c09bd47e3ab0fff13d936feb181a8378ebd9 +size 78024 diff --git a/assets/voxygen/voxel/object/Human_Airship.vox b/assets/voxygen/voxel/object/Human_Airship.vox deleted file mode 100644 index 5d32a3168a..0000000000 --- a/assets/voxygen/voxel/object/Human_Airship.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:561dcec86218a94ae85268b3f1863cd3310e24c452dd2ba248e3c37b6aff45e5 -size 78024 diff --git a/assets/voxygen/voxel/object/Human_Airship.vox b/assets/voxygen/voxel/object/Human_Airship.vox new file mode 120000 index 0000000000..9995596018 --- /dev/null +++ b/assets/voxygen/voxel/object/Human_Airship.vox @@ -0,0 +1 @@ +../../../server/voxel/Human_Airship.vox \ No newline at end of file diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index ef2e93e113..f607cf2ae6 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -1,6 +1,4 @@ -use crate::{ - make_case_elim -}; +use crate::make_case_elim; use serde::{Deserialize, Serialize}; make_case_elim!( @@ -17,22 +15,29 @@ impl From for super::Body { } impl Body { - pub fn manifest_id(&self) -> &'static str { + pub fn manifest_entry(&self) -> &'static str { match self { - Body::DefaultAirship => "server.manifests.ship_manifest", + Body::DefaultAirship => "object.Human_Airship", } } } -/// Duplicate of some of the things defined in `voxygen::scene::figure::load` to avoid having to -/// refactor all of that to `common` for using voxels as collider geometry +/// Duplicate of some of the things defined in `voxygen::scene::figure::load` to +/// avoid having to refactor all of that to `common` for using voxels as +/// collider geometry pub mod figuredata { use crate::{ assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron}, - volumes::dyna::Dyna, + figure::cell::Cell, + terrain::{ + block::{Block, BlockKind}, + sprite::SpriteKind, + }, + volumes::dyna::{ColumnAccess, Dyna}, }; - use serde::Deserialize; use hashbrown::HashMap; + use lazy_static::lazy_static; + use serde::Deserialize; #[derive(Deserialize)] pub struct VoxSimple(pub String); @@ -57,13 +62,43 @@ pub mod figuredata { #[derive(Clone)] pub struct ShipSpec { pub central: AssetHandle>, + pub voxes: HashMap>, } impl assets::Compound for ShipSpec { - fn load(_: &assets::AssetCache, _: &str) -> Result { + fn load( + cache: &assets::AssetCache, + _: &str, + ) -> Result { + let manifest: AssetHandle> = AssetExt::load("server.manifests.ship_manifest")?; + let mut voxes = HashMap::new(); + for (_, spec) in (manifest.read().0).0.iter() { + for bone in [&spec.bone0, &spec.bone1, &spec.bone2].iter() { + // TODO: avoid the requirement for symlinks in "voxygen.voxel.object.", and load + // the models from "server.voxel." instead + let vox = + cache.load::(&["voxygen.voxel.", &bone.central.0].concat())?; + let dyna = Dyna::::from_vox(&vox.read().0, false); + voxes.insert(bone.central.0.clone(), dyna.map_into(|cell| { + if let Some(rgb) = cell.get_color() { + Block::new(BlockKind::Misc, rgb) + } else { + Block::air(SpriteKind::Empty) + } + })); + } + } Ok(ShipSpec { - central: AssetExt::load("server.manifests.ship_manifest")? + central: manifest, + voxes, }) } } + + lazy_static! { + // TODO: load this from the ECS as a resource, and maybe make it more general than ships + // (although figuring out how to keep the figure bones in sync with the terrain offsets seems + // like a hard problem if they're not the same manifest) + pub static ref VOXEL_COLLIDER_MANIFEST: ShipSpec = AssetExt::load_expect_cloned("server.manifests.ship_manifest"); + } } diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index 3e06418792..b10978cc58 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -68,7 +68,7 @@ pub enum Collider { impl Collider { pub fn get_radius(&self) -> f32 { match self { - Collider::Voxel { .. } => 0.0, + Collider::Voxel { .. } => 1.0, Collider::Box { radius, .. } => *radius, Collider::Point => 0.0, } @@ -76,7 +76,7 @@ impl Collider { pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) { match self { - Collider::Voxel { .. } => (0.0, 0.0), + Collider::Voxel { .. } => (0.0, 1.0), Collider::Box { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier), Collider::Point => (0.0, 0.0), } diff --git a/common/src/volumes/dyna.rs b/common/src/volumes/dyna.rs index 3e736c5db1..d0648010a8 100644 --- a/common/src/volumes/dyna.rs +++ b/common/src/volumes/dyna.rs @@ -44,6 +44,16 @@ impl Dyna { None } } + + pub fn map_into W>(self, f: F) -> Dyna { + let Dyna { vox, meta, sz, _phantom } = self; + Dyna { + vox: vox.into_iter().map(f).collect(), + meta, + sz, + _phantom, + } + } } impl BaseVol for Dyna { diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 8545888c7e..47c6abfd1e 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -1,7 +1,7 @@ use common::{ comp::{ BeamSegment, CharacterState, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, - PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, Vel, + PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, Vel, body::ship::figuredata::VOXEL_COLLIDER_MANIFEST, }, consts::{FRIC_GROUND, GRAVITY}, event::{EventBus, ServerEvent}, @@ -15,8 +15,9 @@ use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System}; use hashbrown::HashMap; use rayon::iter::ParallelIterator; use specs::{ - shred::{World, ResourceId}, - Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage, SystemData, + shred::{ResourceId, World}, + Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, WriteExpect, + WriteStorage, }; use std::ops::Range; use vek::*; @@ -114,7 +115,9 @@ impl<'a> PhysicsSystemData<'a> { ) .join() { - let _ = self.w.physics_states + let _ = self + .w + .physics_states .entry(entity) .map(|e| e.or_insert_with(Default::default)); } @@ -137,13 +140,16 @@ impl<'a> PhysicsSystemData<'a> { .map(|(e, _, _, _, _, _, _)| e) .collect::>() { - let _ = self.w.previous_phys_cache.insert(entity, PreviousPhysCache { - velocity_dt: Vec3::zero(), - center: Vec3::zero(), - collision_boundary: 0.0, - scale: 0.0, - scaled_radius: 0.0, - }); + let _ = self + .w + .previous_phys_cache + .insert(entity, PreviousPhysCache { + velocity_dt: Vec3::zero(), + center: Vec3::zero(), + collision_boundary: 0.0, + scale: 0.0, + scaled_radius: 0.0, + }); } //Update PreviousPhysCache @@ -180,10 +186,14 @@ impl<'a> PhysicsSystemData<'a> { } drop(guard); } + fn apply_pushback(&mut self, job: &mut Job) { span!(guard, "Apply pushback"); job.cpu_stats.measure(ParMode::Rayon); - let PhysicsSystemData { r: ref psdr, w: ref mut psdw } = self; + let PhysicsSystemData { + r: ref psdr, + w: ref mut psdw, + } = self; let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); let metrics = ( &psdr.entities, @@ -316,8 +326,13 @@ impl<'a> PhysicsSystemData<'a> { entity_entity_collisions += 1; } - // Don't apply repulsive force to projectiles - if diff.magnitude_squared() > 0.0 && !is_projectile { + // Don't apply repulsive force to projectiles or if we're colliding + // with a terrain-like entity, or if we are a terrain-like entity + if diff.magnitude_squared() > 0.0 + && !is_projectile + && !matches!(collider_other, Some(Collider::Voxel { .. })) + && !matches!(collider, Some(Collider::Voxel { .. })) + { let force = 400.0 * (collision_dist - diff.magnitude()) * mass_other / (mass + mass_other); @@ -346,265 +361,273 @@ impl<'a> PhysicsSystemData<'a> { entity_entity_collisions: old.entity_entity_collisions + new.entity_entity_collisions, }); - psdw.physics_metrics.entity_entity_collision_checks = metrics.entity_entity_collision_checks; + psdw.physics_metrics.entity_entity_collision_checks = + metrics.entity_entity_collision_checks; psdw.physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; drop(guard); } fn handle_movement_and_terrain(&mut self, job: &mut Job) { - let PhysicsSystemData { r: ref psdr, w: ref mut psdw } = self; + let PhysicsSystemData { + r: ref psdr, + w: ref mut psdw, + } = self; // Apply movement inputs span!(guard, "Apply movement and terrain collision"); let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); - let (pos_writes, land_on_grounds) = - ( - &psdr.entities, - psdr.scales.maybe(), - psdr.stickies.maybe(), - &psdr.colliders, - positions, - &mut psdw.velocities, - &psdw.orientations, - &mut psdw.physics_states, - previous_phys_cache, - !&psdr.mountings, - ) - .par_join() - .fold( - || (Vec::new(), Vec::new()), - |(mut pos_writes, mut land_on_grounds), - ( - entity, - scale, - sticky, - collider, - pos, - mut vel, - _ori, - mut physics_state, - previous_cache, - _, - )| { - // defer the writes of positions to allow an inner loop over terrain-like - // entities - let old_pos = *pos; - let mut pos = *pos; - if sticky.is_some() && physics_state.on_surface().is_some() { - vel.0 = Vec3::zero(); - return (pos_writes, land_on_grounds); - } + let (pos_writes, land_on_grounds) = ( + &psdr.entities, + psdr.scales.maybe(), + psdr.stickies.maybe(), + &psdr.colliders, + positions, + &mut psdw.velocities, + &psdw.orientations, + &mut psdw.physics_states, + previous_phys_cache, + !&psdr.mountings, + ) + .par_join() + .fold( + || (Vec::new(), Vec::new()), + |(mut pos_writes, mut land_on_grounds), + ( + entity, + scale, + sticky, + collider, + pos, + mut vel, + _ori, + mut physics_state, + previous_cache, + _, + )| { + // defer the writes of positions to allow an inner loop over terrain-like + // entities + let old_pos = *pos; + let mut pos = *pos; + if sticky.is_some() && physics_state.on_surface().is_some() { + vel.0 = Vec3::zero(); + return (pos_writes, land_on_grounds); + } - let scale = if let Collider::Voxel { .. } = collider { - scale.map(|s| s.0).unwrap_or(1.0) + let scale = if let Collider::Voxel { .. } = collider { + scale.map(|s| s.0).unwrap_or(1.0) + } else { + // TODO: Use scale & actual proportions when pathfinding is good + // enough to manage irregular entity sizes + 1.0 + }; + + let old_vel = *vel; + // Integrate forces + // Friction is assumed to be a constant dependent on location + let friction = FRIC_AIR + .max(if physics_state.on_ground { + FRIC_GROUND } else { - // TODO: Use scale & actual proportions when pathfinding is good - // enough to manage irregular entity sizes - 1.0 - }; - - let old_vel = *vel; - // Integrate forces - // Friction is assumed to be a constant dependent on location - let friction = FRIC_AIR - .max(if physics_state.on_ground { - FRIC_GROUND - } else { - 0.0 - }) - .max(if physics_state.in_liquid.is_some() { - FRIC_FLUID - } else { - 0.0 - }); - let in_loaded_chunk = psdr.terrain - .get_key(psdr.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) - .is_some(); - let downward_force = - if !in_loaded_chunk { - 0.0 // No gravity in unloaded chunks - } else if physics_state - .in_liquid - .map(|depth| depth > 0.75) - .unwrap_or(false) - { - (1.0 - BOUYANCY) * GRAVITY - } else { - GRAVITY - } * psdr.gravities.get(entity).map(|g| g.0).unwrap_or_default(); - vel.0 = integrate_forces(psdr.dt.0, vel.0, downward_force, friction); - - // Don't move if we're not in a loaded chunk - let pos_delta = if in_loaded_chunk { - // this is an approximation that allows most framerates to - // behave in a similar manner. - let dt_lerp = 0.2; - (vel.0 * dt_lerp + old_vel.0 * (1.0 - dt_lerp)) * psdr.dt.0 + 0.0 + }) + .max(if physics_state.in_liquid.is_some() { + FRIC_FLUID } else { - Vec3::zero() - }; - - match &*collider { - Collider::Voxel { .. } => { - // for now, treat entities with voxel colliders as their bounding - // cylinders for the purposes of colliding them with terrain - let radius = collider.get_radius() * scale; - let (z_min, z_max) = collider.get_z_limits(scale); - - let cylinder = (radius, z_min, z_max); - cylinder_voxel_collision( - cylinder, - &*psdr.terrain, - entity, - &mut pos, - pos_delta, - vel, - &mut physics_state, - &mut land_on_grounds, - ); - }, - Collider::Box { - radius, - z_min, - z_max, - } => { - // Scale collider - let radius = radius.min(0.45) * scale; - let z_min = *z_min * scale; - let z_max = z_max.clamped(1.2, 1.95) * scale; - - let cylinder = (radius, z_min, z_max); - cylinder_voxel_collision( - cylinder, - &*psdr.terrain, - entity, - &mut pos, - pos_delta, - vel, - &mut physics_state, - &mut land_on_grounds, - ); - }, - Collider::Point => { - let (dist, block) = psdr.terrain - .ray(pos.0, pos.0 + pos_delta) - .until(|block: &Block| block.is_filled()) - .ignore_error() - .cast(); - - pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist; - - // Can't fail since we do ignore_error above - if block.unwrap().is_some() { - let block_center = pos.0.map(|e| e.floor()) + 0.5; - let block_rpos = (pos.0 - block_center) - .try_normalized() - .unwrap_or(Vec3::zero()); - - // See whether we're on the top/bottom of a block, or the side - if block_rpos.z.abs() - > block_rpos.xy().map(|e| e.abs()).reduce_partial_max() - { - if block_rpos.z > 0.0 { - physics_state.on_ground = true; - } else { - physics_state.on_ceiling = true; - } - vel.0.z = 0.0; - } else { - physics_state.on_wall = - Some(if block_rpos.x.abs() > block_rpos.y.abs() { - vel.0.x = 0.0; - Vec3::unit_x() * -block_rpos.x.signum() - } else { - vel.0.y = 0.0; - Vec3::unit_y() * -block_rpos.y.signum() - }); - } - } - - physics_state.in_liquid = psdr.terrain - .get(pos.0.map(|e| e.floor() as i32)) - .ok() - .and_then(|vox| vox.is_liquid().then_some(1.0)); - }, - } - - // Collide with terrain-like entities - for ( - entity_other, - other, - pos_other, - previous_cache_other, - mass_other, - collider_other, - _, - _, - _, - _, - char_state_other_maybe, - ) in ( - &psdr.entities, - &psdr.uids, - positions, - previous_phys_cache, - psdr.masses.maybe(), - &psdr.colliders, - !&psdr.projectiles, - !&psdr.mountings, - !&psdr.beams, - !&psdr.shockwaves, - psdr.char_states.maybe(), - ) - .join() + 0.0 + }); + let in_loaded_chunk = psdr + .terrain + .get_key(psdr.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) + .is_some(); + let downward_force = + if !in_loaded_chunk { + 0.0 // No gravity in unloaded chunks + } else if physics_state + .in_liquid + .map(|depth| depth > 0.75) + .unwrap_or(false) { - let collision_boundary = previous_cache.collision_boundary - + previous_cache_other.collision_boundary; - if previous_cache - .center - .distance_squared(previous_cache_other.center) - > collision_boundary.powi(2) - || entity == entity_other - { - continue; + (1.0 - BOUYANCY) * GRAVITY + } else { + GRAVITY + } * psdr.gravities.get(entity).map(|g| g.0).unwrap_or_default(); + vel.0 = integrate_forces(psdr.dt.0, vel.0, downward_force, friction); + + // Don't move if we're not in a loaded chunk + let pos_delta = if in_loaded_chunk { + // this is an approximation that allows most framerates to + // behave in a similar manner. + let dt_lerp = 0.2; + (vel.0 * dt_lerp + old_vel.0 * (1.0 - dt_lerp)) * psdr.dt.0 + } else { + Vec3::zero() + }; + + match &*collider { + Collider::Voxel { .. } => { + // for now, treat entities with voxel colliders as their bounding + // cylinders for the purposes of colliding them with terrain + let radius = collider.get_radius() * scale; + let (z_min, z_max) = collider.get_z_limits(scale); + + let cylinder = (radius, z_min, z_max); + cylinder_voxel_collision( + cylinder, + &*psdr.terrain, + entity, + &mut pos, + pos_delta, + vel, + &mut physics_state, + &mut land_on_grounds, + ); + }, + Collider::Box { + radius, + z_min, + z_max, + } => { + // Scale collider + let radius = radius.min(0.45) * scale; + let z_min = *z_min * scale; + let z_max = z_max.clamped(1.2, 1.95) * scale; + + let cylinder = (radius, z_min, z_max); + cylinder_voxel_collision( + cylinder, + &*psdr.terrain, + entity, + &mut pos, + pos_delta, + vel, + &mut physics_state, + &mut land_on_grounds, + ); + }, + Collider::Point => { + let (dist, block) = psdr + .terrain + .ray(pos.0, pos.0 + pos_delta) + .until(|block: &Block| block.is_filled()) + .ignore_error() + .cast(); + + pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist; + + // Can't fail since we do ignore_error above + if block.unwrap().is_some() { + let block_center = pos.0.map(|e| e.floor()) + 0.5; + let block_rpos = (pos.0 - block_center) + .try_normalized() + .unwrap_or(Vec3::zero()); + + // See whether we're on the top/bottom of a block, or the side + if block_rpos.z.abs() + > block_rpos.xy().map(|e| e.abs()).reduce_partial_max() + { + if block_rpos.z > 0.0 { + physics_state.on_ground = true; + } else { + physics_state.on_ceiling = true; + } + vel.0.z = 0.0; + } else { + physics_state.on_wall = + Some(if block_rpos.x.abs() > block_rpos.y.abs() { + vel.0.x = 0.0; + Vec3::unit_x() * -block_rpos.x.signum() + } else { + vel.0.y = 0.0; + Vec3::unit_y() * -block_rpos.y.signum() + }); + } } - if let Collider::Voxel { id } = collider_other { - // use bounding cylinder regardless of our collider - // TODO: extract point-terrain collision above to its own function - let radius = collider.get_radius() * scale; - let (z_min, z_max) = collider.get_z_limits(scale); + physics_state.in_liquid = psdr + .terrain + .get(pos.0.map(|e| e.floor() as i32)) + .ok() + .and_then(|vox| vox.is_liquid().then_some(1.0)); + }, + } - let cylinder = (radius, z_min, z_max); - // TODO: load .vox into a Dyna, and use it (appropriately rotated) - // as the terrain - /*cylinder_voxel_collision( + // Collide with terrain-like entities + for ( + entity_other, + other, + pos_other, + previous_cache_other, + mass_other, + collider_other, + _, + _, + _, + _, + char_state_other_maybe, + ) in ( + &psdr.entities, + &psdr.uids, + positions, + previous_phys_cache, + psdr.masses.maybe(), + &psdr.colliders, + !&psdr.projectiles, + !&psdr.mountings, + !&psdr.beams, + !&psdr.shockwaves, + psdr.char_states.maybe(), + ) + .join() + { + /*let collision_boundary = previous_cache.collision_boundary + + previous_cache_other.collision_boundary; + if previous_cache + .center + .distance_squared(previous_cache_other.center) + > collision_boundary.powi(2) + || entity == entity_other + { + continue; + }*/ + + if let Collider::Voxel { id } = collider_other { + // use bounding cylinder regardless of our collider + // TODO: extract point-terrain collision above to its own function + let radius = collider.get_radius() * scale; + let (z_min, z_max) = collider.get_z_limits(scale); + + pos.0 -= pos_other.0; + let cylinder = (radius, z_min, z_max); + if let Some(dyna) = VOXEL_COLLIDER_MANIFEST.voxes.get(id) { + cylinder_voxel_collision( cylinder, - &*psdr.terrain, + &*dyna, entity, &mut pos, pos_delta, vel, &mut physics_state, &mut land_on_grounds, - );*/ + ); } + pos.0 += pos_other.0; } - if pos != old_pos { - pos_writes.push((entity, pos)); - } + } + if pos != old_pos { + pos_writes.push((entity, pos)); + } - (pos_writes, land_on_grounds) - }, - ) - .reduce( - || (Vec::new(), Vec::new()), - |(mut pos_writes_a, mut land_on_grounds_a), - (mut pos_writes_b, mut land_on_grounds_b)| { - pos_writes_a.append(&mut pos_writes_b); - land_on_grounds_a.append(&mut land_on_grounds_b); - (pos_writes_a, land_on_grounds_a) - }, - ); + (pos_writes, land_on_grounds) + }, + ) + .reduce( + || (Vec::new(), Vec::new()), + |(mut pos_writes_a, mut land_on_grounds_a), + (mut pos_writes_b, mut land_on_grounds_b)| { + pos_writes_a.append(&mut pos_writes_b); + land_on_grounds_a.append(&mut land_on_grounds_b); + (pos_writes_a, land_on_grounds_a) + }, + ); drop(guard); job.cpu_stats.measure(ParMode::Single); @@ -631,10 +654,7 @@ impl<'a> System<'a> for Sys { #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 - fn run( - job: &mut Job, - mut psd: Self::SystemData, - ) { + fn run(job: &mut Job, mut psd: Self::SystemData) { psd.reset(); // Apply pushback @@ -653,7 +673,6 @@ impl<'a> System<'a> for Sys { psd.maintain_pushback_cache(); psd.apply_pushback(job); - psd.handle_movement_and_terrain(job); } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 7e6dc4c454..359bc42413 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -993,11 +993,12 @@ fn handle_spawn_airship( _action: &ChatCommand, ) { match server.state.read_component_copied::(target) { - Some(pos) => { + Some(mut pos) => { + pos.0.z += 50.0; server .state - .create_ship(pos, comp::ship::Body::DefaultAirship) - .with(comp::Scale(50.0)) + .create_ship(pos, comp::ship::Body::DefaultAirship, 1) + .with(comp::Scale(11.0)) .with(LightEmitter { col: Rgb::new(1.0, 0.65, 0.2), strength: 2.0, diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 770b6d28b0..0fdb8c124f 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -41,7 +41,7 @@ pub trait StateExt { ) -> EcsEntityBuilder; /// Build a static object entity fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder; - fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body) -> EcsEntityBuilder; + fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body, level: u16) -> EcsEntityBuilder; /// Build a projectile fn create_projectile( &mut self, @@ -216,16 +216,24 @@ impl StateExt for State { .with(comp::Gravity(1.0)) } - fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body) -> EcsEntityBuilder { + fn create_ship(&mut self, pos: comp::Pos, ship: comp::ship::Body, level: u16) -> EcsEntityBuilder { self.ecs_mut() .create_entity_synced() .with(pos) .with(comp::Vel(Vec3::zero())) .with(comp::Ori::default()) .with(comp::Mass(50.0)) - .with(comp::Collider::Voxel { id: object.manifest_id().to_string() }) - .with(comp::Body::Ship(object)) + .with(comp::Collider::Voxel { id: ship.manifest_entry().to_string() }) + .with(comp::Body::Ship(ship)) .with(comp::Gravity(1.0)) + .with(comp::Controller::default()) + .with(comp::inventory::Inventory::new_empty()) + .with(comp::CharacterState::default()) + .with(comp::Energy::new(ship.into(), level)) + .with(comp::Health::new(ship.into(), level)) + .with(comp::Stats::new("Airship".to_string())) + .with(comp::Buffs::default()) + .with(comp::Combo::default()) } fn create_projectile( diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 12281f4edf..2bba21b833 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -4231,7 +4231,7 @@ impl FigureMgr { .join() // Don't render dead entities .filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead)) - .for_each(|(entity, pos, _, body, _, inventory, _)| { + .for_each(|(entity, pos, _, body, _, inventory, scale)| { if let Some((locals, bone_consts, model, _)) = self.get_model_for_render( tick, camera, @@ -4241,7 +4241,7 @@ impl FigureMgr { inventory, false, pos.0, - figure_lod_render_distance, + figure_lod_render_distance * scale.map_or(1.0, |s| s.0), |state| state.can_shadow_sun(), ) { renderer.render_figure_shadow_directed( @@ -4273,7 +4273,7 @@ impl FigureMgr { let character_state_storage = state.read_storage::(); let character_state = character_state_storage.get(player_entity); - for (entity, pos, _, body, _, inventory, _) in ( + for (entity, pos, _, body, _, inventory, scale) in ( &ecs.entities(), &ecs.read_storage::(), ecs.read_storage::().maybe(), @@ -4298,7 +4298,7 @@ impl FigureMgr { inventory, false, pos.0, - figure_lod_render_distance, + figure_lod_render_distance * scale.map_or(1.0, |s| s.0), |state| state.visible(), ) { renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod); From bcd3799395e1aac923a27a4c4b0aa4359d0d21ca Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Thu, 11 Mar 2021 19:01:16 -0500 Subject: [PATCH 03/41] Airship fixes: figure culling, player hitbox bounds w.r.t. airships, physics state unioning. --- assets/server/manifests/ship_manifest.ron | 3 ++- common/sys/src/phys.rs | 32 +++++++++++++++++++---- server/src/cmd.rs | 2 +- voxygen/src/scene/figure/mod.rs | 1 + 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/assets/server/manifests/ship_manifest.ron b/assets/server/manifests/ship_manifest.ron index 4ccbe5fcc6..e8e96539ca 100644 --- a/assets/server/manifests/ship_manifest.ron +++ b/assets/server/manifests/ship_manifest.ron @@ -2,7 +2,8 @@ DefaultAirship: ( bone0: ( //offset: (-20.0, -35.0, 1.0), - offset: (3.0, 7.0, 1.0), + //offset: (3.0, 7.0, 1.0), + offset: (0.25, 0.25, 0.25), central: ("object.Human_Airship"), ), bone1: ( diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 47c6abfd1e..16d6af3a39 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -1,7 +1,8 @@ use common::{ comp::{ - BeamSegment, CharacterState, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, - PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, Vel, body::ship::figuredata::VOXEL_COLLIDER_MANIFEST, + body::ship::figuredata::VOXEL_COLLIDER_MANIFEST, BeamSegment, CharacterState, Collider, + Gravity, Mass, Mounting, Ori, PhysicsState, Pos, PreviousPhysCache, Projectile, Scale, + Shockwave, Sticky, Vel, }, consts::{FRIC_GROUND, GRAVITY}, event::{EventBus, ServerEvent}, @@ -592,9 +593,14 @@ impl<'a> PhysicsSystemData<'a> { if let Collider::Voxel { id } = collider_other { // use bounding cylinder regardless of our collider // TODO: extract point-terrain collision above to its own function - let radius = collider.get_radius() * scale; - let (z_min, z_max) = collider.get_z_limits(scale); + let radius = collider.get_radius(); + let (z_min, z_max) = collider.get_z_limits(1.0); + let radius = radius.min(0.45) * scale; + let z_min = z_min * scale; + let z_max = z_max.clamped(1.2, 1.95) * scale; + + let mut physics_state_delta = physics_state.clone(); pos.0 -= pos_other.0; let cylinder = (radius, z_min, z_max); if let Some(dyna) = VOXEL_COLLIDER_MANIFEST.voxes.get(id) { @@ -605,11 +611,27 @@ impl<'a> PhysicsSystemData<'a> { &mut pos, pos_delta, vel, - &mut physics_state, + &mut physics_state_delta, &mut land_on_grounds, ); } pos.0 += pos_other.0; + // union in the state updates, so that the state isn't just based on + // the most recent terrain that collision was attempted with + physics_state.on_ground |= physics_state_delta.on_ground; + physics_state.on_ceiling |= physics_state_delta.on_ceiling; + physics_state.on_wall = + physics_state.on_wall.or(physics_state_delta.on_wall); + physics_state + .touch_entities + .append(&mut physics_state_delta.touch_entities); + physics_state.in_liquid = + match (physics_state.in_liquid, physics_state_delta.in_liquid) { + // this match computes `x <|> y <|> liftA2 max x y` + (Some(x), Some(y)) => Some(x.max(y)), + (_, y @ Some(_)) => y, + _ => None, + }; } } if pos != old_pos { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 359bc42413..4982592e3e 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -998,7 +998,7 @@ fn handle_spawn_airship( server .state .create_ship(pos, comp::ship::Body::DefaultAirship, 1) - .with(comp::Scale(11.0)) + .with(comp::Scale(11.0 / 0.8)) .with(LightEmitter { col: Rgb::new(1.0, 0.65, 0.2), strength: 2.0, diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 2bba21b833..f6ae55df31 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -687,6 +687,7 @@ impl FigureMgr { let (in_frustum, lpindex) = if let Some(mut meta) = state { let (in_frustum, lpindex) = BoundingSphere::new(pos.0.into_array(), radius) .coherent_test_against_frustum(frustum, meta.lpindex); + let in_frustum = in_frustum || matches!(body, Body::Ship(_)); meta.visible = in_frustum; meta.lpindex = lpindex; if in_frustum { From 6ff054099f7fbe82c212391a729b080345ecc74d Mon Sep 17 00:00:00 2001 From: James Melkonian Date: Thu, 11 Mar 2021 18:58:57 -0800 Subject: [PATCH 04/41] Allow spawned airships to move --- assets/voxygen/voxel/object/Human_Airship.vox | 4 +- assets/voxygen/voxel/object/airship.vox | 4 +- assets/voxygen/voxel/object/propeller-l.vox | 4 +- assets/voxygen/voxel/object/propeller-r.vox | 4 +- common/src/cmd.rs | 2 +- common/src/comp/agent.rs | 9 +++ common/src/rtsim.rs | 6 ++ server/src/cmd.rs | 7 ++- server/src/rtsim/entity.rs | 5 ++ server/src/state_ext.rs | 59 +++++++++++++------ server/src/sys/agent.rs | 2 +- 11 files changed, 78 insertions(+), 28 deletions(-) diff --git a/assets/voxygen/voxel/object/Human_Airship.vox b/assets/voxygen/voxel/object/Human_Airship.vox index 9995596018..36e3758d03 120000 --- a/assets/voxygen/voxel/object/Human_Airship.vox +++ b/assets/voxygen/voxel/object/Human_Airship.vox @@ -1 +1,3 @@ -../../../server/voxel/Human_Airship.vox \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:ba02746d73ebf853c0511b673510c09bd47e3ab0fff13d936feb181a8378ebd9 +size 78024 diff --git a/assets/voxygen/voxel/object/airship.vox b/assets/voxygen/voxel/object/airship.vox index 3479493953..06bebaa938 120000 --- a/assets/voxygen/voxel/object/airship.vox +++ b/assets/voxygen/voxel/object/airship.vox @@ -1 +1,3 @@ -../../../server/voxel/airship.vox \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:86f317298900ea98f95c6a33192b25fbbcbd3ce5f105cad58ad3c595a7a7d9ee +size 70176 diff --git a/assets/voxygen/voxel/object/propeller-l.vox b/assets/voxygen/voxel/object/propeller-l.vox index a8105d8b1b..a193fa89ee 120000 --- a/assets/voxygen/voxel/object/propeller-l.vox +++ b/assets/voxygen/voxel/object/propeller-l.vox @@ -1 +1,3 @@ -../../../server/voxel/propeller-l.vox \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:09ef4bad2557abcc5a2b938f21053babc7770ebe2333039aef9b98ba930b7ec7 +size 1584 diff --git a/assets/voxygen/voxel/object/propeller-r.vox b/assets/voxygen/voxel/object/propeller-r.vox index 647f3f66d0..5b940751e6 120000 --- a/assets/voxygen/voxel/object/propeller-r.vox +++ b/assets/voxygen/voxel/object/propeller-r.vox @@ -1 +1,3 @@ -../../../server/voxel/propeller-r.vox \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:e4947977524b88bc5adfa934d9061a3499e94b960abb3bcf0a3e2aca482096dc +size 1584 diff --git a/common/src/cmd.rs b/common/src/cmd.rs index a74f0cab09..9aee158547 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -224,7 +224,7 @@ impl ChatCommand { "Temporarily gives a player admin permissions or removes them", Admin, ), - ChatCommand::Airship => cmd(vec![], "Spawns an airship", Admin), + ChatCommand::Airship => cmd(vec![Boolean("moving", "true".to_string(), Optional)], "Spawns an airship", Admin), ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin), ChatCommand::Ban => cmd( vec![Any("username", Required), Message(Optional)], diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 9134e40091..eccf2ce9fa 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -222,6 +222,15 @@ impl Agent { self } + pub fn with_destination() -> Self { + Self { + can_speak: false, + psyche: Psyche { aggro: 1.0 }, + rtsim_controller: RtSimController::zero(), + ..Default::default() + } + } + pub fn new( patrol_origin: Option>, can_speak: bool, diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index e5c1268d2e..809a505b1f 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -46,4 +46,10 @@ impl Default for RtSimController { impl RtSimController { pub fn reset(&mut self) { *self = Self::default(); } + pub fn zero() -> Self { + Self { + travel_to: Some((Vec3::new(0.0, 0.0, 500.0), "".to_string())), + speed_factor: 0.05, + } + } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 4982592e3e..0fbc8bcd0f 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -989,15 +989,16 @@ fn handle_spawn_airship( server: &mut Server, client: EcsEntity, target: EcsEntity, - _args: String, - _action: &ChatCommand, + args: String, + action: &ChatCommand, ) { + let moving = scan_fmt_some!(&args, &action.arg_fmt(), String).unwrap_or_else(|| "false".to_string()) == "true"; match server.state.read_component_copied::(target) { Some(mut pos) => { pos.0.z += 50.0; server .state - .create_ship(pos, comp::ship::Body::DefaultAirship, 1) + .create_ship(pos, comp::ship::Body::DefaultAirship, 1, moving) .with(comp::Scale(11.0 / 0.8)) .with(LightEmitter { col: Rgb::new(1.0, 0.65, 0.2), diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index a65a555890..2a8ef629d7 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -28,6 +28,9 @@ impl Entity { pub fn get_body(&self) -> comp::Body { match self.rng(PERM_GENUS).gen::() { //we want 50% birds, 50% humans for now + x if x < 0.05 => { + comp::Body::Ship(comp::ship::Body::DefaultAirship) + }, x if x < 0.50 => { let species = *(&comp::bird_medium::ALL_SPECIES) .choose(&mut self.rng(PERM_SPECIES)) @@ -53,6 +56,7 @@ impl Entity { comp::Body::BirdSmall(_) => "Warbler".to_string(), comp::Body::Dragon(b) => get_npc_name(&npc_names.dragon, b.species).to_string(), comp::Body::Humanoid(b) => get_npc_name(&npc_names.humanoid, b.species).to_string(), + comp::Body::Ship(_) => "Veloren Air".to_string(), //TODO: finish match as necessary _ => unimplemented!(), } @@ -131,6 +135,7 @@ impl Entity { .iter() .filter(|s| match self.get_body() { comp::Body::Humanoid(_) => s.1.is_settlement() | s.1.is_castle(), + comp::Body::Ship(_) => s.1.is_castle(), _ => s.1.is_dungeon(), }) .filter(|_| thread_rng().gen_range(0i32..4) == 0) diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 0fdb8c124f..787483df06 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -41,7 +41,7 @@ pub trait StateExt { ) -> EcsEntityBuilder; /// Build a static object entity fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder; - fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body, level: u16) -> EcsEntityBuilder; + fn create_ship(&mut self, pos: comp::Pos, ship: comp::ship::Body, level: u16, moving: bool) -> EcsEntityBuilder; /// Build a projectile fn create_projectile( &mut self, @@ -216,24 +216,45 @@ impl StateExt for State { .with(comp::Gravity(1.0)) } - fn create_ship(&mut self, pos: comp::Pos, ship: comp::ship::Body, level: u16) -> EcsEntityBuilder { - self.ecs_mut() - .create_entity_synced() - .with(pos) - .with(comp::Vel(Vec3::zero())) - .with(comp::Ori::default()) - .with(comp::Mass(50.0)) - .with(comp::Collider::Voxel { id: ship.manifest_entry().to_string() }) - .with(comp::Body::Ship(ship)) - .with(comp::Gravity(1.0)) - .with(comp::Controller::default()) - .with(comp::inventory::Inventory::new_empty()) - .with(comp::CharacterState::default()) - .with(comp::Energy::new(ship.into(), level)) - .with(comp::Health::new(ship.into(), level)) - .with(comp::Stats::new("Airship".to_string())) - .with(comp::Buffs::default()) - .with(comp::Combo::default()) + fn create_ship(&mut self, pos: comp::Pos, ship: comp::ship::Body, level: u16, moving: bool) -> EcsEntityBuilder { + if moving { + self.ecs_mut() + .create_entity_synced() + .with(pos) + .with(comp::Vel(Vec3::zero())) + .with(comp::Ori::default()) + .with(comp::Mass(50.0)) + .with(comp::Collider::Voxel { id: ship.manifest_entry().to_string() }) + .with(comp::Body::Ship(ship)) + .with(comp::Gravity(1.0)) + .with(comp::Controller::default()) + .with(comp::inventory::Inventory::new_empty()) + .with(comp::CharacterState::default()) + .with(comp::Energy::new(ship.into(), level)) + .with(comp::Health::new(ship.into(), level)) + .with(comp::Stats::new("Airship".to_string())) + .with(comp::Buffs::default()) + .with(comp::Combo::default()) + .with(comp::Agent::with_destination()) + } else { + self.ecs_mut() + .create_entity_synced() + .with(pos) + .with(comp::Vel(Vec3::zero())) + .with(comp::Ori::default()) + .with(comp::Mass(50.0)) + .with(comp::Collider::Voxel { id: ship.manifest_entry().to_string() }) + .with(comp::Body::Ship(ship)) + .with(comp::Gravity(1.0)) + .with(comp::Controller::default()) + .with(comp::inventory::Inventory::new_empty()) + .with(comp::CharacterState::default()) + .with(comp::Energy::new(ship.into(), level)) + .with(comp::Health::new(ship.into(), level)) + .with(comp::Stats::new("Airship".to_string())) + .with(comp::Buffs::default()) + .with(comp::Combo::default()) + } } fn create_projectile( diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 74cc3aae94..fda3106b13 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -624,7 +624,7 @@ impl<'a> AgentData<'a> { .ray( self.pos.0 + Vec3::unit_z(), self.pos.0 - + bearing.try_normalized().unwrap_or_else(Vec3::unit_y) * 60.0 + + bearing.try_normalized().unwrap_or_else(Vec3::unit_y) * 80.0 + Vec3::unit_z(), ) .until(Block::is_solid) From e9aab63a044ef12e1670967b60e900ca8d21bb05 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Thu, 11 Mar 2021 23:42:53 -0500 Subject: [PATCH 05/41] Account for model translation in the physics. The voxel collider still needs to be north-aligned for now. --- assets/server/manifests/ship_manifest.ron | 15 +++-- common/src/comp/body/ship.rs | 29 ++++++--- common/src/comp/ori.rs | 2 +- common/sys/src/phys.rs | 75 +++++++++++++++-------- voxygen/src/render/renderer.rs | 2 +- voxygen/src/scene/figure/load.rs | 8 ++- 6 files changed, 90 insertions(+), 41 deletions(-) diff --git a/assets/server/manifests/ship_manifest.ron b/assets/server/manifests/ship_manifest.ron index e8e96539ca..6b361aa535 100644 --- a/assets/server/manifests/ship_manifest.ron +++ b/assets/server/manifests/ship_manifest.ron @@ -1,18 +1,23 @@ ({ DefaultAirship: ( bone0: ( - //offset: (-20.0, -35.0, 1.0), //offset: (3.0, 7.0, 1.0), - offset: (0.25, 0.25, 0.25), - central: ("object.Human_Airship"), + //offset: (-20.75, -34.75, 1.25), + //offset: (0.0, 0.0, 0.0), + offset: (-20.0, -35.0, 1.0), + //phys_offset: (0.25, 0.25, 0.25), + phys_offset: (0.0, 0.0, 0.0), + central: ("Human_Airship"), ), bone1: ( offset: (0.0, 40.0, -8.0), - central: ("object.propeller-l"), + phys_offset: (0.0, 0.0, 0.0), + central: ("propeller-l"), ), bone2: ( offset: (0.0, 0.0, -4.0), - central: ("object.propeller-r"), + phys_offset: (0.0, 0.0, 0.0), + central: ("propeller-r"), ), ), }) diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index f607cf2ae6..3cab7a0ecb 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -17,7 +17,7 @@ impl From for super::Body { impl Body { pub fn manifest_entry(&self) -> &'static str { match self { - Body::DefaultAirship => "object.Human_Airship", + Body::DefaultAirship => "Human_Airship", } } } @@ -38,6 +38,7 @@ pub mod figuredata { use hashbrown::HashMap; use lazy_static::lazy_static; use serde::Deserialize; + use vek::Vec3; #[derive(Deserialize)] pub struct VoxSimple(pub String); @@ -55,6 +56,7 @@ pub mod figuredata { #[derive(Deserialize)] pub struct ShipCentralSubSpec { pub offset: [f32; 3], + pub phys_offset: [f32; 3], pub central: VoxSimple, } @@ -62,7 +64,13 @@ pub mod figuredata { #[derive(Clone)] pub struct ShipSpec { pub central: AssetHandle>, - pub voxes: HashMap>, + pub colliders: HashMap, + } + + #[derive(Clone)] + pub struct VoxelCollider { + pub dyna: Dyna, + pub translation: Vec3, } impl assets::Compound for ShipSpec { @@ -71,26 +79,31 @@ pub mod figuredata { _: &str, ) -> Result { let manifest: AssetHandle> = AssetExt::load("server.manifests.ship_manifest")?; - let mut voxes = HashMap::new(); + let mut colliders = HashMap::new(); for (_, spec) in (manifest.read().0).0.iter() { for bone in [&spec.bone0, &spec.bone1, &spec.bone2].iter() { // TODO: avoid the requirement for symlinks in "voxygen.voxel.object.", and load // the models from "server.voxel." instead let vox = - cache.load::(&["voxygen.voxel.", &bone.central.0].concat())?; + cache.load::(&["server.voxel.", &bone.central.0].concat())?; let dyna = Dyna::::from_vox(&vox.read().0, false); - voxes.insert(bone.central.0.clone(), dyna.map_into(|cell| { + let dyna = dyna.map_into(|cell| { if let Some(rgb) = cell.get_color() { Block::new(BlockKind::Misc, rgb) } else { Block::air(SpriteKind::Empty) } - })); + }); + let collider = VoxelCollider { + dyna, + translation: Vec3::from(bone.offset) + Vec3::from(bone.phys_offset), + }; + colliders.insert(bone.central.0.clone(), collider); } } Ok(ShipSpec { central: manifest, - voxes, + colliders, }) } } @@ -99,6 +112,6 @@ pub mod figuredata { // TODO: load this from the ECS as a resource, and maybe make it more general than ships // (although figuring out how to keep the figure bones in sync with the terrain offsets seems // like a hard problem if they're not the same manifest) - pub static ref VOXEL_COLLIDER_MANIFEST: ShipSpec = AssetExt::load_expect_cloned("server.manifests.ship_manifest"); + pub static ref VOXEL_COLLIDER_MANIFEST: AssetHandle = AssetExt::load_expect("server.manifests.ship_manifest"); } } diff --git a/common/src/comp/ori.rs b/common/src/comp/ori.rs index 351db07066..de9e969bcc 100644 --- a/common/src/comp/ori.rs +++ b/common/src/comp/ori.rs @@ -9,7 +9,7 @@ use vek::{Quaternion, Vec2, Vec3}; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(into = "SerdeOri")] #[serde(from = "SerdeOri")] -pub struct Ori(Quaternion); +pub struct Ori(pub Quaternion); impl Default for Ori { /// Returns the default orientation (no rotation; default Dir) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 16d6af3a39..f310b0ae7b 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -375,7 +375,7 @@ impl<'a> PhysicsSystemData<'a> { } = self; // Apply movement inputs span!(guard, "Apply movement and terrain collision"); - let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); + let (positions, previous_phys_cache, orientations) = (&psdw.positions, &psdw.previous_phys_cache, &psdw.orientations); let (pos_writes, land_on_grounds) = ( &psdr.entities, psdr.scales.maybe(), @@ -383,7 +383,7 @@ impl<'a> PhysicsSystemData<'a> { &psdr.colliders, positions, &mut psdw.velocities, - &psdw.orientations, + orientations, &mut psdw.physics_states, previous_phys_cache, !&psdr.mountings, @@ -559,6 +559,7 @@ impl<'a> PhysicsSystemData<'a> { previous_cache_other, mass_other, collider_other, + ori_other, _, _, _, @@ -571,6 +572,7 @@ impl<'a> PhysicsSystemData<'a> { previous_phys_cache, psdr.masses.maybe(), &psdr.colliders, + orientations, !&psdr.projectiles, !&psdr.mountings, !&psdr.beams, @@ -585,10 +587,12 @@ impl<'a> PhysicsSystemData<'a> { .center .distance_squared(previous_cache_other.center) > collision_boundary.powi(2) - || entity == entity_other { continue; }*/ + if entity == entity_other { + continue; + } if let Collider::Voxel { id } = collider_other { // use bounding cylinder regardless of our collider @@ -600,13 +604,31 @@ impl<'a> PhysicsSystemData<'a> { let z_min = z_min * scale; let z_max = z_max.clamped(1.2, 1.95) * scale; - let mut physics_state_delta = physics_state.clone(); - pos.0 -= pos_other.0; - let cylinder = (radius, z_min, z_max); - if let Some(dyna) = VOXEL_COLLIDER_MANIFEST.voxes.get(id) { + if let Some(voxel_collider) = VOXEL_COLLIDER_MANIFEST.read().colliders.get(id) { + let mut physics_state_delta = physics_state.clone(); + //let ori_2d = ori_other.look_dir().xy(); + //let ori_2d_quat = Quaternion::rotation_z(ori_2d.y.atan2(ori_2d.x)); + //let ori_2d_quat = Quaternion::from_xyzw(ori_2d.x, ori_2d.y, 0.0, 1.0).normalized(); + // deliberately don't use scale yet here, because the 11.0/0.8 + // thing is in the comp::Scale for visual reasons + let t1 = Mat4::from(Transform { + position: pos_other.0 + voxel_collider.translation, + orientation: Quaternion::identity(), + scale: Vec3::broadcast(1.0), + }); + let t2 = Mat4::from(Transform { + position: Vec3::zero(), + orientation: ori_other.0.normalized(), + scale: Vec3::broadcast(1.0), + }); + //let transform = t2 * t1; + let transform = t1; + pos.0 = transform.inverted().mul_point(pos.0); + //vel.0 = t2.inverted().mul_point(pos.0); + let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, - &*dyna, + &voxel_collider.dyna, entity, &mut pos, pos_delta, @@ -614,24 +636,27 @@ impl<'a> PhysicsSystemData<'a> { &mut physics_state_delta, &mut land_on_grounds, ); + + pos.0 = transform.mul_point(pos.0); + //vel.0 = t2.mul_point(vel.0); + + // union in the state updates, so that the state isn't just based on + // the most recent terrain that collision was attempted with + physics_state.on_ground |= physics_state_delta.on_ground; + physics_state.on_ceiling |= physics_state_delta.on_ceiling; + physics_state.on_wall = + physics_state.on_wall.or(physics_state_delta.on_wall); + physics_state + .touch_entities + .append(&mut physics_state_delta.touch_entities); + physics_state.in_liquid = + match (physics_state.in_liquid, physics_state_delta.in_liquid) { + // this match computes `x <|> y <|> liftA2 max x y` + (Some(x), Some(y)) => Some(x.max(y)), + (_, y @ Some(_)) => y, + _ => None, + }; } - pos.0 += pos_other.0; - // union in the state updates, so that the state isn't just based on - // the most recent terrain that collision was attempted with - physics_state.on_ground |= physics_state_delta.on_ground; - physics_state.on_ceiling |= physics_state_delta.on_ceiling; - physics_state.on_wall = - physics_state.on_wall.or(physics_state_delta.on_wall); - physics_state - .touch_entities - .append(&mut physics_state_delta.touch_entities); - physics_state.in_liquid = - match (physics_state.in_liquid, physics_state_delta.in_liquid) { - // this match computes `x <|> y <|> liftA2 max x y` - (Some(x), Some(y)) => Some(x.max(y)), - (_, y @ Some(_)) => y, - _ => None, - }; } } if pos != old_pos { diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index eec891e144..6856dbbb4b 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -1972,7 +1972,7 @@ fn create_pipelines( &shaders.figure_vert.read().0, &shaders.figure_frag.read().0, &include_ctx, - gfx::state::CullFace::Nothing, + gfx::state::CullFace::Back, )?; // Construct a pipeline for rendering terrain diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index a584812462..ff334213e2 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -35,6 +35,9 @@ fn load_segment(mesh_name: &str) -> Segment { } fn graceful_load_vox(mesh_name: &str) -> AssetHandle { let full_specifier: String = ["voxygen.voxel.", mesh_name].concat(); + graceful_load_vox_fullspec(&full_specifier) +} +fn graceful_load_vox_fullspec(full_specifier: &str) -> AssetHandle { match DotVoxAsset::load(&full_specifier) { Ok(dot_vox) => dot_vox, Err(_) => { @@ -46,6 +49,9 @@ fn graceful_load_vox(mesh_name: &str) -> AssetHandle { fn graceful_load_segment(mesh_name: &str) -> Segment { Segment::from(&graceful_load_vox(mesh_name).read().0) } +fn graceful_load_segment_fullspec(full_specifier: &str) -> Segment { + Segment::from(&graceful_load_vox_fullspec(full_specifier).read().0) +} fn graceful_load_segment_flipped(mesh_name: &str, flipped: bool) -> Segment { Segment::from_vox(&graceful_load_vox(mesh_name).read().0, flipped) } @@ -4241,7 +4247,7 @@ fn mesh_ship_bone &ShipCentralSubSpec>(ma }, }; let bone = f(spec); - let central = graceful_load_segment(&bone.central.0); + let central = graceful_load_segment_fullspec(&["server.voxel.", &bone.central.0].concat()); (central, Vec3::from(bone.offset)) } From e1e6403c63b0ebae3d8ef647fce5d64dd0b72445 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 12 Mar 2021 11:32:19 +0000 Subject: [PATCH 06/41] Fixed airship collisions under rotation, added position increment for velocity when on ground --- common/src/states/utils.rs | 2 +- common/sys/src/phys.rs | 89 ++++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index ea780a1a38..05928fc940 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -169,7 +169,7 @@ impl Body { quadruped_low::Species::Lavadrake => 4.0, _ => 6.0, }, - Body::Ship(_) => 10.0, + Body::Ship(_) => 1.0, } } diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index f310b0ae7b..8e743b3b70 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -375,14 +375,14 @@ impl<'a> PhysicsSystemData<'a> { } = self; // Apply movement inputs span!(guard, "Apply movement and terrain collision"); - let (positions, previous_phys_cache, orientations) = (&psdw.positions, &psdw.previous_phys_cache, &psdw.orientations); - let (pos_writes, land_on_grounds) = ( + let (positions, velocities, previous_phys_cache, orientations) = (&psdw.positions, &psdw.velocities, &psdw.previous_phys_cache, &psdw.orientations); + let (pos_writes, vel_writes, land_on_grounds) = ( &psdr.entities, psdr.scales.maybe(), psdr.stickies.maybe(), &psdr.colliders, positions, - &mut psdw.velocities, + velocities, orientations, &mut psdw.physics_states, previous_phys_cache, @@ -390,15 +390,15 @@ impl<'a> PhysicsSystemData<'a> { ) .par_join() .fold( - || (Vec::new(), Vec::new()), - |(mut pos_writes, mut land_on_grounds), + || (Vec::new(), Vec::new(), Vec::new()), + |(mut pos_writes, mut vel_writes, mut land_on_grounds), ( entity, scale, sticky, collider, pos, - mut vel, + vel, _ori, mut physics_state, previous_cache, @@ -408,9 +408,10 @@ impl<'a> PhysicsSystemData<'a> { // entities let old_pos = *pos; let mut pos = *pos; + let mut vel = *vel; if sticky.is_some() && physics_state.on_surface().is_some() { vel.0 = Vec3::zero(); - return (pos_writes, land_on_grounds); + return (pos_writes, vel_writes, land_on_grounds); } let scale = if let Collider::Voxel { .. } = collider { @@ -421,7 +422,7 @@ impl<'a> PhysicsSystemData<'a> { 1.0 }; - let old_vel = *vel; + let old_vel = vel; // Integrate forces // Friction is assumed to be a constant dependent on location let friction = FRIC_AIR @@ -477,9 +478,9 @@ impl<'a> PhysicsSystemData<'a> { entity, &mut pos, pos_delta, - vel, + &mut vel, &mut physics_state, - &mut land_on_grounds, + |entity, vel| land_on_grounds.push((entity, vel)), ); }, Collider::Box { @@ -499,9 +500,9 @@ impl<'a> PhysicsSystemData<'a> { entity, &mut pos, pos_delta, - vel, + &mut vel, &mut physics_state, - &mut land_on_grounds, + |entity, vel| land_on_grounds.push((entity, vel)), ); }, Collider::Point => { @@ -556,6 +557,7 @@ impl<'a> PhysicsSystemData<'a> { entity_other, other, pos_other, + vel_other, previous_cache_other, mass_other, collider_other, @@ -569,6 +571,7 @@ impl<'a> PhysicsSystemData<'a> { &psdr.entities, &psdr.uids, positions, + velocities, previous_phys_cache, psdr.masses.maybe(), &psdr.colliders, @@ -606,39 +609,32 @@ impl<'a> PhysicsSystemData<'a> { if let Some(voxel_collider) = VOXEL_COLLIDER_MANIFEST.read().colliders.get(id) { let mut physics_state_delta = physics_state.clone(); - //let ori_2d = ori_other.look_dir().xy(); - //let ori_2d_quat = Quaternion::rotation_z(ori_2d.y.atan2(ori_2d.x)); - //let ori_2d_quat = Quaternion::from_xyzw(ori_2d.x, ori_2d.y, 0.0, 1.0).normalized(); // deliberately don't use scale yet here, because the 11.0/0.8 // thing is in the comp::Scale for visual reasons - let t1 = Mat4::from(Transform { - position: pos_other.0 + voxel_collider.translation, - orientation: Quaternion::identity(), - scale: Vec3::broadcast(1.0), - }); - let t2 = Mat4::from(Transform { - position: Vec3::zero(), - orientation: ori_other.0.normalized(), - scale: Vec3::broadcast(1.0), - }); - //let transform = t2 * t1; - let transform = t1; - pos.0 = transform.inverted().mul_point(pos.0); - //vel.0 = t2.inverted().mul_point(pos.0); + let transform_from = Mat4::::translation_3d(pos_other.0) + * Mat4::from(ori_other.0) + * Mat4::::translation_3d(voxel_collider.translation); + let transform_to = transform_from.inverted(); + pos.0 = transform_to.mul_point(pos.0); + vel.0 = transform_to.mul_direction(vel.0); let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, &voxel_collider.dyna, entity, &mut pos, - pos_delta, - vel, + transform_to.mul_direction(pos_delta), + &mut vel, &mut physics_state_delta, - &mut land_on_grounds, + |entity, vel| land_on_grounds.push((entity, Vel(transform_from.mul_direction(vel.0)))), ); - pos.0 = transform.mul_point(pos.0); - //vel.0 = t2.mul_point(vel.0); + pos.0 = transform_from.mul_point(pos.0); + vel.0 = transform_from.mul_direction(vel.0); + + if physics_state_delta.on_ground { + pos.0 += vel_other.0 * psdr.dt.0; + } // union in the state updates, so that the state isn't just based on // the most recent terrain that collision was attempted with @@ -662,27 +658,36 @@ impl<'a> PhysicsSystemData<'a> { if pos != old_pos { pos_writes.push((entity, pos)); } + if vel != old_vel { + vel_writes.push((entity, vel)); + } - (pos_writes, land_on_grounds) + (pos_writes, vel_writes, land_on_grounds) }, ) .reduce( - || (Vec::new(), Vec::new()), - |(mut pos_writes_a, mut land_on_grounds_a), - (mut pos_writes_b, mut land_on_grounds_b)| { + || (Vec::new(), Vec::new(), Vec::new()), + |(mut pos_writes_a, mut vel_writes_a, mut land_on_grounds_a), + (mut pos_writes_b, mut vel_writes_b, mut land_on_grounds_b)| { pos_writes_a.append(&mut pos_writes_b); + vel_writes_a.append(&mut vel_writes_b); land_on_grounds_a.append(&mut land_on_grounds_b); - (pos_writes_a, land_on_grounds_a) + (pos_writes_a, vel_writes_a, land_on_grounds_a) }, ); drop(guard); job.cpu_stats.measure(ParMode::Single); let pos_writes: HashMap = pos_writes.into_iter().collect(); - for (entity, pos) in (&psdr.entities, &mut psdw.positions).join() { + let vel_writes: HashMap = vel_writes.into_iter().collect(); + for (entity, pos, vel) in (&psdr.entities, &mut psdw.positions, &mut psdw.velocities).join() { if let Some(new_pos) = pos_writes.get(&entity) { *pos = *new_pos; } + + if let Some(new_vel) = vel_writes.get(&entity) { + *vel = *new_vel; + } } let mut event_emitter = psdr.event_bus.emitter(); @@ -732,7 +737,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( mut pos_delta: Vec3, vel: &mut Vel, physics_state: &mut PhysicsState, - land_on_grounds: &mut Vec<(Entity, Vel)>, + mut land_on_ground: impl FnMut(Entity, Vel), ) { let (radius, z_min, z_max) = cylinder; @@ -898,7 +903,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( on_ground = true; if !was_on_ground { - land_on_grounds.push((entity, *vel)); + land_on_ground(entity, *vel); } } else if resolve_dir.z < 0.0 && vel.0.z >= 0.0 { on_ceiling = true; From afd99788b21b304e2a70c23bc1bd70be964205de Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 12 Mar 2021 13:26:02 +0000 Subject: [PATCH 07/41] Made animations and friction ground-relative --- common/src/comp/phys.rs | 2 + common/sys/src/phys.rs | 34 +++- voxygen/src/scene/figure/mod.rs | 347 +++++++++++++++++++------------- voxygen/src/scene/simple.rs | 2 + 4 files changed, 234 insertions(+), 151 deletions(-) diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index b10978cc58..149f8ebfd4 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -109,6 +109,7 @@ pub struct PhysicsState { pub on_wall: Option>, pub touch_entities: Vec, pub in_liquid: Option, // Depth + pub ground_vel: Vec3, } impl PhysicsState { @@ -118,6 +119,7 @@ impl PhysicsState { touch_entities.clear(); *self = Self { touch_entities, + ground_vel: self.ground_vel, // Preserved, since it's the velocity of the last contact point ..Self::default() } } diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 8e743b3b70..f4c1215c6e 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -426,11 +426,11 @@ impl<'a> PhysicsSystemData<'a> { // Integrate forces // Friction is assumed to be a constant dependent on location let friction = FRIC_AIR - .max(if physics_state.on_ground { - FRIC_GROUND - } else { - 0.0 - }) + // .max(if physics_state.on_ground { + // FRIC_GROUND + // } else { + // 0.0 + // }) .max(if physics_state.in_liquid.is_some() { FRIC_FLUID } else { @@ -480,6 +480,9 @@ impl<'a> PhysicsSystemData<'a> { pos_delta, &mut vel, &mut physics_state, + Vec3::zero(), + &psdr.dt, + true, |entity, vel| land_on_grounds.push((entity, vel)), ); }, @@ -502,6 +505,9 @@ impl<'a> PhysicsSystemData<'a> { pos_delta, &mut vel, &mut physics_state, + Vec3::zero(), + &psdr.dt, + true, |entity, vel| land_on_grounds.push((entity, vel)), ); }, @@ -626,18 +632,20 @@ impl<'a> PhysicsSystemData<'a> { transform_to.mul_direction(pos_delta), &mut vel, &mut physics_state_delta, + transform_to.mul_direction(vel_other.0), + &psdr.dt, + false, |entity, vel| land_on_grounds.push((entity, Vel(transform_from.mul_direction(vel.0)))), ); pos.0 = transform_from.mul_point(pos.0); vel.0 = transform_from.mul_direction(vel.0); - if physics_state_delta.on_ground { - pos.0 += vel_other.0 * psdr.dt.0; - } - // union in the state updates, so that the state isn't just based on // the most recent terrain that collision was attempted with + if physics_state_delta.on_ground { + physics_state.ground_vel = vel_other.0; + } physics_state.on_ground |= physics_state_delta.on_ground; physics_state.on_ceiling |= physics_state_delta.on_ceiling; physics_state.on_wall = @@ -737,6 +745,9 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( mut pos_delta: Vec3, vel: &mut Vel, physics_state: &mut PhysicsState, + ground_vel: Vec3, + dt: &DeltaTime, + apply_velocity_step: bool, // Stupid hack mut land_on_ground: impl FnMut(Entity, Vel), ) { let (radius, z_min, z_max) = cylinder; @@ -826,7 +837,9 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( let old_pos = pos.0; fn block_true(_: &Block) -> bool { true } for _ in 0..increments as usize { - pos.0 += pos_delta / increments; + if apply_velocity_step { + pos.0 += pos_delta / increments; + } const MAX_ATTEMPTS: usize = 16; @@ -966,6 +979,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( if on_ground { physics_state.on_ground = true; + vel.0 = ground_vel + (vel.0 - ground_vel) * (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); // If the space below us is free, then "snap" to the ground } else if collision_with( pos.0 - Vec3::unit_z() * 1.05, diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index f6ae55df31..926680fdfa 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -748,6 +748,9 @@ impl FigureMgr { let hands = (active_tool_hand, second_tool_hand); + // Velocity relative to the current ground + let rel_vel = vel.0 - physics.ground_vel; + match body { Body::Humanoid(body) => { let (model, skeleton_attr) = self.model_cache.get_or_create_model( @@ -768,6 +771,10 @@ impl FigureMgr { .or_insert_with(|| { FigureState::new(renderer, CharacterSkeleton::default()) }); + + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -779,7 +786,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -790,7 +797,7 @@ impl FigureMgr { second_tool_kind, hands, time, - state.avg_vel, + rel_avg_vel, ), state.state_time, &mut state_animation_rate, @@ -803,12 +810,12 @@ impl FigureMgr { active_tool_kind, second_tool_kind, hands, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -822,7 +829,7 @@ impl FigureMgr { active_tool_kind, second_tool_kind, hands, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), @@ -839,12 +846,12 @@ impl FigureMgr { active_tool_kind, second_tool_kind, hands, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, ), state.state_time, &mut state_animation_rate, @@ -889,7 +896,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), + rel_vel.magnitude(), time, None, ), @@ -917,7 +924,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), @@ -949,7 +956,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), @@ -983,7 +990,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), ), @@ -1016,7 +1023,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), ), @@ -1030,7 +1037,7 @@ impl FigureMgr { &target_base, ( active_tool_kind, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), @@ -1047,7 +1054,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), + rel_vel.magnitude(), time, None, ), @@ -1106,7 +1113,7 @@ impl FigureMgr { active_tool_kind, second_tool_kind, time, - vel.0.magnitude(), + rel_vel.magnitude(), Some(s.stage_section), ), stage_progress, @@ -1173,7 +1180,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), ), @@ -1209,7 +1216,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), ), @@ -1239,7 +1246,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1256,7 +1263,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1287,7 +1294,7 @@ impl FigureMgr { active_tool_kind, second_tool_kind, time, - vel.0.magnitude(), + rel_vel.magnitude(), Some(s.stage_section), ), stage_progress, @@ -1351,7 +1358,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), ), @@ -1364,7 +1371,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), ), @@ -1377,7 +1384,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), ), @@ -1399,7 +1406,7 @@ impl FigureMgr { CharacterState::Equipping { .. } => { anim::character::EquipAnimation::update_skeleton( &target_base, - (active_tool_kind, second_tool_kind, vel.0.magnitude(), time), + (active_tool_kind, second_tool_kind, rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1410,7 +1417,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0.magnitude(), + rel_vel.magnitude(), time, look_dir, ), @@ -1422,7 +1429,7 @@ impl FigureMgr { if physics.in_liquid.is_some() { anim::character::SwimWieldAnimation::update_skeleton( &target_base, - (active_tool_kind, second_tool_kind, vel.0.magnitude(), time), + (active_tool_kind, second_tool_kind, rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1437,7 +1444,7 @@ impl FigureMgr { ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), look_dir, - vel.0, + rel_vel, time, ), state.state_time, @@ -1452,7 +1459,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), @@ -1469,7 +1476,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), time, @@ -1494,7 +1501,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), @@ -1533,6 +1540,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::QuadrupedSmall(body) => { @@ -1556,6 +1564,9 @@ impl FigureMgr { FigureState::new(renderer, QuadrupedSmallSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -1567,7 +1578,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -1585,12 +1596,12 @@ impl FigureMgr { anim::quadruped_small::RunAnimation::update_skeleton( &QuadrupedSmallSkeleton::default(), ( - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -1602,12 +1613,12 @@ impl FigureMgr { (false, _, true) => anim::quadruped_small::RunAnimation::update_skeleton( &QuadrupedSmallSkeleton::default(), ( - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -1617,7 +1628,7 @@ impl FigureMgr { // In air (false, _, false) => anim::quadruped_small::JumpAnimation::update_skeleton( &QuadrupedSmallSkeleton::default(), - (vel.0.magnitude(), time), + (rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1659,7 +1670,7 @@ impl FigureMgr { anim::quadruped_small::AlphaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1690,7 +1701,7 @@ impl FigureMgr { anim::quadruped_small::StunnedAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1731,6 +1742,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::QuadrupedMedium(body) => { @@ -1754,6 +1766,9 @@ impl FigureMgr { FigureState::new(renderer, QuadrupedMediumSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -1765,7 +1780,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > 0.25, // Moving + rel_vel.magnitude_squared() > 0.25, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -1783,12 +1798,12 @@ impl FigureMgr { anim::quadruped_medium::RunAnimation::update_skeleton( &QuadrupedMediumSkeleton::default(), ( - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -1800,12 +1815,12 @@ impl FigureMgr { (false, _, true) => anim::quadruped_medium::RunAnimation::update_skeleton( &QuadrupedMediumSkeleton::default(), ( - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -1850,7 +1865,7 @@ impl FigureMgr { anim::quadruped_medium::HoofAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1878,7 +1893,7 @@ impl FigureMgr { anim::quadruped_medium::DashAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1908,7 +1923,7 @@ impl FigureMgr { anim::quadruped_medium::LeapMeleeAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1946,7 +1961,7 @@ impl FigureMgr { 1 => anim::quadruped_medium::AlphaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1958,7 +1973,7 @@ impl FigureMgr { 2 => anim::quadruped_medium::BetaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1970,7 +1985,7 @@ impl FigureMgr { _ => anim::quadruped_medium::AlphaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -1999,7 +2014,7 @@ impl FigureMgr { anim::quadruped_medium::StunnedAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2013,7 +2028,7 @@ impl FigureMgr { anim::quadruped_medium::StunnedAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2054,6 +2069,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::QuadrupedLow(body) => { @@ -2077,6 +2093,9 @@ impl FigureMgr { FigureState::new(renderer, QuadrupedLowSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -2088,7 +2107,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -2105,12 +2124,12 @@ impl FigureMgr { (true, true, false) => anim::quadruped_low::RunAnimation::update_skeleton( &QuadrupedLowSkeleton::default(), ( - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -2121,12 +2140,12 @@ impl FigureMgr { (false, _, true) => anim::quadruped_low::RunAnimation::update_skeleton( &QuadrupedLowSkeleton::default(), ( - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -2136,7 +2155,7 @@ impl FigureMgr { // In air (false, _, false) => anim::quadruped_low::JumpAnimation::update_skeleton( &QuadrupedLowSkeleton::default(), - (vel.0.magnitude(), time), + (rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2165,7 +2184,7 @@ impl FigureMgr { }; anim::quadruped_low::ShootAnimation::update_skeleton( &target_base, - (vel.0.magnitude(), time, Some(s.stage_section)), + (rel_vel.magnitude(), time, Some(s.stage_section)), stage_progress, &mut state_animation_rate, skeleton_attr, @@ -2190,7 +2209,7 @@ impl FigureMgr { anim::quadruped_low::BetaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2220,7 +2239,7 @@ impl FigureMgr { anim::quadruped_low::TailwhipAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2248,7 +2267,7 @@ impl FigureMgr { anim::quadruped_low::StunnedAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2262,7 +2281,7 @@ impl FigureMgr { anim::quadruped_low::StunnedAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2302,7 +2321,7 @@ impl FigureMgr { 1 => anim::quadruped_low::AlphaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2314,7 +2333,7 @@ impl FigureMgr { 2 => anim::quadruped_low::BetaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2326,7 +2345,7 @@ impl FigureMgr { _ => anim::quadruped_low::AlphaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2352,7 +2371,7 @@ impl FigureMgr { anim::quadruped_low::BreatheAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2380,7 +2399,7 @@ impl FigureMgr { anim::quadruped_low::DashAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -2410,6 +2429,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::BirdMedium(body) => { @@ -2432,6 +2452,9 @@ impl FigureMgr { FigureState::new(renderer, BirdMediumSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -2443,7 +2466,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -2457,7 +2480,7 @@ impl FigureMgr { // Running (true, true, false) => anim::bird_medium::RunAnimation::update_skeleton( &BirdMediumSkeleton::default(), - (vel.0.magnitude(), time), + (rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2465,7 +2488,7 @@ impl FigureMgr { // Running (false, _, true) => anim::bird_medium::RunAnimation::update_skeleton( &BirdMediumSkeleton::default(), - (vel.0.magnitude(), time), + (rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2473,7 +2496,7 @@ impl FigureMgr { // In air (false, _, false) => anim::bird_medium::FlyAnimation::update_skeleton( &BirdMediumSkeleton::default(), - (vel.0.magnitude(), time), + (rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -2516,6 +2539,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::FishMedium(body) => { @@ -2538,6 +2562,9 @@ impl FigureMgr { FigureState::new(renderer, FishMediumSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -2549,19 +2576,19 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Idle (_, false, _) => anim::fish_medium::IdleAnimation::update_skeleton( &FishMediumSkeleton::default(), ( - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, ), state.state_time, &mut state_animation_rate, @@ -2571,12 +2598,12 @@ impl FigureMgr { (_, true, _) => anim::fish_medium::SwimAnimation::update_skeleton( &FishMediumSkeleton::default(), ( - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -2601,6 +2628,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::BipedSmall(body) => { @@ -2623,6 +2651,9 @@ impl FigureMgr { FigureState::new(renderer, BipedSmallSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -2634,18 +2665,18 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Idle (true, false, false) => anim::biped_small::IdleAnimation::update_skeleton( &BipedSmallSkeleton::default(), ( - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, ), state.state_time, &mut state_animation_rate, @@ -2655,11 +2686,11 @@ impl FigureMgr { (true, true, _) => anim::biped_small::RunAnimation::update_skeleton( &BipedSmallSkeleton::default(), ( - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -2670,11 +2701,11 @@ impl FigureMgr { (false, _, false) => anim::biped_small::RunAnimation::update_skeleton( &BipedSmallSkeleton::default(), ( - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -2685,11 +2716,11 @@ impl FigureMgr { (false, _, true) => anim::biped_small::RunAnimation::update_skeleton( &BipedSmallSkeleton::default(), ( - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -2699,11 +2730,11 @@ impl FigureMgr { _ => anim::biped_small::IdleAnimation::update_skeleton( &BipedSmallSkeleton::default(), ( - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, ), state.state_time, &mut state_animation_rate, @@ -2717,11 +2748,11 @@ impl FigureMgr { &target_base, ( active_tool_kind, - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -2749,11 +2780,11 @@ impl FigureMgr { anim::biped_small::DashAnimation::update_skeleton( &target_base, ( - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, Some(s.stage_section), state.state_time, @@ -2819,11 +2850,11 @@ impl FigureMgr { &target_base, ( active_tool_kind, - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, Some(s.stage_section), state.state_time, @@ -2850,11 +2881,11 @@ impl FigureMgr { &target_base, ( active_tool_kind, - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, Some(s.stage_section), state.state_time, @@ -2892,11 +2923,11 @@ impl FigureMgr { 1 => anim::biped_small::AlphaAnimation::update_skeleton( &target_base, ( - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, Some(s.stage_section), state.state_time, @@ -2908,11 +2939,11 @@ impl FigureMgr { _ => anim::biped_small::AlphaAnimation::update_skeleton( &target_base, ( - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, Some(s.stage_section), state.state_time, @@ -2943,6 +2974,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::Dragon(body) => { @@ -2962,6 +2994,9 @@ impl FigureMgr { FigureState::new(renderer, DragonSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -2973,7 +3008,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -2988,12 +3023,12 @@ impl FigureMgr { (true, true, false) => anim::dragon::RunAnimation::update_skeleton( &DragonSkeleton::default(), ( - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, ), state.state_time, &mut state_animation_rate, @@ -3002,7 +3037,7 @@ impl FigureMgr { // In air (false, _, false) => anim::dragon::FlyAnimation::update_skeleton( &DragonSkeleton::default(), - (vel.0.magnitude(), time), + (rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -3033,6 +3068,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::Theropod(body) => { @@ -3053,6 +3089,9 @@ impl FigureMgr { .entry(entity) .or_insert_with(|| FigureState::new(renderer, TheropodSkeleton::default())); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -3064,7 +3103,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -3079,12 +3118,12 @@ impl FigureMgr { (true, true, false) => anim::theropod::RunAnimation::update_skeleton( &TheropodSkeleton::default(), ( - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -3095,12 +3134,12 @@ impl FigureMgr { (false, _, false) => anim::theropod::JumpAnimation::update_skeleton( &TheropodSkeleton::default(), ( - vel.0.magnitude(), + rel_vel.magnitude(), // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, ), state.state_time, &mut state_animation_rate, @@ -3143,7 +3182,7 @@ impl FigureMgr { 1 => anim::theropod::AlphaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -3155,7 +3194,7 @@ impl FigureMgr { _ => anim::theropod::BetaAnimation::update_skeleton( &target_base, ( - vel.0.magnitude(), + rel_vel.magnitude(), time, Some(s.stage_section), state.state_time, @@ -3216,6 +3255,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::BirdSmall(body) => { @@ -3238,6 +3278,9 @@ impl FigureMgr { FigureState::new(renderer, BirdSmallSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -3249,7 +3292,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -3263,7 +3306,7 @@ impl FigureMgr { // Running (true, true, false) => anim::bird_small::RunAnimation::update_skeleton( &BirdSmallSkeleton::default(), - (vel.0.magnitude(), time), + (rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -3271,7 +3314,7 @@ impl FigureMgr { // In air (false, _, false) => anim::bird_small::JumpAnimation::update_skeleton( &BirdSmallSkeleton::default(), - (vel.0.magnitude(), time), + (rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -3303,6 +3346,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::FishSmall(body) => { @@ -3325,6 +3369,9 @@ impl FigureMgr { FigureState::new(renderer, FishSmallSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -3336,19 +3383,19 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Idle (_, false, _) => anim::fish_small::IdleAnimation::update_skeleton( &FishSmallSkeleton::default(), ( - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, ), state.state_time, &mut state_animation_rate, @@ -3358,12 +3405,12 @@ impl FigureMgr { (_, true, _) => anim::fish_small::SwimAnimation::update_skeleton( &FishSmallSkeleton::default(), ( - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -3388,6 +3435,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::BipedLarge(body) => { @@ -3410,6 +3458,9 @@ impl FigureMgr { FigureState::new(renderer, BipedLargeSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -3421,7 +3472,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Running @@ -3430,12 +3481,12 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, - state.avg_vel, + rel_avg_vel, state.acc_vel, ), state.state_time, @@ -3462,7 +3513,7 @@ impl FigureMgr { CharacterState::Equipping { .. } => { anim::biped_large::EquipAnimation::update_skeleton( &target_base, - (active_tool_kind, second_tool_kind, vel.0.magnitude(), time), + (active_tool_kind, second_tool_kind, rel_vel.magnitude(), time), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -3474,7 +3525,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, state.acc_vel, ), @@ -3489,7 +3540,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, None, state.acc_vel, @@ -3518,7 +3569,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), @@ -3550,7 +3601,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), @@ -3585,7 +3636,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), state.acc_vel, @@ -3625,7 +3676,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), state.acc_vel, @@ -3639,7 +3690,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), state.acc_vel, @@ -3653,7 +3704,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), state.acc_vel, @@ -3691,7 +3742,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), ), @@ -3731,7 +3782,7 @@ impl FigureMgr { ( active_tool_kind, second_tool_kind, - vel.0, + rel_vel, time, Some(s.stage_section), ), @@ -3760,7 +3811,7 @@ impl FigureMgr { active_tool_kind, second_tool_kind, time, - vel.0.magnitude(), + rel_vel.magnitude(), Some(s.stage_section), ), stage_progress, @@ -3786,7 +3837,7 @@ impl FigureMgr { active_tool_kind, second_tool_kind, time, - vel.0, + rel_vel, Some(s.stage_section), state.acc_vel, ), @@ -3815,6 +3866,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::Golem(body) => { @@ -3834,6 +3886,9 @@ impl FigureMgr { FigureState::new(renderer, GolemSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => continue, @@ -3845,7 +3900,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -3860,7 +3915,7 @@ impl FigureMgr { (true, true, false) => anim::golem::RunAnimation::update_skeleton( &GolemSkeleton::default(), ( - vel.0, + rel_vel, // TODO: Update to use the quaternion. ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), @@ -3875,7 +3930,7 @@ impl FigureMgr { (false, _, false) => anim::golem::RunAnimation::update_skeleton( &GolemSkeleton::default(), ( - vel.0, + rel_vel, ori * anim::vek::Vec3::::unit_y(), state.last_ori * anim::vek::Vec3::::unit_y(), time, @@ -3944,7 +3999,7 @@ impl FigureMgr { }; anim::golem::ShockwaveAnimation::update_skeleton( &target_base, - (Some(s.stage_section), vel.0.magnitude(), time), + (Some(s.stage_section), rel_vel.magnitude(), time), stage_progress, &mut state_animation_rate, skeleton_attr, @@ -3995,6 +4050,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::Object(body) => { @@ -4014,6 +4070,9 @@ impl FigureMgr { FigureState::new(renderer, ObjectSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => (&CharacterState::Idle, &Last { @@ -4027,7 +4086,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -4119,6 +4178,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, Body::Ship(body) => { @@ -4138,6 +4198,9 @@ impl FigureMgr { FigureState::new(renderer, ShipSkeleton::default()) }); + // Average velocity relative to the current ground + let rel_avg_vel = state.avg_vel - physics.ground_vel; + let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), _ => (&CharacterState::Idle, &Last { @@ -4151,7 +4214,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, - vel.0.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving + rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving physics.in_liquid.is_some(), // In water ) { // Standing @@ -4192,6 +4255,7 @@ impl FigureMgr { camera, &mut update_buf, terrain, + physics.ground_vel, ); }, } @@ -4891,6 +4955,7 @@ impl FigureState { _camera: &Camera, buf: &mut [anim::FigureBoneData; anim::MAX_BONE_COUNT], terrain: Option<&Terrain>, + ground_vel: Vec3, ) { // NOTE: As long as update() always gets called after get_or_create_model(), and // visibility is not set again until after the model is rendered, we @@ -5002,7 +5067,7 @@ impl FigureState { // Can potentially overflow if self.avg_vel.magnitude_squared() != 0.0 { - self.acc_vel += self.avg_vel.magnitude() * dt; + self.acc_vel += (self.avg_vel - ground_vel).magnitude() * dt; } else { self.acc_vel = 0.0; } diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index 2502c6c176..bc735af9e1 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -199,6 +199,7 @@ impl Scene { &camera, &mut buf, None, + Vec3::zero(), ); (model, state) }), @@ -378,6 +379,7 @@ impl Scene { &self.camera, &mut buf, None, + Vec3::zero(), ); } } From f4b3d8145b5ac8c15deb22516db2ed7986288408 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 12 Mar 2021 14:56:47 +0000 Subject: [PATCH 08/41] Smooth airship movement --- common/src/states/utils.rs | 18 ++++++++++-------- common/sys/src/phys.rs | 4 +++- server/src/sys/agent.rs | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 05928fc940..688c9c0d7f 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -169,15 +169,16 @@ impl Body { quadruped_low::Species::Lavadrake => 4.0, _ => 6.0, }, - Body::Ship(_) => 1.0, + Body::Ship(_) => 0.1, } } - pub fn can_fly(&self) -> bool { - matches!( - self, - Body::BirdMedium(_) | Body::Dragon(_) | Body::BirdSmall(_) | Body::Ship(ship::Body::DefaultAirship) - ) + pub fn can_fly(&self) -> Option { + match self { + Body::BirdMedium(_) | Body::Dragon(_) | Body::BirdSmall(_) => Some(1.0), + Body::Ship(ship::Body::DefaultAirship) => Some(1.5), + _ => None, + } } pub fn can_climb(&self) -> bool { matches!(self, Body::Humanoid(_)) } @@ -189,9 +190,10 @@ pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { swim_move(data, update, efficiency, depth); } else if input_is_pressed(data, InputKind::Fly) && !data.physics.on_ground - && data.body.can_fly() + && data.body.can_fly().is_some() { - fly_move(data, update, efficiency); + fly_move(data, update, efficiency * data.body.can_fly().expect("can_fly is_some right above this")); + } else if data.inputs.fly.is_pressed() && !data.physics.on_ground && data.body.can_fly().is_some() { } else { basic_move(data, update, efficiency); } diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index f4c1215c6e..23d93333ab 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -425,7 +425,7 @@ impl<'a> PhysicsSystemData<'a> { let old_vel = vel; // Integrate forces // Friction is assumed to be a constant dependent on location - let friction = FRIC_AIR + let friction = if physics_state.on_ground { 0.0 } else { FRIC_AIR } // .max(if physics_state.on_ground { // FRIC_GROUND // } else { @@ -979,7 +979,9 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( if on_ground { physics_state.on_ground = true; + vel.0 = ground_vel + (vel.0 - ground_vel) * (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); + physics_state.ground_vel = ground_vel; // If the space below us is free, then "snap" to the ground } else if collision_with( pos.0 - Vec3::unit_z() * 1.05, diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index fda3106b13..73046aeabd 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -213,7 +213,7 @@ impl<'a> System<'a> for Sys { in_liquid: physics_state.in_liquid.is_some(), min_tgt_dist: 1.0, can_climb: body.map(|b| b.can_climb()).unwrap_or(false), - can_fly: body.map(|b| b.can_fly()).unwrap_or(false), + can_fly: body.map(|b| b.can_fly().is_some()).unwrap_or(false), }; let flees = alignment From f44ca1898c51a0602e5394d3d3d57d5e43b9d409 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 12 Mar 2021 15:26:12 +0000 Subject: [PATCH 09/41] Made airship terrain hitboxes smaller --- common/sys/src/phys.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 23d93333ab..501714daf6 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -468,7 +468,8 @@ impl<'a> PhysicsSystemData<'a> { Collider::Voxel { .. } => { // for now, treat entities with voxel colliders as their bounding // cylinders for the purposes of colliding them with terrain - let radius = collider.get_radius() * scale; + // Actually no, make them smaller to avoid lag + let radius = collider.get_radius() * scale * 0.1; let (z_min, z_max) = collider.get_z_limits(scale); let cylinder = (radius, z_min, z_max); From 09142d99b83d52495ae551f8863e9954b1343152 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 12 Mar 2021 15:38:48 +0000 Subject: [PATCH 10/41] Fixed water collision bug --- common/sys/src/phys.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 501714daf6..99ed590750 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -658,6 +658,7 @@ impl<'a> PhysicsSystemData<'a> { match (physics_state.in_liquid, physics_state_delta.in_liquid) { // this match computes `x <|> y <|> liftA2 max x y` (Some(x), Some(y)) => Some(x.max(y)), + (x @ Some(_), _) => x, (_, y @ Some(_)) => y, _ => None, }; From e59b68c26295dd8e611b0a4021c3821dc4dc50a9 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 12 Mar 2021 17:08:40 +0000 Subject: [PATCH 11/41] Mountable airships --- common/src/states/utils.rs | 2 +- server/src/state_ext.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 688c9c0d7f..794e1c1c4b 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -176,7 +176,7 @@ impl Body { pub fn can_fly(&self) -> Option { match self { Body::BirdMedium(_) | Body::Dragon(_) | Body::BirdSmall(_) => Some(1.0), - Body::Ship(ship::Body::DefaultAirship) => Some(1.5), + Body::Ship(ship::Body::DefaultAirship) => Some(1.0), _ => None, } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 787483df06..f19b105b5d 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -233,6 +233,7 @@ impl StateExt for State { .with(comp::Energy::new(ship.into(), level)) .with(comp::Health::new(ship.into(), level)) .with(comp::Stats::new("Airship".to_string())) + .with(comp::MountState::Unmounted) .with(comp::Buffs::default()) .with(comp::Combo::default()) .with(comp::Agent::with_destination()) From 5ff72a4a2e57feb7d1f237f42445f7bb762012a2 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Fri, 12 Mar 2021 13:53:06 -0500 Subject: [PATCH 12/41] `/airship angle` command and RtSim airships. --- common/src/cmd.rs | 2 +- common/src/comp/agent.rs | 6 +-- common/src/comp/body.rs | 10 +++++ common/src/rtsim.rs | 6 +-- server/src/cmd.rs | 7 ++- server/src/rtsim/entity.rs | 4 +- server/src/rtsim/tick.rs | 5 ++- server/src/state_ext.rs | 91 ++++++++++++++++++++------------------ server/src/sys/agent.rs | 26 ++++++++++- 9 files changed, 100 insertions(+), 57 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 9aee158547..3da0e9b52d 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -224,7 +224,7 @@ impl ChatCommand { "Temporarily gives a player admin permissions or removes them", Admin, ), - ChatCommand::Airship => cmd(vec![Boolean("moving", "true".to_string(), Optional)], "Spawns an airship", Admin), + ChatCommand::Airship => cmd(vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)], "Spawns an airship", Admin), ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin), ChatCommand::Ban => cmd( vec![Any("username", Required), Message(Optional)], diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index eccf2ce9fa..2df698c1c8 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -222,11 +222,11 @@ impl Agent { self } - pub fn with_destination() -> Self { + pub fn with_destination(pos: Vec3) -> Self { Self { - can_speak: false, + can_speak: true, psyche: Psyche { aggro: 1.0 }, - rtsim_controller: RtSimController::zero(), + rtsim_controller: RtSimController::with_destination(pos), ..Default::default() } } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 1733eec1e1..2abbe775c6 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -519,6 +519,16 @@ impl Body { } } + pub fn flying_height(&self) -> f32 { + match self { + Body::BirdSmall(_) => 30.0, + Body::BirdMedium(_) => 40.0, + Body::Dragon(_) => 60.0, + Body::Ship(ship::Body::DefaultAirship) => 60.0, + _ => 0.0, + } + } + pub fn immune_to(&self, buff: BuffKind) -> bool { match buff { BuffKind::Bleeding => matches!(self, Body::Object(_) | Body::Golem(_) | Body::Ship(_)), diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index 809a505b1f..716bacf382 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -46,10 +46,10 @@ impl Default for RtSimController { impl RtSimController { pub fn reset(&mut self) { *self = Self::default(); } - pub fn zero() -> Self { + pub fn with_destination(pos: Vec3) -> Self { Self { - travel_to: Some((Vec3::new(0.0, 0.0, 500.0), "".to_string())), - speed_factor: 0.05, + travel_to: Some((pos, format!("{:0.1?}", pos))), + speed_factor: 0.25, } } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 0fbc8bcd0f..7aae9f5339 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -992,13 +992,16 @@ fn handle_spawn_airship( args: String, action: &ChatCommand, ) { - let moving = scan_fmt_some!(&args, &action.arg_fmt(), String).unwrap_or_else(|| "false".to_string()) == "true"; + let angle = scan_fmt!(&args, &action.arg_fmt(), f32).ok(); match server.state.read_component_copied::(target) { Some(mut pos) => { pos.0.z += 50.0; + const DESTINATION_RADIUS: f32 = 2000.0; + let angle = angle.map(|a| a * std::f32::consts::PI / 180.0); + let destination = angle.map(|a| pos.0 + Vec3::new(DESTINATION_RADIUS*a.cos(), DESTINATION_RADIUS*a.sin(), 200.0)); server .state - .create_ship(pos, comp::ship::Body::DefaultAirship, 1, moving) + .create_ship(pos, comp::ship::Body::DefaultAirship, 1, destination) .with(comp::Scale(11.0 / 0.8)) .with(LightEmitter { col: Rgb::new(1.0, 0.65, 0.2), diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index 2a8ef629d7..18f500f695 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -27,7 +27,7 @@ impl Entity { pub fn get_body(&self) -> comp::Body { match self.rng(PERM_GENUS).gen::() { - //we want 50% birds, 50% humans for now + //we want 5% airships, 45% birds, 50% humans x if x < 0.05 => { comp::Body::Ship(comp::ship::Body::DefaultAirship) }, @@ -135,7 +135,7 @@ impl Entity { .iter() .filter(|s| match self.get_body() { comp::Body::Humanoid(_) => s.1.is_settlement() | s.1.is_castle(), - comp::Body::Ship(_) => s.1.is_castle(), + comp::Body::Ship(_) => s.1.is_settlement(), _ => s.1.is_dungeon(), }) .filter(|_| thread_rng().gen_range(0i32..4) == 0) diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 6faddda0c1..30b16f5d1f 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -122,7 +122,10 @@ impl<'a> System<'a> for Sys { comp::Body::Humanoid(_) => comp::Alignment::Npc, _ => comp::Alignment::Wild, }, - scale: comp::Scale(1.0), + scale: match body { + comp::Body::Ship(_) => comp::Scale(11.0 / 0.8), + _ => comp::Scale(1.0), + }, drop_item: None, home_chunk: None, rtsim_entity: Some(RtSimEntity(id)), diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index f19b105b5d..155b07e286 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -41,7 +41,13 @@ pub trait StateExt { ) -> EcsEntityBuilder; /// Build a static object entity fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder; - fn create_ship(&mut self, pos: comp::Pos, ship: comp::ship::Body, level: u16, moving: bool) -> EcsEntityBuilder; + fn create_ship( + &mut self, + pos: comp::Pos, + ship: comp::ship::Body, + level: u16, + destination: Option>, + ) -> EcsEntityBuilder; /// Build a projectile fn create_projectile( &mut self, @@ -173,10 +179,15 @@ impl StateExt for State { )) .unwrap_or_default(), ) - .with(comp::Collider::Box { - radius: body.radius(), - z_min: 0.0, - z_max: body.height(), + .with(match body { + comp::Body::Ship(ship) => comp::Collider::Voxel { + id: ship.manifest_entry().to_string(), + }, + _ => comp::Collider::Box { + radius: body.radius(), + z_min: 0.0, + z_max: body.height(), + }, }) .with(comp::Controller::default()) .with(body) @@ -216,46 +227,38 @@ impl StateExt for State { .with(comp::Gravity(1.0)) } - fn create_ship(&mut self, pos: comp::Pos, ship: comp::ship::Body, level: u16, moving: bool) -> EcsEntityBuilder { - if moving { - self.ecs_mut() - .create_entity_synced() - .with(pos) - .with(comp::Vel(Vec3::zero())) - .with(comp::Ori::default()) - .with(comp::Mass(50.0)) - .with(comp::Collider::Voxel { id: ship.manifest_entry().to_string() }) - .with(comp::Body::Ship(ship)) - .with(comp::Gravity(1.0)) - .with(comp::Controller::default()) - .with(comp::inventory::Inventory::new_empty()) - .with(comp::CharacterState::default()) - .with(comp::Energy::new(ship.into(), level)) - .with(comp::Health::new(ship.into(), level)) - .with(comp::Stats::new("Airship".to_string())) - .with(comp::MountState::Unmounted) - .with(comp::Buffs::default()) - .with(comp::Combo::default()) - .with(comp::Agent::with_destination()) - } else { - self.ecs_mut() - .create_entity_synced() - .with(pos) - .with(comp::Vel(Vec3::zero())) - .with(comp::Ori::default()) - .with(comp::Mass(50.0)) - .with(comp::Collider::Voxel { id: ship.manifest_entry().to_string() }) - .with(comp::Body::Ship(ship)) - .with(comp::Gravity(1.0)) - .with(comp::Controller::default()) - .with(comp::inventory::Inventory::new_empty()) - .with(comp::CharacterState::default()) - .with(comp::Energy::new(ship.into(), level)) - .with(comp::Health::new(ship.into(), level)) - .with(comp::Stats::new("Airship".to_string())) - .with(comp::Buffs::default()) - .with(comp::Combo::default()) + fn create_ship( + &mut self, + pos: comp::Pos, + ship: comp::ship::Body, + level: u16, + destination: Option>, + ) -> EcsEntityBuilder { + let mut builder = self + .ecs_mut() + .create_entity_synced() + .with(pos) + .with(comp::Vel(Vec3::zero())) + .with(comp::Ori::default()) + .with(comp::Mass(50.0)) + .with(comp::Collider::Voxel { + id: ship.manifest_entry().to_string(), + }) + .with(comp::Body::Ship(ship)) + .with(comp::Gravity(1.0)) + .with(comp::Controller::default()) + .with(comp::inventory::Inventory::new_empty()) + .with(comp::CharacterState::default()) + .with(comp::Energy::new(ship.into(), level)) + .with(comp::Health::new(ship.into(), level)) + .with(comp::Stats::new("Airship".to_string())) + .with(comp::Buffs::default()) + .with(comp::MountState::Unmounted) + .with(comp::Combo::default()); + if let Some(pos) = destination { + builder = builder.with(comp::Agent::with_destination(pos)) } + builder } fn create_projectile( diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 73046aeabd..3d36f8f8c1 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -34,7 +34,7 @@ use specs::{ Entities, Entity as EcsEntity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, World, Write, WriteStorage, }; -use std::f32::consts::PI; +use std::{f32::consts::PI, sync::Arc}; use vek::*; struct AgentData<'a> { @@ -82,6 +82,7 @@ pub struct ReadData<'a> { mount_states: ReadStorage<'a, MountState>, time_of_day: Read<'a, TimeOfDay>, light_emitter: ReadStorage<'a, LightEmitter>, + world: ReadExpect<'a, Arc>, } // This is 3.1 to last longer than the last damage timer (3.0 seconds) @@ -631,6 +632,29 @@ impl<'a> AgentData<'a> { .cast() .1 .map_or(true, |b| b.is_some()) + || self + .body + .map(|body| { + let height_approx = self.pos.0.y + - read_data + .world + .sim() + .get_alt_approx(self.pos.0.xy().map(|x: f32| x as i32)) + .unwrap_or(0.0); + + height_approx < body.flying_height() + || read_data + .terrain + .ray( + self.pos.0, + self.pos.0 - body.flying_height() * Vec3::unit_z(), + ) + .until(|b: &Block| b.is_solid() || b.is_liquid()) + .cast() + .1 + .map_or(false, |b| b.is_some()) + }) + .unwrap_or(false) { 1.0 //fly up when approaching obstacles } else { From 3496c356e25c387dba1105083cf06fc3aeea9ec6 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 12 Mar 2021 18:53:01 +0000 Subject: [PATCH 13/41] More relative motion changes --- common/src/states/utils.rs | 2 +- common/sys/src/phys.rs | 13 +++++++++---- common/sys/src/state.rs | 3 ++- server/src/events/entity_creation.rs | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 794e1c1c4b..65a9849b97 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -169,7 +169,7 @@ impl Body { quadruped_low::Species::Lavadrake => 4.0, _ => 6.0, }, - Body::Ship(_) => 0.1, + Body::Ship(_) => 0.5, } } diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 99ed590750..7144928bf6 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -464,6 +464,8 @@ impl<'a> PhysicsSystemData<'a> { Vec3::zero() }; + let was_on_ground = physics_state.on_ground; + match &*collider { Collider::Voxel { .. } => { // for now, treat entities with voxel colliders as their bounding @@ -484,6 +486,7 @@ impl<'a> PhysicsSystemData<'a> { Vec3::zero(), &psdr.dt, true, + was_on_ground, |entity, vel| land_on_grounds.push((entity, vel)), ); }, @@ -509,6 +512,7 @@ impl<'a> PhysicsSystemData<'a> { Vec3::zero(), &psdr.dt, true, + was_on_ground, |entity, vel| land_on_grounds.push((entity, vel)), ); }, @@ -636,6 +640,7 @@ impl<'a> PhysicsSystemData<'a> { transform_to.mul_direction(vel_other.0), &psdr.dt, false, + was_on_ground, |entity, vel| land_on_grounds.push((entity, Vel(transform_from.mul_direction(vel.0)))), ); @@ -750,6 +755,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( ground_vel: Vec3, dt: &DeltaTime, apply_velocity_step: bool, // Stupid hack + was_on_ground: bool, mut land_on_ground: impl FnMut(Entity, Vel), ) { let (radius, z_min, z_max) = cylinder; @@ -825,7 +831,6 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( > 0 } - let was_on_ground = physics_state.on_ground; physics_state.on_ground = false; let mut on_ground = false; @@ -948,7 +953,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( { // ...block-hop! pos.0.z = (pos.0.z + 0.1).floor() + block_height; - vel.0.z = 0.0; + vel.0.z = vel.0.z.max(0.0); on_ground = true; break; } else { @@ -956,7 +961,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( vel.0 = vel.0.map2( resolve_dir, |e, d| { - if d * e.signum() < 0.0 { 0.0 } else { e } + if d * e.signum() < 0.0 { if d < 0.0 { d.max(0.0) } else { d.min(0.0) } } else { e } }, ); pos_delta *= resolve_dir.map(|e| if e != 0.0 { 0.0 } else { 1.0 }); @@ -992,7 +997,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( near_iter.clone(), radius, z_range.clone(), - ) && vel.0.z < 0.0 + ) && vel.0.z < 0.01 && vel.0.z > -1.5 && was_on_ground && !collision_with( diff --git a/common/sys/src/state.rs b/common/sys/src/state.rs index 3d15d4e3ff..f0e9108f7c 100644 --- a/common/sys/src/state.rs +++ b/common/sys/src/state.rs @@ -455,10 +455,11 @@ impl State { for event in events { let mut velocities = self.ecs.write_storage::(); let mut controllers = self.ecs.write_storage::(); + let physics = self.ecs.read_storage::(); match event { LocalEvent::Jump(entity) => { if let Some(vel) = velocities.get_mut(entity) { - vel.0.z = HUMANOID_JUMP_ACCEL; + vel.0.z = HUMANOID_JUMP_ACCEL + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z); } }, LocalEvent::ApplyImpulse { entity, impulse } => { diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index a10b60c9d9..a3b3702975 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -129,7 +129,7 @@ pub fn handle_shoot( return; }; - let vel = *dir * speed; + let vel = *dir * speed + state.ecs().read_storage::().get(entity).map_or(Vec3::zero(), |v| v.0); // Add an outcome state From 40c284fd718a8b5f59c096616d366d67ee41256f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 12 Mar 2021 19:49:20 +0000 Subject: [PATCH 14/41] Relative velocity collisions --- common/sys/src/phys.rs | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 7144928bf6..9fbba02e14 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -627,7 +627,7 @@ impl<'a> PhysicsSystemData<'a> { * Mat4::::translation_3d(voxel_collider.translation); let transform_to = transform_from.inverted(); pos.0 = transform_to.mul_point(pos.0); - vel.0 = transform_to.mul_direction(vel.0); + vel.0 = transform_to.mul_direction(vel.0 - vel_other.0); let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, @@ -645,7 +645,7 @@ impl<'a> PhysicsSystemData<'a> { ); pos.0 = transform_from.mul_point(pos.0); - vel.0 = transform_from.mul_direction(vel.0); + vel.0 = transform_from.mul_direction(vel.0) + vel_other.0; // union in the state updates, so that the state isn't just based on // the most recent terrain that collision was attempted with @@ -919,7 +919,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( // When the resolution direction is pointing upwards, we must be on the // ground - if resolve_dir.z > 0.0 && vel.0.z <= 0.0 { + if resolve_dir.z > 0.0 /*&& vel.0.z <= 0.0*/ { on_ground = true; if !was_on_ground { @@ -986,39 +986,41 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( if on_ground { physics_state.on_ground = true; - - vel.0 = ground_vel + (vel.0 - ground_vel) * (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); - physics_state.ground_vel = ground_vel; // If the space below us is free, then "snap" to the ground } else if collision_with( - pos.0 - Vec3::unit_z() * 1.05, + pos.0 - Vec3::unit_z() * 1.1, &terrain, block_true, near_iter.clone(), radius, z_range.clone(), - ) && vel.0.z < 0.01 + ) && vel.0.z < 0.1 && vel.0.z > -1.5 - && was_on_ground - && !collision_with( - pos.0 - Vec3::unit_z() * 0.05, - &terrain, - |block| block.solid_height() >= (pos.0.z - 0.05).rem_euclid(1.0), - near_iter.clone(), - radius, - z_range.clone(), - ) + // && was_on_ground + // && !collision_with( + // pos.0 - Vec3::unit_z() * 0.0, + // &terrain, + // |block| block.solid_height() >= (pos.0.z - 0.1).rem_euclid(1.0), + // near_iter.clone(), + // radius, + // z_range.clone(), + // ) { let snap_height = terrain - .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.05).map(|e| e.floor() as i32)) + .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.1).map(|e| e.floor() as i32)) .ok() .filter(|block| block.is_solid()) .map(|block| block.solid_height()) .unwrap_or(0.0); - pos.0.z = (pos.0.z - 0.05).floor() + snap_height; + pos.0.z = (pos.0.z - 0.1).floor() + snap_height; physics_state.on_ground = true; } + if physics_state.on_ground { + vel.0 = ground_vel * 0.0 + (vel.0 - ground_vel * 0.0) * (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); + physics_state.ground_vel = ground_vel; + } + let dirs = [ Vec3::unit_x(), Vec3::unit_y(), From aa56166c80e4e3c7b1d74eb249586798a793e289 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Fri, 12 Mar 2021 17:14:08 -0500 Subject: [PATCH 15/41] Fix fmt and clippy, and rename {psdr,psdw} to {read,write} in physics. --- common/src/cmd.rs | 6 +- common/src/comp/body.rs | 8 +- common/src/comp/body/ship.rs | 3 +- common/src/comp/mod.rs | 4 +- common/src/comp/phys.rs | 3 +- common/src/rtsim.rs | 1 + common/src/states/utils.rs | 3 +- common/src/volumes/dyna.rs | 7 +- common/sys/src/phys.rs | 276 ++++++++++++++------------- common/sys/src/state.rs | 3 +- server/src/cmd.rs | 9 +- server/src/events/entity_creation.rs | 7 +- server/src/rtsim/entity.rs | 4 +- server/src/sys/agent.rs | 51 ++--- voxygen/anim/src/lib.rs | 2 +- voxygen/anim/src/ship/idle.rs | 1 - voxygen/anim/src/ship/mod.rs | 5 +- voxygen/src/scene/figure/load.rs | 21 +- voxygen/src/scene/figure/mod.rs | 89 +++++---- 19 files changed, 280 insertions(+), 223 deletions(-) diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 3da0e9b52d..e53c82609e 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -224,7 +224,11 @@ impl ChatCommand { "Temporarily gives a player admin permissions or removes them", Admin, ), - ChatCommand::Airship => cmd(vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)], "Spawns an airship", Admin), + ChatCommand::Airship => cmd( + vec![Float("destination_degrees_ccw_of_east", 90.0, Optional)], + "Spawns an airship", + Admin, + ), ChatCommand::Alias => cmd(vec![Any("name", Required)], "Change your alias", NoAdmin), ChatCommand::Ban => cmd( vec![Any("username", Required), Message(Optional)], diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 2abbe775c6..9993e6a0c8 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -539,7 +539,13 @@ impl Body { /// Returns a multiplier representing increased difficulty not accounted for /// due to AI or not using an actual weapon // TODO: Match on species - pub fn combat_multiplier(&self) -> f32 { if let Body::Object(_) | Body::Ship(_) = self { 0.0 } else { 1.0 } } + pub fn combat_multiplier(&self) -> f32 { + if let Body::Object(_) | Body::Ship(_) = self { + 0.0 + } else { + 1.0 + } + } pub fn base_poise(&self) -> u32 { match self { diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index 3cab7a0ecb..4a49d9f2a1 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -78,7 +78,8 @@ pub mod figuredata { cache: &assets::AssetCache, _: &str, ) -> Result { - let manifest: AssetHandle> = AssetExt::load("server.manifests.ship_manifest")?; + let manifest: AssetHandle> = + AssetExt::load("server.manifests.ship_manifest")?; let mut colliders = HashMap::new(); for (_, spec) in (manifest.read().0).0.iter() { for bone in [&spec.bone0, &spec.bone1, &spec.bone2].iter() { diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index d97e265919..6f951b9704 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -48,8 +48,8 @@ pub use self::{ beam::{Beam, BeamSegment}, body::{ biped_large, biped_small, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, - humanoid, object, quadruped_low, quadruped_medium, quadruped_small, theropod, ship, AllBodies, - Body, BodyData, + humanoid, object, quadruped_low, quadruped_medium, quadruped_small, ship, theropod, + AllBodies, Body, BodyData, }, buff::{ Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs, diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index 149f8ebfd4..33729f28ad 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -119,7 +119,8 @@ impl PhysicsState { touch_entities.clear(); *self = Self { touch_entities, - ground_vel: self.ground_vel, // Preserved, since it's the velocity of the last contact point + ground_vel: self.ground_vel, /* Preserved, since it's the velocity of the last + * contact point */ ..Self::default() } } diff --git a/common/src/rtsim.rs b/common/src/rtsim.rs index 716bacf382..49a831e12d 100644 --- a/common/src/rtsim.rs +++ b/common/src/rtsim.rs @@ -46,6 +46,7 @@ impl Default for RtSimController { impl RtSimController { pub fn reset(&mut self) { *self = Self::default(); } + pub fn with_destination(pos: Vec3) -> Self { Self { travel_to: Some((pos, format!("{:0.1?}", pos))), diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 65a9849b97..6ee2449950 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -3,7 +3,7 @@ use crate::{ biped_large, biped_small, inventory::slot::EquipSlot, item::{Hands, ItemKind, Tool, ToolKind}, - quadruped_low, quadruped_medium, quadruped_small, + quadruped_low, quadruped_medium, quadruped_small, ship, skills::Skill, theropod, ship, Body, CharacterAbility, CharacterState, InputKind, InventoryAction, StateUpdate, }, @@ -193,7 +193,6 @@ pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { && data.body.can_fly().is_some() { fly_move(data, update, efficiency * data.body.can_fly().expect("can_fly is_some right above this")); - } else if data.inputs.fly.is_pressed() && !data.physics.on_ground && data.body.can_fly().is_some() { } else { basic_move(data, update, efficiency); } diff --git a/common/src/volumes/dyna.rs b/common/src/volumes/dyna.rs index d0648010a8..b6cb3e6bdc 100644 --- a/common/src/volumes/dyna.rs +++ b/common/src/volumes/dyna.rs @@ -46,7 +46,12 @@ impl Dyna { } pub fn map_into W>(self, f: F) -> Dyna { - let Dyna { vox, meta, sz, _phantom } = self; + let Dyna { + vox, + meta, + sz, + _phantom, + } = self; Dyna { vox: vox.into_iter().map(f).collect(), meta, diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 9fbba02e14..e01bcd276f 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -69,7 +69,7 @@ fn calc_z_limit( pub struct Sys; #[derive(SystemData)] -pub struct PhysicsSystemDataRead<'a> { +pub struct PhysicsRead<'a> { entities: Entities<'a>, uids: ReadStorage<'a, Uid>, terrain: ReadExpect<'a, TerrainGrid>, @@ -88,7 +88,7 @@ pub struct PhysicsSystemDataRead<'a> { } #[derive(SystemData)] -pub struct PhysicsSystemDataWrite<'a> { +pub struct PhysicsWrite<'a> { physics_metrics: WriteExpect<'a, PhysicsMetrics>, physics_states: WriteStorage<'a, PhysicsState>, positions: WriteStorage<'a, Pos>, @@ -98,26 +98,26 @@ pub struct PhysicsSystemDataWrite<'a> { } #[derive(SystemData)] -pub struct PhysicsSystemData<'a> { - r: PhysicsSystemDataRead<'a>, - w: PhysicsSystemDataWrite<'a>, +pub struct PhysicsData<'a> { + read: PhysicsRead<'a>, + write: PhysicsWrite<'a>, } -impl<'a> PhysicsSystemData<'a> { +impl<'a> PhysicsData<'a> { /// Add/reset physics state components fn reset(&mut self) { span!(guard, "Add/reset physics state components"); for (entity, _, _, _, _) in ( - &self.r.entities, - &self.r.colliders, - &self.w.positions, - &self.w.velocities, - &self.w.orientations, + &self.read.entities, + &self.read.colliders, + &self.write.positions, + &self.write.velocities, + &self.write.orientations, ) .join() { let _ = self - .w + .write .physics_states .entry(entity) .map(|e| e.or_insert_with(Default::default)); @@ -129,20 +129,20 @@ impl<'a> PhysicsSystemData<'a> { span!(guard, "Maintain pushback cache"); //Add PreviousPhysCache for all relevant entities for entity in ( - &self.r.entities, - &self.w.velocities, - &self.w.positions, - !&self.w.previous_phys_cache, - !&self.r.mountings, - !&self.r.beams, - !&self.r.shockwaves, + &self.read.entities, + &self.write.velocities, + &self.write.positions, + !&self.write.previous_phys_cache, + !&self.read.mountings, + !&self.read.beams, + !&self.read.shockwaves, ) .join() .map(|(e, _, _, _, _, _, _)| e) .collect::>() { let _ = self - .w + .write .previous_phys_cache .insert(entity, PreviousPhysCache { velocity_dt: Vec3::zero(), @@ -155,16 +155,16 @@ impl<'a> PhysicsSystemData<'a> { //Update PreviousPhysCache for (_, vel, position, mut phys_cache, collider, scale, cs, _, _, _) in ( - &self.r.entities, - &self.w.velocities, - &self.w.positions, - &mut self.w.previous_phys_cache, - self.r.colliders.maybe(), - self.r.scales.maybe(), - self.r.char_states.maybe(), - !&self.r.mountings, - !&self.r.beams, - !&self.r.shockwaves, + &self.read.entities, + &self.write.velocities, + &self.write.positions, + &mut self.write.previous_phys_cache, + self.read.colliders.maybe(), + self.read.scales.maybe(), + self.read.char_states.maybe(), + !&self.read.mountings, + !&self.read.beams, + !&self.read.shockwaves, ) .join() { @@ -173,7 +173,7 @@ impl<'a> PhysicsSystemData<'a> { let z_limits = (z_limits.0 * scale, z_limits.1 * scale); let half_height = (z_limits.1 - z_limits.0) / 2.0; - phys_cache.velocity_dt = vel.0 * self.r.dt.0; + phys_cache.velocity_dt = vel.0 * self.read.dt.0; let entity_center = position.0 + Vec3::new(0.0, z_limits.0 + half_height, 0.0); let flat_radius = collider.map(|c| c.get_radius()).unwrap_or(0.5) * scale; let radius = (flat_radius.powi(2) + half_height.powi(2)).sqrt(); @@ -191,25 +191,25 @@ impl<'a> PhysicsSystemData<'a> { fn apply_pushback(&mut self, job: &mut Job) { span!(guard, "Apply pushback"); job.cpu_stats.measure(ParMode::Rayon); - let PhysicsSystemData { - r: ref psdr, - w: ref mut psdw, + let PhysicsData { + ref read, + ref mut write, } = self; - let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); + let (positions, previous_phys_cache) = (&write.positions, &write.previous_phys_cache); let metrics = ( - &psdr.entities, + &read.entities, positions, - &mut psdw.velocities, + &mut write.velocities, previous_phys_cache, - psdr.masses.maybe(), - psdr.colliders.maybe(), - !&psdr.mountings, - psdr.stickies.maybe(), - &mut psdw.physics_states, + read.masses.maybe(), + read.colliders.maybe(), + !&read.mountings, + read.stickies.maybe(), + &mut write.physics_states, // TODO: if we need to avoid collisions for other things consider moving whether it // should interact into the collider component or into a separate component - psdr.projectiles.maybe(), - psdr.char_states.maybe(), + read.projectiles.maybe(), + read.char_states.maybe(), ) .par_join() .filter(|(_, _, _, _, _, _, _, sticky, physics, _, _)| { @@ -259,17 +259,17 @@ impl<'a> PhysicsSystemData<'a> { _, char_state_other_maybe, ) in ( - &psdr.entities, - &psdr.uids, + &read.entities, + &read.uids, positions, previous_phys_cache, - psdr.masses.maybe(), - psdr.colliders.maybe(), - !&psdr.projectiles, - !&psdr.mountings, - !&psdr.beams, - !&psdr.shockwaves, - psdr.char_states.maybe(), + read.masses.maybe(), + read.colliders.maybe(), + !&read.projectiles, + !&read.mountings, + !&read.beams, + !&read.shockwaves, + read.char_states.maybe(), ) .join() { @@ -347,7 +347,7 @@ impl<'a> PhysicsSystemData<'a> { } // Change velocity - vel.0 += vel_delta * psdr.dt.0; + vel.0 += vel_delta * read.dt.0; // Metrics PhysicsMetrics { @@ -362,31 +362,36 @@ impl<'a> PhysicsSystemData<'a> { entity_entity_collisions: old.entity_entity_collisions + new.entity_entity_collisions, }); - psdw.physics_metrics.entity_entity_collision_checks = + write.physics_metrics.entity_entity_collision_checks = metrics.entity_entity_collision_checks; - psdw.physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; + write.physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; drop(guard); } fn handle_movement_and_terrain(&mut self, job: &mut Job) { - let PhysicsSystemData { - r: ref psdr, - w: ref mut psdw, + let PhysicsData { + ref read, + ref mut write, } = self; // Apply movement inputs span!(guard, "Apply movement and terrain collision"); - let (positions, velocities, previous_phys_cache, orientations) = (&psdw.positions, &psdw.velocities, &psdw.previous_phys_cache, &psdw.orientations); + let (positions, velocities, previous_phys_cache, orientations) = ( + &write.positions, + &write.velocities, + &write.previous_phys_cache, + &write.orientations, + ); let (pos_writes, vel_writes, land_on_grounds) = ( - &psdr.entities, - psdr.scales.maybe(), - psdr.stickies.maybe(), - &psdr.colliders, + &read.entities, + read.scales.maybe(), + read.stickies.maybe(), + &read.colliders, positions, velocities, orientations, - &mut psdw.physics_states, + &mut write.physics_states, previous_phys_cache, - !&psdr.mountings, + !&read.mountings, ) .par_join() .fold( @@ -401,7 +406,7 @@ impl<'a> PhysicsSystemData<'a> { vel, _ori, mut physics_state, - previous_cache, + _previous_cache, _, )| { // defer the writes of positions to allow an inner loop over terrain-like @@ -436,9 +441,9 @@ impl<'a> PhysicsSystemData<'a> { } else { 0.0 }); - let in_loaded_chunk = psdr + let in_loaded_chunk = read .terrain - .get_key(psdr.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) + .get_key(read.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) .is_some(); let downward_force = if !in_loaded_chunk { @@ -451,22 +456,22 @@ impl<'a> PhysicsSystemData<'a> { (1.0 - BOUYANCY) * GRAVITY } else { GRAVITY - } * psdr.gravities.get(entity).map(|g| g.0).unwrap_or_default(); - vel.0 = integrate_forces(psdr.dt.0, vel.0, downward_force, friction); + } * read.gravities.get(entity).map(|g| g.0).unwrap_or_default(); + vel.0 = integrate_forces(read.dt.0, vel.0, downward_force, friction); // Don't move if we're not in a loaded chunk let pos_delta = if in_loaded_chunk { // this is an approximation that allows most framerates to // behave in a similar manner. let dt_lerp = 0.2; - (vel.0 * dt_lerp + old_vel.0 * (1.0 - dt_lerp)) * psdr.dt.0 + (vel.0 * dt_lerp + old_vel.0 * (1.0 - dt_lerp)) * read.dt.0 } else { Vec3::zero() }; let was_on_ground = physics_state.on_ground; - match &*collider { + match &collider { Collider::Voxel { .. } => { // for now, treat entities with voxel colliders as their bounding // cylinders for the purposes of colliding them with terrain @@ -477,14 +482,14 @@ impl<'a> PhysicsSystemData<'a> { let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, - &*psdr.terrain, + &*read.terrain, entity, &mut pos, pos_delta, &mut vel, &mut physics_state, Vec3::zero(), - &psdr.dt, + &read.dt, true, was_on_ground, |entity, vel| land_on_grounds.push((entity, vel)), @@ -503,35 +508,35 @@ impl<'a> PhysicsSystemData<'a> { let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, - &*psdr.terrain, + &*read.terrain, entity, &mut pos, pos_delta, &mut vel, &mut physics_state, Vec3::zero(), - &psdr.dt, + &read.dt, true, was_on_ground, |entity, vel| land_on_grounds.push((entity, vel)), ); }, Collider::Point => { - let (dist, block) = psdr + let (dist, block) = read .terrain .ray(pos.0, pos.0 + pos_delta) .until(|block: &Block| block.is_filled()) .ignore_error() .cast(); - pos.0 += pos_delta.try_normalized().unwrap_or(Vec3::zero()) * dist; + pos.0 += pos_delta.try_normalized().unwrap_or_else(Vec3::zero) * dist; // Can't fail since we do ignore_error above if block.unwrap().is_some() { let block_center = pos.0.map(|e| e.floor()) + 0.5; let block_rpos = (pos.0 - block_center) .try_normalized() - .unwrap_or(Vec3::zero()); + .unwrap_or_else(Vec3::zero); // See whether we're on the top/bottom of a block, or the side if block_rpos.z.abs() @@ -555,7 +560,7 @@ impl<'a> PhysicsSystemData<'a> { } } - physics_state.in_liquid = psdr + physics_state.in_liquid = read .terrain .get(pos.0.map(|e| e.floor() as i32)) .ok() @@ -566,35 +571,36 @@ impl<'a> PhysicsSystemData<'a> { // Collide with terrain-like entities for ( entity_other, - other, + _other, pos_other, vel_other, - previous_cache_other, - mass_other, + _previous_cache_other, + _mass_other, collider_other, ori_other, _, _, _, _, - char_state_other_maybe, + _char_state_other_maybe, ) in ( - &psdr.entities, - &psdr.uids, + &read.entities, + &read.uids, positions, velocities, previous_phys_cache, - psdr.masses.maybe(), - &psdr.colliders, + read.masses.maybe(), + &read.colliders, orientations, - !&psdr.projectiles, - !&psdr.mountings, - !&psdr.beams, - !&psdr.shockwaves, - psdr.char_states.maybe(), + !&read.projectiles, + !&read.mountings, + !&read.beams, + !&read.shockwaves, + read.char_states.maybe(), ) .join() { + // TODO: terrain-collider-size aware broadphase /*let collision_boundary = previous_cache.collision_boundary + previous_cache_other.collision_boundary; if previous_cache @@ -618,7 +624,9 @@ impl<'a> PhysicsSystemData<'a> { let z_min = z_min * scale; let z_max = z_max.clamped(1.2, 1.95) * scale; - if let Some(voxel_collider) = VOXEL_COLLIDER_MANIFEST.read().colliders.get(id) { + if let Some(voxel_collider) = + VOXEL_COLLIDER_MANIFEST.read().colliders.get(&*id) + { let mut physics_state_delta = physics_state.clone(); // deliberately don't use scale yet here, because the 11.0/0.8 // thing is in the comp::Scale for visual reasons @@ -638,10 +646,15 @@ impl<'a> PhysicsSystemData<'a> { &mut vel, &mut physics_state_delta, transform_to.mul_direction(vel_other.0), - &psdr.dt, + &read.dt, false, was_on_ground, - |entity, vel| land_on_grounds.push((entity, Vel(transform_from.mul_direction(vel.0)))), + |entity, vel| { + land_on_grounds.push(( + entity, + Vel(transform_from.mul_direction(vel.0)), + )) + }, ); pos.0 = transform_from.mul_point(pos.0); @@ -659,14 +672,16 @@ impl<'a> PhysicsSystemData<'a> { physics_state .touch_entities .append(&mut physics_state_delta.touch_entities); - physics_state.in_liquid = - match (physics_state.in_liquid, physics_state_delta.in_liquid) { - // this match computes `x <|> y <|> liftA2 max x y` - (Some(x), Some(y)) => Some(x.max(y)), - (x @ Some(_), _) => x, - (_, y @ Some(_)) => y, - _ => None, - }; + physics_state.in_liquid = match ( + physics_state.in_liquid, + physics_state_delta.in_liquid, + ) { + // this match computes `x <|> y <|> liftA2 max x y` + (Some(x), Some(y)) => Some(x.max(y)), + (x @ Some(_), _) => x, + (_, y @ Some(_)) => y, + _ => None, + }; } } } @@ -695,7 +710,9 @@ impl<'a> PhysicsSystemData<'a> { let pos_writes: HashMap = pos_writes.into_iter().collect(); let vel_writes: HashMap = vel_writes.into_iter().collect(); - for (entity, pos, vel) in (&psdr.entities, &mut psdw.positions, &mut psdw.velocities).join() { + for (entity, pos, vel) in + (&read.entities, &mut write.positions, &mut write.velocities).join() + { if let Some(new_pos) = pos_writes.get(&entity) { *pos = *new_pos; } @@ -705,7 +722,7 @@ impl<'a> PhysicsSystemData<'a> { } } - let mut event_emitter = psdr.event_bus.emitter(); + let mut event_emitter = read.event_bus.emitter(); land_on_grounds.into_iter().for_each(|(entity, vel)| { event_emitter.emit(ServerEvent::LandOnGround { entity, vel: vel.0 }); }); @@ -713,14 +730,12 @@ impl<'a> PhysicsSystemData<'a> { } impl<'a> System<'a> for Sys { - type SystemData = PhysicsSystemData<'a>; + type SystemData = PhysicsData<'a>; const NAME: &'static str = "phys"; const ORIGIN: Origin = Origin::Common; const PHASE: Phase = Phase::Create; - #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 - #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 fn run(job: &mut Job, mut psd: Self::SystemData) { psd.reset(); @@ -744,6 +759,7 @@ impl<'a> System<'a> for Sys { } } +#[allow(clippy::too_many_arguments)] fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( cylinder: (f32, f32, f32), terrain: &'a T, @@ -919,7 +935,9 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( // When the resolution direction is pointing upwards, we must be on the // ground - if resolve_dir.z > 0.0 /*&& vel.0.z <= 0.0*/ { + if resolve_dir.z > 0.0 + /* && vel.0.z <= 0.0 */ + { on_ground = true; if !was_on_ground { @@ -958,12 +976,13 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( break; } else { // Correct the velocity - vel.0 = vel.0.map2( - resolve_dir, - |e, d| { - if d * e.signum() < 0.0 { if d < 0.0 { d.max(0.0) } else { d.min(0.0) } } else { e } - }, - ); + vel.0 = vel.0.map2(resolve_dir, |e, d| { + if d * e.signum() < 0.0 { + if d < 0.0 { d.max(0.0) } else { d.min(0.0) } + } else { + e + } + }); pos_delta *= resolve_dir.map(|e| if e != 0.0 { 0.0 } else { 1.0 }); } @@ -996,15 +1015,15 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( z_range.clone(), ) && vel.0.z < 0.1 && vel.0.z > -1.5 - // && was_on_ground - // && !collision_with( - // pos.0 - Vec3::unit_z() * 0.0, - // &terrain, - // |block| block.solid_height() >= (pos.0.z - 0.1).rem_euclid(1.0), - // near_iter.clone(), - // radius, - // z_range.clone(), - // ) + // && was_on_ground + // && !collision_with( + // pos.0 - Vec3::unit_z() * 0.0, + // &terrain, + // |block| block.solid_height() >= (pos.0.z - 0.1).rem_euclid(1.0), + // near_iter.clone(), + // radius, + // z_range.clone(), + // ) { let snap_height = terrain .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.1).map(|e| e.floor() as i32)) @@ -1017,7 +1036,8 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( } if physics_state.on_ground { - vel.0 = ground_vel * 0.0 + (vel.0 - ground_vel * 0.0) * (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); + vel.0 = ground_vel * 0.0 + + (vel.0 - ground_vel * 0.0) * (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); physics_state.ground_vel = ground_vel; } @@ -1054,7 +1074,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( &|block| block.is_liquid(), // The liquid part of a liquid block always extends 1 block high. &|_block| 1.0, - near_iter.clone(), + near_iter, radius, z_min..z_max, ) diff --git a/common/sys/src/state.rs b/common/sys/src/state.rs index f0e9108f7c..8aae465bfd 100644 --- a/common/sys/src/state.rs +++ b/common/sys/src/state.rs @@ -459,7 +459,8 @@ impl State { match event { LocalEvent::Jump(entity) => { if let Some(vel) = velocities.get_mut(entity) { - vel.0.z = HUMANOID_JUMP_ACCEL + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z); + vel.0.z = HUMANOID_JUMP_ACCEL + + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z); } }, LocalEvent::ApplyImpulse { entity, impulse } => { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 7aae9f5339..851e8876c8 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -998,7 +998,14 @@ fn handle_spawn_airship( pos.0.z += 50.0; const DESTINATION_RADIUS: f32 = 2000.0; let angle = angle.map(|a| a * std::f32::consts::PI / 180.0); - let destination = angle.map(|a| pos.0 + Vec3::new(DESTINATION_RADIUS*a.cos(), DESTINATION_RADIUS*a.sin(), 200.0)); + let destination = angle.map(|a| { + pos.0 + + Vec3::new( + DESTINATION_RADIUS * a.cos(), + DESTINATION_RADIUS * a.sin(), + 200.0, + ) + }); server .state .create_ship(pos, comp::ship::Body::DefaultAirship, 1, destination) diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index a3b3702975..e9868eae25 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -129,7 +129,12 @@ pub fn handle_shoot( return; }; - let vel = *dir * speed + state.ecs().read_storage::().get(entity).map_or(Vec3::zero(), |v| v.0); + let vel = *dir * speed + + state + .ecs() + .read_storage::() + .get(entity) + .map_or(Vec3::zero(), |v| v.0); // Add an outcome state diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index 18f500f695..0803406c55 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -28,9 +28,7 @@ impl Entity { pub fn get_body(&self) -> comp::Body { match self.rng(PERM_GENUS).gen::() { //we want 5% airships, 45% birds, 50% humans - x if x < 0.05 => { - comp::Body::Ship(comp::ship::Body::DefaultAirship) - }, + //x if x < 0.05 => comp::Body::Ship(comp::ship::Body::DefaultAirship), x if x < 0.50 => { let species = *(&comp::bird_medium::ALL_SPECIES) .choose(&mut self.rng(PERM_SPECIES)) diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 3d36f8f8c1..4abf971136 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -620,7 +620,7 @@ impl<'a> AgentData<'a> { controller.inputs.move_z = bearing.z + if self.traversal_config.can_fly { - if read_data + let obstacle_ahead = read_data .terrain .ray( self.pos.0 + Vec3::unit_z(), @@ -631,31 +631,32 @@ impl<'a> AgentData<'a> { .until(Block::is_solid) .cast() .1 - .map_or(true, |b| b.is_some()) - || self - .body - .map(|body| { - let height_approx = self.pos.0.y - - read_data - .world - .sim() - .get_alt_approx(self.pos.0.xy().map(|x: f32| x as i32)) - .unwrap_or(0.0); + .map_or(true, |b| b.is_some()); + let ground_too_close = self + .body + .map(|body| { + let height_approx = self.pos.0.y + - read_data + .world + .sim() + .get_alt_approx(self.pos.0.xy().map(|x: f32| x as i32)) + .unwrap_or(0.0); - height_approx < body.flying_height() - || read_data - .terrain - .ray( - self.pos.0, - self.pos.0 - body.flying_height() * Vec3::unit_z(), - ) - .until(|b: &Block| b.is_solid() || b.is_liquid()) - .cast() - .1 - .map_or(false, |b| b.is_some()) - }) - .unwrap_or(false) - { + height_approx < body.flying_height() + || read_data + .terrain + .ray( + self.pos.0, + self.pos.0 - body.flying_height() * Vec3::unit_z(), + ) + .until(|b: &Block| b.is_solid() || b.is_liquid()) + .cast() + .1 + .map_or(false, |b| b.is_some()) + }) + .unwrap_or(false); + + if obstacle_ahead || ground_too_close { 1.0 //fly up when approaching obstacles } else { -0.1 diff --git a/voxygen/anim/src/lib.rs b/voxygen/anim/src/lib.rs index d903d6572f..c424b4d869 100644 --- a/voxygen/anim/src/lib.rs +++ b/voxygen/anim/src/lib.rs @@ -51,10 +51,10 @@ pub mod fish_small; pub mod fixture; pub mod golem; pub mod object; -pub mod ship; pub mod quadruped_low; pub mod quadruped_medium; pub mod quadruped_small; +pub mod ship; pub mod theropod; pub mod vek; diff --git a/voxygen/anim/src/ship/idle.rs b/voxygen/anim/src/ship/idle.rs index b96c9fd64e..a8e59d5c56 100644 --- a/voxygen/anim/src/ship/idle.rs +++ b/voxygen/anim/src/ship/idle.rs @@ -31,4 +31,3 @@ impl Animation for IdleAnimation { next } } - diff --git a/voxygen/anim/src/ship/mod.rs b/voxygen/anim/src/ship/mod.rs index 2e2783add3..14316d2ab3 100644 --- a/voxygen/anim/src/ship/mod.rs +++ b/voxygen/anim/src/ship/mod.rs @@ -64,8 +64,5 @@ impl Default for SkeletonAttr { } impl<'a> From<&'a Body> for SkeletonAttr { - fn from(_: &'a Body) -> Self { - Self::default() - } + fn from(_: &'a Body) -> Self { Self::default() } } - diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index ff334213e2..233c77c01c 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -13,10 +13,13 @@ use common::{ humanoid::{self, Body, BodyType, EyeColor, Skin, Species}, item::{ItemDef, ModularComponentKind}, object, - ship::{self, figuredata::{ShipSpec, ShipCentralSubSpec}}, quadruped_low::{self, BodyType as QLBodyType, Species as QLSpecies}, quadruped_medium::{self, BodyType as QMBodyType, Species as QMSpecies}, quadruped_small::{self, BodyType as QSBodyType, Species as QSSpecies}, + ship::{ + self, + figuredata::{ShipCentralSubSpec, ShipSpec}, + }, theropod::{self, BodyType as TBodyType, Species as TSpecies}, }, figure::{DynaUnionizer, MatSegment, Material, Segment}, @@ -4238,7 +4241,11 @@ impl ShipCentralSpec { (central, Vec3::from(bone.offset)) } }*/ -fn mesh_ship_bone &ShipCentralSubSpec>(map: &HashMap, obj: &K, f: F) -> BoneMeshes { +fn mesh_ship_bone &ShipCentralSubSpec>( + map: &HashMap, + obj: &K, + f: F, +) -> BoneMeshes { let spec = match map.get(&obj) { Some(spec) => spec, None => { @@ -4256,9 +4263,7 @@ impl BodySpec for ship::Body { type Spec = ShipSpec; #[allow(unused_variables)] - fn load_spec() -> Result, assets::Error> { - Self::Spec::load("") - } + fn load_spec() -> Result, assets::Error> { Self::Spec::load("") } fn bone_meshes( FigureKey { body, .. }: &FigureKey, @@ -4266,9 +4271,9 @@ impl BodySpec for ship::Body { ) -> [Option; anim::MAX_BONE_COUNT] { let map = &(spec.central.read().0).0; [ - Some(mesh_ship_bone(map, body, |spec| &spec.bone0,)), - Some(mesh_ship_bone(map, body, |spec| &spec.bone1,)), - Some(mesh_ship_bone(map, body, |spec| &spec.bone2,)), + Some(mesh_ship_bone(map, body, |spec| &spec.bone0)), + Some(mesh_ship_bone(map, body, |spec| &spec.bone1)), + Some(mesh_ship_bone(map, body, |spec| &spec.bone2)), None, None, None, diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 926680fdfa..f2070d03cc 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -21,9 +21,9 @@ use anim::{ biped_large::BipedLargeSkeleton, biped_small::BipedSmallSkeleton, bird_medium::BirdMediumSkeleton, bird_small::BirdSmallSkeleton, character::CharacterSkeleton, dragon::DragonSkeleton, fish_medium::FishMediumSkeleton, fish_small::FishSmallSkeleton, - golem::GolemSkeleton, object::ObjectSkeleton, ship::ShipSkeleton, quadruped_low::QuadrupedLowSkeleton, + golem::GolemSkeleton, object::ObjectSkeleton, quadruped_low::QuadrupedLowSkeleton, quadruped_medium::QuadrupedMediumSkeleton, quadruped_small::QuadrupedSmallSkeleton, - theropod::TheropodSkeleton, Animation, Skeleton, + ship::ShipSkeleton, theropod::TheropodSkeleton, Animation, Skeleton, }; use common::{ comp::{ @@ -320,11 +320,7 @@ impl FigureMgrStates { .iter() .filter(|(_, c)| c.visible()) .count() - + self - .ship_states - .iter() - .filter(|(_, c)| c.visible()) - .count() + + self.ship_states.iter().filter(|(_, c)| c.visible()).count() } } @@ -787,18 +783,12 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => anim::character::StandAnimation::update_skeleton( &CharacterSkeleton::default(), - ( - active_tool_kind, - second_tool_kind, - hands, - time, - rel_avg_vel, - ), + (active_tool_kind, second_tool_kind, hands, time, rel_avg_vel), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1406,7 +1396,12 @@ impl FigureMgr { CharacterState::Equipping { .. } => { anim::character::EquipAnimation::update_skeleton( &target_base, - (active_tool_kind, second_tool_kind, rel_vel.magnitude(), time), + ( + active_tool_kind, + second_tool_kind, + rel_vel.magnitude(), + time, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1429,7 +1424,12 @@ impl FigureMgr { if physics.in_liquid.is_some() { anim::character::SwimWieldAnimation::update_skeleton( &target_base, - (active_tool_kind, second_tool_kind, rel_vel.magnitude(), time), + ( + active_tool_kind, + second_tool_kind, + rel_vel.magnitude(), + time, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -1579,7 +1579,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => { @@ -1781,7 +1781,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > 0.25, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => { @@ -2108,7 +2108,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => { @@ -2453,7 +2453,7 @@ impl FigureMgr { }); // Average velocity relative to the current ground - let rel_avg_vel = state.avg_vel - physics.ground_vel; + let _rel_avg_vel = state.avg_vel - physics.ground_vel; let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), @@ -2467,7 +2467,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => anim::bird_medium::IdleAnimation::update_skeleton( @@ -2577,7 +2577,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Idle (_, false, _) => anim::fish_medium::IdleAnimation::update_skeleton( @@ -2666,7 +2666,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Idle (true, false, false) => anim::biped_small::IdleAnimation::update_skeleton( @@ -3009,7 +3009,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => anim::dragon::IdleAnimation::update_skeleton( @@ -3104,7 +3104,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => anim::theropod::IdleAnimation::update_skeleton( @@ -3279,7 +3279,7 @@ impl FigureMgr { }); // Average velocity relative to the current ground - let rel_avg_vel = state.avg_vel - physics.ground_vel; + let _rel_avg_vel = state.avg_vel - physics.ground_vel; let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), @@ -3293,7 +3293,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => anim::bird_small::IdleAnimation::update_skeleton( @@ -3384,7 +3384,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Idle (_, false, _) => anim::fish_small::IdleAnimation::update_skeleton( @@ -3473,7 +3473,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Running (true, true, false) => anim::biped_large::RunAnimation::update_skeleton( @@ -3513,7 +3513,12 @@ impl FigureMgr { CharacterState::Equipping { .. } => { anim::biped_large::EquipAnimation::update_skeleton( &target_base, - (active_tool_kind, second_tool_kind, rel_vel.magnitude(), time), + ( + active_tool_kind, + second_tool_kind, + rel_vel.magnitude(), + time, + ), state.state_time, &mut state_animation_rate, skeleton_attr, @@ -3887,7 +3892,7 @@ impl FigureMgr { }); // Average velocity relative to the current ground - let rel_avg_vel = state.avg_vel - physics.ground_vel; + let _rel_avg_vel = state.avg_vel - physics.ground_vel; let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), @@ -3901,7 +3906,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => anim::golem::IdleAnimation::update_skeleton( @@ -4071,7 +4076,7 @@ impl FigureMgr { }); // Average velocity relative to the current ground - let rel_avg_vel = state.avg_vel - physics.ground_vel; + let _rel_avg_vel = state.avg_vel - physics.ground_vel; let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), @@ -4087,7 +4092,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => anim::object::IdleAnimation::update_skeleton( @@ -4193,13 +4198,14 @@ impl FigureMgr { scene_data.runtime, ); - let state = - self.states.ship_states.entry(entity).or_insert_with(|| { - FigureState::new(renderer, ShipSkeleton::default()) - }); + let state = self + .states + .ship_states + .entry(entity) + .or_insert_with(|| FigureState::new(renderer, ShipSkeleton::default())); // Average velocity relative to the current ground - let rel_avg_vel = state.avg_vel - physics.ground_vel; + let _rel_avg_vel = state.avg_vel - physics.ground_vel; let (character, last_character) = match (character, last_character) { (Some(c), Some(l)) => (c, l), @@ -4215,7 +4221,7 @@ impl FigureMgr { let target_base = match ( physics.on_ground, rel_vel.magnitude_squared() > MOVING_THRESHOLD_SQR, // Moving - physics.in_liquid.is_some(), // In water + physics.in_liquid.is_some(), // In water ) { // Standing (true, false, false) => anim::ship::IdleAnimation::update_skeleton( @@ -4234,6 +4240,7 @@ impl FigureMgr { ), }; + #[allow(clippy::match_single_binding)] let target_bones = match &character { // TODO! _ => target_base, From a32be4ac5aa1a7ce022f291ca80a6547999b17bb Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Fri, 12 Mar 2021 18:19:10 -0500 Subject: [PATCH 16/41] Address MR 1888 review comments. - Delete obsolete symbolic links. - Add suggested comments. - Remove dead code. --- assets/voxygen/voxel/object/Human_Airship.vox | 3 - assets/voxygen/voxel/object/airship.vox | 3 - assets/voxygen/voxel/object/propeller-l.vox | 3 - assets/voxygen/voxel/object/propeller-r.vox | 3 - common/src/comp/body/ship.rs | 7 +++ common/sys/src/phys.rs | 4 +- server/src/cmd.rs | 2 +- server/src/rtsim/entity.rs | 4 +- server/src/rtsim/tick.rs | 2 +- server/src/state_ext.rs | 2 + voxygen/src/scene/figure/load.rs | 59 ------------------- 11 files changed, 17 insertions(+), 75 deletions(-) delete mode 120000 assets/voxygen/voxel/object/Human_Airship.vox delete mode 120000 assets/voxygen/voxel/object/airship.vox delete mode 120000 assets/voxygen/voxel/object/propeller-l.vox delete mode 120000 assets/voxygen/voxel/object/propeller-r.vox diff --git a/assets/voxygen/voxel/object/Human_Airship.vox b/assets/voxygen/voxel/object/Human_Airship.vox deleted file mode 120000 index 36e3758d03..0000000000 --- a/assets/voxygen/voxel/object/Human_Airship.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ba02746d73ebf853c0511b673510c09bd47e3ab0fff13d936feb181a8378ebd9 -size 78024 diff --git a/assets/voxygen/voxel/object/airship.vox b/assets/voxygen/voxel/object/airship.vox deleted file mode 120000 index 06bebaa938..0000000000 --- a/assets/voxygen/voxel/object/airship.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86f317298900ea98f95c6a33192b25fbbcbd3ce5f105cad58ad3c595a7a7d9ee -size 70176 diff --git a/assets/voxygen/voxel/object/propeller-l.vox b/assets/voxygen/voxel/object/propeller-l.vox deleted file mode 120000 index a193fa89ee..0000000000 --- a/assets/voxygen/voxel/object/propeller-l.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09ef4bad2557abcc5a2b938f21053babc7770ebe2333039aef9b98ba930b7ec7 -size 1584 diff --git a/assets/voxygen/voxel/object/propeller-r.vox b/assets/voxygen/voxel/object/propeller-r.vox deleted file mode 120000 index 5b940751e6..0000000000 --- a/assets/voxygen/voxel/object/propeller-r.vox +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e4947977524b88bc5adfa934d9061a3499e94b960abb3bcf0a3e2aca482096dc -size 1584 diff --git a/common/src/comp/body/ship.rs b/common/src/comp/body/ship.rs index 4a49d9f2a1..ceab9d86a1 100644 --- a/common/src/comp/body/ship.rs +++ b/common/src/comp/body/ship.rs @@ -22,6 +22,13 @@ impl Body { } } +/// Terrain is 11.0 scale relative to small-scale voxels, and all figures get +/// multiplied by 0.8 in rendering. For now, have a constant in `comp::Scale` +/// that compensates for both of these, but there might be a more elegant way +/// (e.g. using `Scale(0.8)` for everything else and not having a magic number +/// in figure rendering, and multiplying terrain models by 11.0 in animation). +pub const AIRSHIP_SCALE: f32 = 11.0 / 0.8; + /// Duplicate of some of the things defined in `voxygen::scene::figure::load` to /// avoid having to refactor all of that to `common` for using voxels as /// collider geometry diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index e01bcd276f..a73150ebaa 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -475,7 +475,9 @@ impl<'a> PhysicsData<'a> { Collider::Voxel { .. } => { // for now, treat entities with voxel colliders as their bounding // cylinders for the purposes of colliding them with terrain - // Actually no, make them smaller to avoid lag + + // Additionally, multiply radius by 0.1 to make the cylinder smaller to + // avoid lag let radius = collider.get_radius() * scale * 0.1; let (z_min, z_max) = collider.get_z_limits(scale); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 851e8876c8..8ca53368fb 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1009,7 +1009,7 @@ fn handle_spawn_airship( server .state .create_ship(pos, comp::ship::Body::DefaultAirship, 1, destination) - .with(comp::Scale(11.0 / 0.8)) + .with(comp::Scale(comp::ship::AIRSHIP_SCALE)) .with(LightEmitter { col: Rgb::new(1.0, 0.65, 0.2), strength: 2.0, diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index 0803406c55..05b96fcbf2 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -27,7 +27,9 @@ impl Entity { pub fn get_body(&self) -> comp::Body { match self.rng(PERM_GENUS).gen::() { - //we want 5% airships, 45% birds, 50% humans + // we want 5% airships, 45% birds, 50% humans + // TODO: uncomment this to re-enable RtSim airships once physics is interpolated well + // in multiplayer. //x if x < 0.05 => comp::Body::Ship(comp::ship::Body::DefaultAirship), x if x < 0.50 => { let species = *(&comp::bird_medium::ALL_SPECIES) diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs index 30b16f5d1f..13ab05faaf 100644 --- a/server/src/rtsim/tick.rs +++ b/server/src/rtsim/tick.rs @@ -123,7 +123,7 @@ impl<'a> System<'a> for Sys { _ => comp::Alignment::Wild, }, scale: match body { - comp::Body::Ship(_) => comp::Scale(11.0 / 0.8), + comp::Body::Ship(_) => comp::Scale(comp::ship::AIRSHIP_SCALE), _ => comp::Scale(1.0), }, drop_item: None, diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 155b07e286..4e9369a939 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -249,6 +249,8 @@ impl StateExt for State { .with(comp::Controller::default()) .with(comp::inventory::Inventory::new_empty()) .with(comp::CharacterState::default()) + // TODO: some of these are required in order for the character_behavior system to + // recognize a possesed airship; that system should be refactored to use `.maybe()` .with(comp::Energy::new(ship.into(), level)) .with(comp::Health::new(ship.into(), level)) .with(comp::Stats::new("Airship".to_string())) diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 233c77c01c..a04e9c5d16 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -4112,17 +4112,6 @@ impl QuadrupedLowLateralSpec { #[derive(Deserialize)] struct ObjectCentralSpec(HashMap); -/* -#[derive(Deserialize)] -struct ShipCentralSpec(HashMap); - -#[derive(Deserialize)] -struct SidedShipCentralVoxSpec { - bone0: ObjectCentralSubSpec, - bone1: ObjectCentralSubSpec, - bone2: ObjectCentralSubSpec, -}*/ - #[derive(Deserialize)] struct SidedObjectCentralVoxSpec { bone0: ObjectCentralSubSpec, @@ -4193,54 +4182,6 @@ impl ObjectCentralSpec { } } -/*make_vox_spec!( - ship::Body, - struct ShipSpec { - central: ShipCentralSpec = "server.manifests.ship_manifest", - }, - |FigureKey { body, .. }, spec| { - [ - Some(spec.central.read().0.mesh_bone( - body, |spec| &spec.bone0, - )), - Some(spec.central.read().0.mesh_bone( - body, |spec| &spec.bone1 - )), - Some(spec.central.read().0.mesh_bone( - body, |spec| &spec.bone2 - )), - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - None, - ] - }, -); - -impl ShipCentralSpec { - fn mesh_bone &ObjectCentralSubSpec>(&self, obj: &ship::Body, f: F) -> BoneMeshes { - let spec = match self.0.get(&obj) { - Some(spec) => spec, - None => { - error!("No specification exists for {:?}", obj); - return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5)); - }, - }; - let bone = f(spec); - let central = graceful_load_segment(&bone.central.0); - - (central, Vec3::from(bone.offset)) - } -}*/ fn mesh_ship_bone &ShipCentralSubSpec>( map: &HashMap, obj: &K, From b2ab1046c83bd1d4d2050a7c5d6ff92c298f7d95 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 02:24:36 +0000 Subject: [PATCH 17/41] Fewer precision issues by using player-relative coordinate space --- common/sys/src/phys.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index a73150ebaa..e2ff048790 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -632,12 +632,16 @@ impl<'a> PhysicsData<'a> { let mut physics_state_delta = physics_state.clone(); // deliberately don't use scale yet here, because the 11.0/0.8 // thing is in the comp::Scale for visual reasons - let transform_from = Mat4::::translation_3d(pos_other.0) - * Mat4::from(ori_other.0) - * Mat4::::translation_3d(voxel_collider.translation); + let wpos = pos.0; + let transform_from = + Mat4::::translation_3d(pos_other.0 - wpos) + * Mat4::from(ori_other.0) + * Mat4::::translation_3d(voxel_collider.translation); let transform_to = transform_from.inverted(); - pos.0 = transform_to.mul_point(pos.0); - vel.0 = transform_to.mul_direction(vel.0 - vel_other.0); + let ori_to = Mat4::from(ori_other.0); + let ori_from = ori_to.inverted(); + pos.0 = transform_to.mul_point(Vec3::zero()); + vel.0 = ori_to.mul_direction(vel.0 - vel_other.0); let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, @@ -647,20 +651,18 @@ impl<'a> PhysicsData<'a> { transform_to.mul_direction(pos_delta), &mut vel, &mut physics_state_delta, - transform_to.mul_direction(vel_other.0), + ori_to.mul_direction(vel_other.0), &read.dt, false, was_on_ground, |entity, vel| { - land_on_grounds.push(( - entity, - Vel(transform_from.mul_direction(vel.0)), - )) + land_on_grounds + .push((entity, Vel(ori_from.mul_direction(vel.0)))) }, ); - pos.0 = transform_from.mul_point(pos.0); - vel.0 = transform_from.mul_direction(vel.0) + vel_other.0; + pos.0 = transform_from.mul_point(pos.0) + wpos; + vel.0 = ori_from.mul_direction(vel.0) + vel_other.0; // union in the state updates, so that the state isn't just based on // the most recent terrain that collision was attempted with From 7feb4942e3d08fef2a8173d17e7c428c2dfb7237 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 10:52:45 +0000 Subject: [PATCH 18/41] Fixed incorrect wall climb direction on airships --- common/sys/src/phys.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index e2ff048790..80efbf3a74 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -672,7 +672,9 @@ impl<'a> PhysicsData<'a> { physics_state.on_ground |= physics_state_delta.on_ground; physics_state.on_ceiling |= physics_state_delta.on_ceiling; physics_state.on_wall = - physics_state.on_wall.or(physics_state_delta.on_wall); + physics_state.on_wall.or(physics_state_delta + .on_wall + .map(|dir| ori_to.mul_direction(dir))); physics_state .touch_entities .append(&mut physics_state_delta.touch_entities); From a4e5ba06390928f715c08d1bab0cdc77ae00060f Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 12:35:57 +0000 Subject: [PATCH 19/41] Fixed incorrectly reversed orientation matrix --- common/sys/src/phys.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 80efbf3a74..212f3e119a 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -638,8 +638,8 @@ impl<'a> PhysicsData<'a> { * Mat4::from(ori_other.0) * Mat4::::translation_3d(voxel_collider.translation); let transform_to = transform_from.inverted(); - let ori_to = Mat4::from(ori_other.0); - let ori_from = ori_to.inverted(); + let ori_from = Mat4::from(ori_other.0); + let ori_to = ori_from.inverted(); pos.0 = transform_to.mul_point(Vec3::zero()); vel.0 = ori_to.mul_direction(vel.0 - vel_other.0); let cylinder = (radius, z_min, z_max); @@ -674,7 +674,7 @@ impl<'a> PhysicsData<'a> { physics_state.on_wall = physics_state.on_wall.or(physics_state_delta .on_wall - .map(|dir| ori_to.mul_direction(dir))); + .map(|dir| ori_from.mul_direction(dir))); physics_state .touch_entities .append(&mut physics_state_delta.touch_entities); @@ -1042,8 +1042,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( } if physics_state.on_ground { - vel.0 = ground_vel * 0.0 - + (vel.0 - ground_vel * 0.0) * (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); + vel.0 *= (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); physics_state.ground_vel = ground_vel; } From 6d8ba31e5b1c6ae4e224506c2b6bc0264fa07f63 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 14:25:56 +0000 Subject: [PATCH 20/41] Reduced air resistance for better-behaving airships --- common/src/states/glide.rs | 4 ++-- common/src/states/utils.rs | 9 ++++++--- common/sys/src/phys.rs | 19 ++++++++++--------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/common/src/states/glide.rs b/common/src/states/glide.rs index a54046e38e..8a6ac909b9 100644 --- a/common/src/states/glide.rs +++ b/common/src/states/glide.rs @@ -9,8 +9,8 @@ use vek::Vec2; // Gravity is 9.81 * 4, so this makes gravity equal to .15 const GLIDE_ANTIGRAV: f32 = crate::consts::GRAVITY * 0.90; -const GLIDE_ACCEL: f32 = 12.0; -const GLIDE_SPEED: f32 = 45.0; +const GLIDE_ACCEL: f32 = 8.0; +const GLIDE_SPEED: f32 = 16.0; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] pub struct Data; diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 6ee2449950..f256559283 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -17,8 +17,8 @@ use std::time::Duration; use vek::*; pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0; -const BASE_HUMANOID_AIR_ACCEL: f32 = 8.0; -const BASE_FLIGHT_ACCEL: f32 = 16.0; +const BASE_HUMANOID_AIR_ACCEL: f32 = 0.5; +const BASE_FLIGHT_ACCEL: f32 = 3.5; const BASE_HUMANOID_WATER_ACCEL: f32 = 150.0; const BASE_HUMANOID_WATER_SPEED: f32 = 180.0; // const BASE_HUMANOID_CLIMB_ACCEL: f32 = 10.0; @@ -310,7 +310,10 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth: fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { // Update velocity (counteract gravity with lift) // TODO: Do this better - update.vel.0 += Vec3::unit_z() * data.dt.0 * GRAVITY + // A loss factor is needed to counteract the very slight deviation in gravity + // due to precision issues + const LOSS_FACTOR: f32 = 0.995; + update.vel.0 += Vec3::unit_z() * data.dt.0 * GRAVITY * LOSS_FACTOR + Vec3::new( data.inputs.move_dir.x, data.inputs.move_dir.y, diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 212f3e119a..a2e3c6b916 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -30,7 +30,7 @@ pub const BOUYANCY: f32 = 1.0; // friction is 0.01, and the speed is 1.0, then after 1/60th of a second the // speed will be 0.99. after 1 second the speed will be 0.54, which is 0.99 ^ // 60. -pub const FRIC_AIR: f32 = 0.0125; +pub const FRIC_AIR: f32 = 0.0025; pub const FRIC_FLUID: f32 = 0.4; // Integrates forces, calculates the new velocity based off of the old velocity @@ -415,7 +415,7 @@ impl<'a> PhysicsData<'a> { let mut pos = *pos; let mut vel = *vel; if sticky.is_some() && physics_state.on_surface().is_some() { - vel.0 = Vec3::zero(); + vel.0 = physics_state.ground_vel; return (pos_writes, vel_writes, land_on_grounds); } @@ -977,7 +977,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( { // ...block-hop! pos.0.z = (pos.0.z + 0.1).floor() + block_height; - vel.0.z = vel.0.z.max(0.0); + vel.0.z = 0.0; on_ground = true; break; } else { @@ -1019,7 +1019,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( near_iter.clone(), radius, z_range.clone(), - ) && vel.0.z < 0.1 + ) && vel.0.z < 0.25 && vel.0.z > -1.5 // && was_on_ground // && !collision_with( @@ -1037,15 +1037,11 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( .filter(|block| block.is_solid()) .map(|block| block.solid_height()) .unwrap_or(0.0); + vel.0.z = 0.0; pos.0.z = (pos.0.z - 0.1).floor() + snap_height; physics_state.on_ground = true; } - if physics_state.on_ground { - vel.0 *= (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); - physics_state.ground_vel = ground_vel; - } - let dirs = [ Vec3::unit_x(), Vec3::unit_y(), @@ -1072,6 +1068,11 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( physics_state.on_wall = None; } + if physics_state.on_ground || physics_state.on_wall.is_some() { + vel.0 *= (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); + physics_state.ground_vel = ground_vel; + } + // Figure out if we're in water physics_state.in_liquid = collision_iter( pos.0, From 44f9932450fca2a7f43e9865888f37f720b2680c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 15:36:56 +0000 Subject: [PATCH 21/41] Properly propagate velocity steps during airship collision to avoid falling through airships --- common/src/states/glide.rs | 12 +++------ common/src/states/utils.rs | 4 +-- common/sys/src/phys.rs | 50 ++++++++++++++++++++++---------------- 3 files changed, 34 insertions(+), 32 deletions(-) diff --git a/common/src/states/glide.rs b/common/src/states/glide.rs index 8a6ac909b9..6655e5b440 100644 --- a/common/src/states/glide.rs +++ b/common/src/states/glide.rs @@ -9,7 +9,7 @@ use vek::Vec2; // Gravity is 9.81 * 4, so this makes gravity equal to .15 const GLIDE_ANTIGRAV: f32 = crate::consts::GRAVITY * 0.90; -const GLIDE_ACCEL: f32 = 8.0; +const GLIDE_ACCEL: f32 = 6.0; const GLIDE_SPEED: f32 = 16.0; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] @@ -40,13 +40,7 @@ impl CharacterBehavior for Data { handle_climb(&data, &mut update); // Move player according to movement direction vector - update.vel.0 += Vec2::broadcast(data.dt.0) - * data.inputs.move_dir - * if data.vel.0.magnitude_squared() < GLIDE_SPEED.powi(2) { - GLIDE_ACCEL - } else { - 0.0 - }; + update.vel.0 += Vec2::broadcast(data.dt.0) * data.inputs.move_dir * GLIDE_ACCEL; // Determine orientation vector from movement direction vector let horiz_vel = Vec2::::from(update.vel.0); @@ -56,7 +50,7 @@ impl CharacterBehavior for Data { // Apply Glide antigrav lift let horiz_speed_sq = horiz_vel.magnitude_squared(); - if horiz_speed_sq < GLIDE_SPEED.powi(2) && update.vel.0.z < 0.0 { + if update.vel.0.z < 0.0 { let lift = (GLIDE_ANTIGRAV + update.vel.0.z.powi(2) * 0.15) * (horiz_speed_sq * f32::powf(0.075, 2.0)).clamp(0.2, 1.0); diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index f256559283..fda66fb758 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -17,8 +17,8 @@ use std::time::Duration; use vek::*; pub const MOVEMENT_THRESHOLD_VEL: f32 = 3.0; -const BASE_HUMANOID_AIR_ACCEL: f32 = 0.5; -const BASE_FLIGHT_ACCEL: f32 = 3.5; +const BASE_HUMANOID_AIR_ACCEL: f32 = 2.0; +const BASE_FLIGHT_ACCEL: f32 = 2.0; const BASE_HUMANOID_WATER_ACCEL: f32 = 150.0; const BASE_HUMANOID_WATER_SPEED: f32 = 180.0; // const BASE_HUMANOID_CLIMB_ACCEL: f32 = 10.0; diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index a2e3c6b916..53f9061e3f 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -411,7 +411,6 @@ impl<'a> PhysicsData<'a> { )| { // defer the writes of positions to allow an inner loop over terrain-like // entities - let old_pos = *pos; let mut pos = *pos; let mut vel = *vel; if sticky.is_some() && physics_state.on_surface().is_some() { @@ -469,6 +468,8 @@ impl<'a> PhysicsData<'a> { Vec3::zero() }; + let mut tgt_pos = pos.0 + pos_delta; + let was_on_ground = physics_state.on_ground; match &collider { @@ -481,21 +482,22 @@ impl<'a> PhysicsData<'a> { let radius = collider.get_radius() * scale * 0.1; let (z_min, z_max) = collider.get_z_limits(scale); + let mut cpos = pos; let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, &*read.terrain, entity, - &mut pos, - pos_delta, + &mut cpos, + tgt_pos, &mut vel, &mut physics_state, Vec3::zero(), &read.dt, - true, was_on_ground, |entity, vel| land_on_grounds.push((entity, vel)), ); + tgt_pos = cpos.0; }, Collider::Box { radius, @@ -508,20 +510,21 @@ impl<'a> PhysicsData<'a> { let z_max = z_max.clamped(1.2, 1.95) * scale; let cylinder = (radius, z_min, z_max); + let mut cpos = pos; cylinder_voxel_collision( cylinder, &*read.terrain, entity, - &mut pos, - pos_delta, + &mut cpos, + tgt_pos, &mut vel, &mut physics_state, Vec3::zero(), &read.dt, - true, was_on_ground, |entity, vel| land_on_grounds.push((entity, vel)), ); + tgt_pos = cpos.0; }, Collider::Point => { let (dist, block) = read @@ -567,6 +570,8 @@ impl<'a> PhysicsData<'a> { .get(pos.0.map(|e| e.floor() as i32)) .ok() .and_then(|vox| vox.is_liquid().then_some(1.0)); + + tgt_pos = pos.0; }, } @@ -632,7 +637,8 @@ impl<'a> PhysicsData<'a> { let mut physics_state_delta = physics_state.clone(); // deliberately don't use scale yet here, because the 11.0/0.8 // thing is in the comp::Scale for visual reasons - let wpos = pos.0; + let mut cpos = pos; + let wpos = cpos.0; let transform_from = Mat4::::translation_3d(pos_other.0 - wpos) * Mat4::from(ori_other.0) @@ -640,20 +646,19 @@ impl<'a> PhysicsData<'a> { let transform_to = transform_from.inverted(); let ori_from = Mat4::from(ori_other.0); let ori_to = ori_from.inverted(); - pos.0 = transform_to.mul_point(Vec3::zero()); + cpos.0 = transform_to.mul_point(Vec3::zero()); vel.0 = ori_to.mul_direction(vel.0 - vel_other.0); let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, &voxel_collider.dyna, entity, - &mut pos, - transform_to.mul_direction(pos_delta), + &mut cpos, + transform_to.mul_point(tgt_pos - wpos), &mut vel, &mut physics_state_delta, ori_to.mul_direction(vel_other.0), &read.dt, - false, was_on_ground, |entity, vel| { land_on_grounds @@ -661,8 +666,9 @@ impl<'a> PhysicsData<'a> { }, ); - pos.0 = transform_from.mul_point(pos.0) + wpos; + cpos.0 = transform_from.mul_point(cpos.0) + wpos; vel.0 = ori_from.mul_direction(vel.0) + vel_other.0; + tgt_pos = cpos.0; // union in the state updates, so that the state isn't just based on // the most recent terrain that collision was attempted with @@ -691,8 +697,9 @@ impl<'a> PhysicsData<'a> { } } } - if pos != old_pos { - pos_writes.push((entity, pos)); + + if tgt_pos != pos.0 { + pos_writes.push((entity, Pos(tgt_pos))); } if vel != old_vel { vel_writes.push((entity, vel)); @@ -771,12 +778,11 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( terrain: &'a T, entity: Entity, pos: &mut Pos, - mut pos_delta: Vec3, + tgt_pos: Vec3, vel: &mut Vel, physics_state: &mut PhysicsState, ground_vel: Vec3, dt: &DeltaTime, - apply_velocity_step: bool, // Stupid hack was_on_ground: bool, mut land_on_ground: impl FnMut(Entity, Vel), ) { @@ -859,6 +865,8 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( let mut on_ceiling = false; let mut attempts = 0; // Don't loop infinitely here + let mut pos_delta = tgt_pos - pos.0; + // Don't jump too far at once let increments = (pos_delta.map(|e| e.abs()).reduce_partial_max() / 0.3) .ceil() @@ -866,9 +874,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( let old_pos = pos.0; fn block_true(_: &Block) -> bool { true } for _ in 0..increments as usize { - if apply_velocity_step { - pos.0 += pos_delta / increments; - } + pos.0 += pos_delta / increments; const MAX_ATTEMPTS: usize = 16; @@ -1069,7 +1075,9 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( } if physics_state.on_ground || physics_state.on_wall.is_some() { - vel.0 *= (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); + if physics_state.on_ground { + vel.0 *= (1.0 - FRIC_GROUND.min(1.0)).powf(dt.0 * 60.0); + } physics_state.ground_vel = ground_vel; } From 9424998e6ecc245bfd0106584c05a772ef9436dd Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 16:14:13 +0000 Subject: [PATCH 22/41] Fixed arrows --- common/sys/src/phys.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 53f9061e3f..041ad1b992 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -468,6 +468,18 @@ impl<'a> PhysicsData<'a> { Vec3::zero() }; + // What's going on here? Because collisions need to be resolved against multiple + // colliders, this code takes the current position and + // propagates it forward according to velocity to find a + // 'target' position. This is where we'd ideally end up at the end of the tick, + // assuming no collisions. Then, we refine this target by + // stepping from the original position to the target for + // every obstacle, refining the target position as we go. It's not perfect, but + // it works pretty well in practice. Oddities can occur on + // the intersection between multiple colliders, but it's not + // like any game physics system 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; let was_on_ground = physics_state.on_ground; @@ -527,6 +539,8 @@ impl<'a> PhysicsData<'a> { tgt_pos = cpos.0; }, Collider::Point => { + let mut pos = pos; + let (dist, block) = read .terrain .ray(pos.0, pos.0 + pos_delta) @@ -966,11 +980,12 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( && resolve_dir.z == 0.0 // ...and the vertical resolution direction is sufficiently great... && -dir.z > 0.1 - // ...and we're falling/standing OR there is a block *directly* beneath our current origin (note: not hitbox)... - && (vel.0.z <= 0.0 || terrain - .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) - .map(|block| block.is_solid()) - .unwrap_or(false)) + && was_on_ground + // // ...and we're falling/standing OR there is a block *directly* beneath our current origin (note: not hitbox)... + // && (vel.0.z <= 0.0 || terrain + // .get((pos.0 - Vec3::unit_z() * 0.1).map(|e| e.floor() as i32)) + // .map(|block| block.is_solid()) + // .unwrap_or(false)) // ...and there is a collision with a block beneath our current hitbox... && collision_with( pos.0 + resolve_dir - Vec3::unit_z() * 1.05, From 1af86ef2f85748689906c6da24133e3c158c7b3c Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 16:43:01 +0000 Subject: [PATCH 23/41] Removed unnecessary matrix mul --- common/src/states/glide.rs | 3 +-- common/sys/src/phys.rs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/common/src/states/glide.rs b/common/src/states/glide.rs index 6655e5b440..04bfbb8fe6 100644 --- a/common/src/states/glide.rs +++ b/common/src/states/glide.rs @@ -9,8 +9,7 @@ use vek::Vec2; // Gravity is 9.81 * 4, so this makes gravity equal to .15 const GLIDE_ANTIGRAV: f32 = crate::consts::GRAVITY * 0.90; -const GLIDE_ACCEL: f32 = 6.0; -const GLIDE_SPEED: f32 = 16.0; +const GLIDE_ACCEL: f32 = 5.0; #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)] pub struct Data; diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 041ad1b992..a7e10c92d1 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -411,7 +411,6 @@ impl<'a> PhysicsData<'a> { )| { // defer the writes of positions to allow an inner loop over terrain-like // entities - let mut pos = *pos; let mut vel = *vel; if sticky.is_some() && physics_state.on_surface().is_some() { vel.0 = physics_state.ground_vel; @@ -494,7 +493,7 @@ impl<'a> PhysicsData<'a> { let radius = collider.get_radius() * scale * 0.1; let (z_min, z_max) = collider.get_z_limits(scale); - let mut cpos = pos; + let mut cpos = *pos; let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, @@ -522,7 +521,7 @@ impl<'a> PhysicsData<'a> { let z_max = z_max.clamped(1.2, 1.95) * scale; let cylinder = (radius, z_min, z_max); - let mut cpos = pos; + let mut cpos = *pos; cylinder_voxel_collision( cylinder, &*read.terrain, @@ -539,7 +538,7 @@ impl<'a> PhysicsData<'a> { tgt_pos = cpos.0; }, Collider::Point => { - let mut pos = pos; + let mut pos = *pos; let (dist, block) = read .terrain @@ -651,7 +650,7 @@ impl<'a> PhysicsData<'a> { let mut physics_state_delta = physics_state.clone(); // deliberately don't use scale yet here, because the 11.0/0.8 // thing is in the comp::Scale for visual reasons - let mut cpos = pos; + let mut cpos = *pos; let wpos = cpos.0; let transform_from = Mat4::::translation_3d(pos_other.0 - wpos) @@ -691,10 +690,11 @@ impl<'a> PhysicsData<'a> { } physics_state.on_ground |= physics_state_delta.on_ground; physics_state.on_ceiling |= physics_state_delta.on_ceiling; - physics_state.on_wall = - physics_state.on_wall.or(physics_state_delta + physics_state.on_wall = physics_state.on_wall.or_else(|| { + physics_state_delta .on_wall - .map(|dir| ori_from.mul_direction(dir))); + .map(|dir| ori_from.mul_direction(dir)) + }); physics_state .touch_entities .append(&mut physics_state_delta.touch_entities); From 71c07734f752223113afe4ef51e8a9d91572731d Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 16:54:14 +0000 Subject: [PATCH 24/41] Slightly increase block-hop height for better airship block-hopping --- common/sys/src/phys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index a7e10c92d1..1d3c008ade 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -988,7 +988,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( // .unwrap_or(false)) // ...and there is a collision with a block beneath our current hitbox... && collision_with( - pos.0 + resolve_dir - Vec3::unit_z() * 1.05, + pos.0 + resolve_dir - Vec3::unit_z() * 1.25, &terrain, block_true, near_iter.clone(), From b8a27f493cbfd231d66feaa4bbd7060f8dc4ac6b Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 19:40:00 +0000 Subject: [PATCH 25/41] Initial pass for gravity and air resistance for more stable physics --- common/sys/src/phys.rs | 88 ++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 1d3c008ade..d4d4614484 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -377,10 +377,59 @@ impl<'a> PhysicsData<'a> { span!(guard, "Apply movement and terrain collision"); let (positions, velocities, previous_phys_cache, orientations) = ( &write.positions, - &write.velocities, + &mut write.velocities, &write.previous_phys_cache, &write.orientations, ); + + // First pass: update velocity using air resistance and gravity for each entity. + // We do this in a first pass because it helps keep things more stable for + // entities that are anchored to other entities (such as airships). + ( + &read.entities, + positions, + velocities, + &write.physics_states, + !&read.mountings, + ) + .par_join() + .for_each(|(entity, pos, vel, physics_state, _)| { + let in_loaded_chunk = read + .terrain + .get_key(read.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) + .is_some(); + // Integrate forces + // Friction is assumed to be a constant dependent on location + let friction = if physics_state.on_ground { 0.0 } else { FRIC_AIR } + // .max(if physics_state.on_ground { + // FRIC_GROUND + // } else { + // 0.0 + // }) + .max(if physics_state.in_liquid.is_some() { + FRIC_FLUID + } else { + 0.0 + }); + let downward_force = + if !in_loaded_chunk { + 0.0 // No gravity in unloaded chunks + } else if physics_state + .in_liquid + .map(|depth| depth > 0.75) + .unwrap_or(false) + { + (1.0 - BOUYANCY) * GRAVITY + } else { + GRAVITY + } * read.gravities.get(entity).map(|g| g.0).unwrap_or_default(); + + vel.0 = integrate_forces(read.dt.0, vel.0, downward_force, friction); + }); + + let velocities = &write.velocities; + + // Second pass: resolve collisions let (pos_writes, vel_writes, land_on_grounds) = ( &read.entities, read.scales.maybe(), @@ -409,9 +458,10 @@ impl<'a> PhysicsData<'a> { _previous_cache, _, )| { - // defer the writes of positions to allow an inner loop over terrain-like - // entities + // defer the writes of positions and velocities to allow an inner loop over + // terrain-like entities let mut vel = *vel; + let old_vel = vel; if sticky.is_some() && physics_state.on_surface().is_some() { vel.0 = physics_state.ground_vel; return (pos_writes, vel_writes, land_on_grounds); @@ -425,44 +475,14 @@ impl<'a> PhysicsData<'a> { 1.0 }; - let old_vel = vel; - // Integrate forces - // Friction is assumed to be a constant dependent on location - let friction = if physics_state.on_ground { 0.0 } else { FRIC_AIR } - // .max(if physics_state.on_ground { - // FRIC_GROUND - // } else { - // 0.0 - // }) - .max(if physics_state.in_liquid.is_some() { - FRIC_FLUID - } else { - 0.0 - }); let in_loaded_chunk = read .terrain .get_key(read.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) .is_some(); - let downward_force = - if !in_loaded_chunk { - 0.0 // No gravity in unloaded chunks - } else if physics_state - .in_liquid - .map(|depth| depth > 0.75) - .unwrap_or(false) - { - (1.0 - BOUYANCY) * GRAVITY - } else { - GRAVITY - } * read.gravities.get(entity).map(|g| g.0).unwrap_or_default(); - vel.0 = integrate_forces(read.dt.0, vel.0, downward_force, friction); // Don't move if we're not in a loaded chunk let pos_delta = if in_loaded_chunk { - // this is an approximation that allows most framerates to - // behave in a similar manner. - let dt_lerp = 0.2; - (vel.0 * dt_lerp + old_vel.0 * (1.0 - dt_lerp)) * read.dt.0 + vel.0 * read.dt.0 } else { Vec3::zero() }; From 5bc9f894ab3067f1a9dd2660e3ebccce4f8e8fbe Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 19:52:54 +0000 Subject: [PATCH 26/41] Fixed airship manifest offset --- assets/server/manifests/ship_manifest.ron | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/server/manifests/ship_manifest.ron b/assets/server/manifests/ship_manifest.ron index 6b361aa535..817d579120 100644 --- a/assets/server/manifests/ship_manifest.ron +++ b/assets/server/manifests/ship_manifest.ron @@ -4,7 +4,7 @@ //offset: (3.0, 7.0, 1.0), //offset: (-20.75, -34.75, 1.25), //offset: (0.0, 0.0, 0.0), - offset: (-20.0, -35.0, 1.0), + offset: (-17.5, -35.0, 1.0), //phys_offset: (0.25, 0.25, 0.25), phys_offset: (0.0, 0.0, 0.0), central: ("Human_Airship"), From 8247c136bc44fdbae232afc26f60f08918656aff Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 20:05:02 +0000 Subject: [PATCH 27/41] Prevented jumping airships --- common/src/states/utils.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index fda66fb758..3a82acb977 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -181,6 +181,13 @@ impl Body { } } + pub fn can_jump(&self) -> bool { + match self { + Body::Object(_) | Body::Ship(_) => false, + _ => true, + } + } + pub fn can_climb(&self) -> bool { matches!(self, Body::Humanoid(_)) } } @@ -435,6 +442,7 @@ pub fn handle_jump(data: &JoinData, update: &mut StateUpdate) { .in_liquid .map(|depth| depth > 1.0) .unwrap_or(false) + && data.body.can_jump() { update .local_events From e232a2e473e7c2021274576c789cae6216db1916 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 20:51:32 +0000 Subject: [PATCH 28/41] Fixed jumping inconsistencies, no block-snapping for ships --- common/src/event.rs | 7 +------ common/src/states/climb.rs | 2 +- common/src/states/utils.rs | 16 +++++++++------- common/sys/src/phys.rs | 24 ++++++++++++------------ common/sys/src/state.rs | 22 ++-------------------- 5 files changed, 25 insertions(+), 46 deletions(-) diff --git a/common/src/event.rs b/common/src/event.rs index 21c8bc33af..c74ec46931 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -20,17 +20,12 @@ pub type SiteId = u64; pub enum LocalEvent { /// Applies upward force to entity's `Vel` - Jump(EcsEntity), + Jump(EcsEntity, f32), /// Applies the `impulse` to `entity`'s `Vel` ApplyImpulse { entity: EcsEntity, impulse: Vec3, }, - /// Applies leaping force to `entity`'s `Vel` away from `wall_dir` direction - WallLeap { - entity: EcsEntity, - wall_dir: Vec3, - }, /// Applies `vel` velocity to `entity` Boost { entity: EcsEntity, vel: Vec3 }, } diff --git a/common/src/states/climb.rs b/common/src/states/climb.rs index 51a5a176d5..cb97013ca9 100644 --- a/common/src/states/climb.rs +++ b/common/src/states/climb.rs @@ -36,7 +36,7 @@ impl CharacterBehavior for Data { // They've climbed atop something, give them a boost update .local_events - .push_front(LocalEvent::Jump(data.entity)); + .push_front(LocalEvent::Jump(data.entity, BASE_JUMP_IMPULSE * 0.5)); } update.character = CharacterState::Idle {}; return update; diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 3a82acb977..a9097aa08e 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -21,6 +21,7 @@ const BASE_HUMANOID_AIR_ACCEL: f32 = 2.0; const BASE_FLIGHT_ACCEL: f32 = 2.0; const BASE_HUMANOID_WATER_ACCEL: f32 = 150.0; const BASE_HUMANOID_WATER_SPEED: f32 = 180.0; +pub const BASE_JUMP_IMPULSE: f32 = 16.0; // const BASE_HUMANOID_CLIMB_ACCEL: f32 = 10.0; // const ROLL_SPEED: f32 = 17.0; // const CHARGE_SPEED: f32 = 20.0; @@ -181,10 +182,10 @@ impl Body { } } - pub fn can_jump(&self) -> bool { + pub fn jump_impulse(&self) -> Option { match self { - Body::Object(_) | Body::Ship(_) => false, - _ => true, + Body::Object(_) | Body::Ship(_) => None, + _ => Some(BASE_JUMP_IMPULSE), } } @@ -442,11 +443,12 @@ pub fn handle_jump(data: &JoinData, update: &mut StateUpdate) { .in_liquid .map(|depth| depth > 1.0) .unwrap_or(false) - && data.body.can_jump() + && data.body.jump_impulse().is_some() { - update - .local_events - .push_front(LocalEvent::Jump(data.entity)); + update.local_events.push_front(LocalEvent::Jump( + data.entity, + data.body.jump_impulse().unwrap(), + )); } } diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index d4d4614484..9c73962b31 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -1,8 +1,8 @@ use common::{ comp::{ - body::ship::figuredata::VOXEL_COLLIDER_MANIFEST, BeamSegment, CharacterState, Collider, - Gravity, Mass, Mounting, Ori, PhysicsState, Pos, PreviousPhysCache, Projectile, Scale, - Shockwave, Sticky, Vel, + body::ship::figuredata::VOXEL_COLLIDER_MANIFEST, BeamSegment, Body, CharacterState, + Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, PreviousPhysCache, Projectile, + Scale, Shockwave, Sticky, Vel, }, consts::{FRIC_GROUND, GRAVITY}, event::{EventBus, ServerEvent}, @@ -85,6 +85,7 @@ pub struct PhysicsRead<'a> { beams: ReadStorage<'a, BeamSegment>, shockwaves: ReadStorage<'a, Shockwave>, char_states: ReadStorage<'a, CharacterState>, + bodies: ReadStorage<'a, Body>, } #[derive(SystemData)] @@ -438,6 +439,7 @@ impl<'a> PhysicsData<'a> { positions, velocities, orientations, + read.bodies.maybe(), &mut write.physics_states, previous_phys_cache, !&read.mountings, @@ -454,6 +456,7 @@ impl<'a> PhysicsData<'a> { pos, vel, _ori, + body, mut physics_state, _previous_cache, _, @@ -502,6 +505,7 @@ impl<'a> PhysicsData<'a> { let mut tgt_pos = pos.0 + pos_delta; let was_on_ground = physics_state.on_ground; + let block_snap = body.map_or(false, |body| body.jump_impulse().is_some()); match &collider { Collider::Voxel { .. } => { @@ -526,6 +530,7 @@ impl<'a> PhysicsData<'a> { Vec3::zero(), &read.dt, was_on_ground, + block_snap, |entity, vel| land_on_grounds.push((entity, vel)), ); tgt_pos = cpos.0; @@ -553,6 +558,7 @@ impl<'a> PhysicsData<'a> { Vec3::zero(), &read.dt, was_on_ground, + block_snap, |entity, vel| land_on_grounds.push((entity, vel)), ); tgt_pos = cpos.0; @@ -693,6 +699,7 @@ impl<'a> PhysicsData<'a> { ori_to.mul_direction(vel_other.0), &read.dt, was_on_ground, + block_snap, |entity, vel| { land_on_grounds .push((entity, Vel(ori_from.mul_direction(vel.0)))) @@ -818,6 +825,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( ground_vel: Vec3, dt: &DeltaTime, was_on_ground: bool, + block_snap: bool, mut land_on_ground: impl FnMut(Entity, Vel), ) { let (radius, z_min, z_max) = cylinder; @@ -1062,15 +1070,7 @@ fn cylinder_voxel_collision<'a, T: BaseVol + ReadVol>( z_range.clone(), ) && vel.0.z < 0.25 && vel.0.z > -1.5 - // && was_on_ground - // && !collision_with( - // pos.0 - Vec3::unit_z() * 0.0, - // &terrain, - // |block| block.solid_height() >= (pos.0.z - 0.1).rem_euclid(1.0), - // near_iter.clone(), - // radius, - // z_range.clone(), - // ) + && block_snap { let snap_height = terrain .get(Vec3::new(pos.0.x, pos.0.y, pos.0.z - 0.1).map(|e| e.floor() as i32)) diff --git a/common/sys/src/state.rs b/common/sys/src/state.rs index 8aae465bfd..a3d222ecdc 100644 --- a/common/sys/src/state.rs +++ b/common/sys/src/state.rs @@ -36,7 +36,6 @@ const DAY_CYCLE_FACTOR: f64 = 24.0 * 2.0; /// this value, the game's physics will begin to produce time lag. Ideally, we'd /// avoid such a situation. const MAX_DELTA_TIME: f32 = 1.0; -const HUMANOID_JUMP_ACCEL: f32 = 16.0; #[derive(Default)] pub struct BlockChange { @@ -454,13 +453,11 @@ impl State { let events = self.ecs.read_resource::>().recv_all(); for event in events { let mut velocities = self.ecs.write_storage::(); - let mut controllers = self.ecs.write_storage::(); let physics = self.ecs.read_storage::(); match event { - LocalEvent::Jump(entity) => { + LocalEvent::Jump(entity, impulse) => { if let Some(vel) = velocities.get_mut(entity) { - vel.0.z = HUMANOID_JUMP_ACCEL - + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z); + vel.0.z = impulse + physics.get(entity).map_or(0.0, |ps| ps.ground_vel.z); } }, LocalEvent::ApplyImpulse { entity, impulse } => { @@ -468,21 +465,6 @@ impl State { vel.0 = impulse; } }, - LocalEvent::WallLeap { entity, wall_dir } => { - if let (Some(vel), Some(_controller)) = - (velocities.get_mut(entity), controllers.get_mut(entity)) - { - let hspeed = Vec2::::from(vel.0).magnitude(); - if hspeed > 0.001 && hspeed < 0.5 { - vel.0 += vel.0.normalized() - * Vec3::new(1.0, 1.0, 0.0) - * HUMANOID_JUMP_ACCEL - * 1.5 - - wall_dir * 0.03; - vel.0.z = HUMANOID_JUMP_ACCEL * 0.5; - } - } - }, LocalEvent::Boost { entity, vel: extra_vel, From f6c631995250a03e28cee343310fa0cd63895059 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Sat, 13 Mar 2021 22:17:53 +0000 Subject: [PATCH 29/41] Made entities rotate smoothly with airships, fixed rotation speeds --- common/src/comp/phys.rs | 1 + common/src/states/utils.rs | 10 +++++++--- common/sys/src/phys.rs | 33 ++++++++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/common/src/comp/phys.rs b/common/src/comp/phys.rs index 33729f28ad..cddf4832f8 100644 --- a/common/src/comp/phys.rs +++ b/common/src/comp/phys.rs @@ -33,6 +33,7 @@ pub struct PreviousPhysCache { pub collision_boundary: f32, pub scale: f32, pub scaled_radius: f32, + pub ori: Quaternion, } impl Component for PreviousPhysCache { diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index a9097aa08e..1b74af1a55 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -170,7 +170,7 @@ impl Body { quadruped_low::Species::Lavadrake => 4.0, _ => 6.0, }, - Body::Ship(_) => 0.5, + Body::Ship(_) => 0.175, } } @@ -300,7 +300,11 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth: } * efficiency; - handle_orientation(data, update, if data.physics.on_ground { 9.0 } else { 2.0 }); + handle_orientation( + data, + update, + data.body.base_ori_rate() * if data.physics.on_ground { 0.5 } else { 0.1 }, + ); // Swim update.vel.0.z = (update.vel.0.z @@ -330,7 +334,7 @@ fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) { * BASE_FLIGHT_ACCEL * efficiency; - handle_orientation(data, update, 1.0); + handle_orientation(data, update, data.body.base_ori_rate()); } /// Checks if an input related to an attack is held. If one is, moves entity diff --git a/common/sys/src/phys.rs b/common/sys/src/phys.rs index 9c73962b31..e5577546ec 100644 --- a/common/sys/src/phys.rs +++ b/common/sys/src/phys.rs @@ -151,6 +151,7 @@ impl<'a> PhysicsData<'a> { collision_boundary: 0.0, scale: 0.0, scaled_radius: 0.0, + ori: Quaternion::identity(), }); } @@ -620,7 +621,7 @@ impl<'a> PhysicsData<'a> { _other, pos_other, vel_other, - _previous_cache_other, + previous_cache_other, _mass_other, collider_other, ori_other, @@ -678,6 +679,9 @@ impl<'a> PhysicsData<'a> { // thing is in the comp::Scale for visual reasons let mut cpos = *pos; let wpos = cpos.0; + + // TODO: Cache the matrices here to avoid recomputing + let transform_from = Mat4::::translation_3d(pos_other.0 - wpos) * Mat4::from(ori_other.0) @@ -685,8 +689,21 @@ impl<'a> PhysicsData<'a> { let transform_to = transform_from.inverted(); let ori_from = Mat4::from(ori_other.0); let ori_to = ori_from.inverted(); + + // The velocity of the collider, taking into account orientation. + let wpos_rel = (Mat4::::translation_3d(pos_other.0) + * Mat4::from(ori_other.0) + * Mat4::::translation_3d(voxel_collider.translation)) + .inverted() + .mul_point(wpos); + let wpos_last = (Mat4::::translation_3d(pos_other.0) + * Mat4::from(previous_cache_other.ori) + * Mat4::::translation_3d(voxel_collider.translation)) + .mul_point(wpos_rel); + let vel_other = vel_other.0 + (wpos - wpos_last) / read.dt.0; + cpos.0 = transform_to.mul_point(Vec3::zero()); - vel.0 = ori_to.mul_direction(vel.0 - vel_other.0); + vel.0 = ori_to.mul_direction(vel.0 - vel_other); let cylinder = (radius, z_min, z_max); cylinder_voxel_collision( cylinder, @@ -696,7 +713,7 @@ impl<'a> PhysicsData<'a> { transform_to.mul_point(tgt_pos - wpos), &mut vel, &mut physics_state_delta, - ori_to.mul_direction(vel_other.0), + ori_to.mul_direction(vel_other), &read.dt, was_on_ground, block_snap, @@ -707,13 +724,13 @@ impl<'a> PhysicsData<'a> { ); cpos.0 = transform_from.mul_point(cpos.0) + wpos; - vel.0 = ori_from.mul_direction(vel.0) + vel_other.0; + vel.0 = ori_from.mul_direction(vel.0) + vel_other; tgt_pos = cpos.0; // union in the state updates, so that the state isn't just based on // the most recent terrain that collision was attempted with if physics_state_delta.on_ground { - physics_state.ground_vel = vel_other.0; + physics_state.ground_vel = vel_other; } physics_state.on_ground |= physics_state_delta.on_ground; physics_state.on_ceiling |= physics_state_delta.on_ceiling; @@ -776,6 +793,12 @@ impl<'a> PhysicsData<'a> { } } + for (ori, previous_phys_cache) in + (&write.orientations, &mut write.previous_phys_cache).join() + { + previous_phys_cache.ori = ori.0; + } + let mut event_emitter = read.event_bus.emitter(); land_on_grounds.into_iter().for_each(|(entity, vel)| { event_emitter.emit(ServerEvent::LandOnGround { entity, vel: vel.0 }); From a71bacdce093272eb0b5bc75b2d123632b079ab4 Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Fri, 12 Mar 2021 21:53:19 -0500 Subject: [PATCH 30/41] Add machinery for storing component buffers for interpolation. --- Cargo.lock | 1 + common/net/Cargo.toml | 3 ++- common/net/src/msg/ecs_packet.rs | 6 ++--- common/net/src/sync/interpolation.rs | 26 +++++++++++++++++++++ common/net/src/sync/mod.rs | 6 +++-- common/net/src/sync/packet.rs | 34 ++++++++++++++++++++++++++++ common/sys/src/state.rs | 3 ++- 7 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 common/net/src/sync/interpolation.rs diff --git a/Cargo.lock b/Cargo.lock index 3e6de98ee3..c16404c034 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5619,6 +5619,7 @@ dependencies = [ "hashbrown", "serde", "specs", + "specs-idvs", "sum_type", "tracing", "vek 0.14.1", diff --git a/common/net/Cargo.toml b/common/net/Cargo.toml index 227ae093f9..b01fd9fc5e 100644 --- a/common/net/Cargo.toml +++ b/common/net/Cargo.toml @@ -25,6 +25,7 @@ authc = { git = "https://gitlab.com/veloren/auth.git", rev = "fb3dcbc4962b367253 # ECS specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control"], rev = "5a9b71035007be0e3574f35184acac1cd4530496" } +specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "b65fb220e94f5d3c9bc30074a076149763795556" } # Serde -serde = { version = "1.0.110", features = ["derive"] } \ No newline at end of file +serde = { version = "1.0.110", features = ["derive"] } diff --git a/common/net/src/msg/ecs_packet.rs b/common/net/src/msg/ecs_packet.rs index 1ee77e127d..804d1ed1f1 100644 --- a/common/net/src/msg/ecs_packet.rs +++ b/common/net/src/msg/ecs_packet.rs @@ -100,7 +100,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Gravity(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::Pos(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::Pos(comp) => sync::handle_interp_insert(comp, entity, world), EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Ori(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world), @@ -132,7 +132,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Gravity(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::Pos(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::Pos(comp) => sync::handle_interp_modify(comp, entity, world), EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Ori(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world), @@ -168,7 +168,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::CharacterState(_) => { sync::handle_remove::(entity, world) }, - EcsCompPhantom::Pos(_) => sync::handle_remove::(entity, world), + EcsCompPhantom::Pos(_) => sync::handle_interp_remove::(entity, world), EcsCompPhantom::Vel(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Ori(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Shockwave(_) => sync::handle_remove::(entity, world), diff --git a/common/net/src/sync/interpolation.rs b/common/net/src/sync/interpolation.rs new file mode 100644 index 0000000000..6c321eb0a7 --- /dev/null +++ b/common/net/src/sync/interpolation.rs @@ -0,0 +1,26 @@ +// impls of `InterpolatableComponent` on things defined in `common`, since `common_net` is +// downstream of `common` +use common::comp::{Pos, Vel}; +use super::InterpolatableComponent; +use specs::{Component, Entity, World}; +use specs_idvs::IdvStorage; +use vek::Vec3; + +#[derive(Default)] +pub struct PosBuffer(pub [Vec3; 4]); + +impl Component for PosBuffer { + type Storage = IdvStorage; +} + +impl InterpolatableComponent for Pos { + type InterpData = PosBuffer; + + fn interpolate(self, data: &mut Self::InterpData, entity: Entity, world: &World) -> Self { + for i in 0..data.0.len()-1 { + data.0[i] = data.0[i+1]; + } + data.0[data.0.len()-1] = self.0; + self + } +} diff --git a/common/net/src/sync/mod.rs b/common/net/src/sync/mod.rs index 2b25c0db43..7997437db5 100644 --- a/common/net/src/sync/mod.rs +++ b/common/net/src/sync/mod.rs @@ -1,5 +1,6 @@ // Note: Currently only one-way sync is supported until a usecase for two-way // sync arises +pub mod interpolation; mod packet; mod sync_ext; mod track; @@ -7,8 +8,9 @@ mod track; // Reexports pub use common::uid::{Uid, UidAllocator}; pub use packet::{ - handle_insert, handle_modify, handle_remove, CompPacket, CompSyncPackage, EntityPackage, - EntitySyncPackage, StatePackage, + handle_insert, handle_interp_insert, handle_interp_modify, handle_interp_remove, handle_modify, + handle_remove, CompPacket, CompSyncPackage, EntityPackage, EntitySyncPackage, + InterpolatableComponent, StatePackage, }; pub use sync_ext::WorldSyncExt; pub use track::UpdateTracker; diff --git a/common/net/src/sync/packet.rs b/common/net/src/sync/packet.rs index e86dfb30fa..d9d3e67d0d 100644 --- a/common/net/src/sync/packet.rs +++ b/common/net/src/sync/packet.rs @@ -42,6 +42,40 @@ pub fn handle_remove(entity: Entity, world: &World) { world.write_storage::().remove(entity); } +pub trait InterpolatableComponent: Component { + type InterpData: Component + Default; + + fn interpolate(self, data: &mut Self::InterpData, entity: Entity, world: &World) -> Self; +} + +pub fn handle_interp_insert(comp: C, entity: Entity, world: &World) { + let mut interp_data = C::InterpData::default(); + let comp = comp.interpolate(&mut interp_data, entity, world); + handle_insert(comp, entity, world); + handle_insert(interp_data, entity, world); +} + +pub fn handle_interp_modify( + comp: C, + entity: Entity, + world: &World, +) { + if let Some(mut interp_data) = world.write_storage::().get_mut(entity) { + let comp = comp.interpolate(&mut interp_data, entity, world); + handle_modify(comp, entity, world); + } else { + error!( + ?comp, + "Error modifying interpolation data for synced component, it doesn't seem to exist" + ); + } +} + +pub fn handle_interp_remove(entity: Entity, world: &World) { + handle_remove::(entity, world); + handle_remove::(entity, world); +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub enum CompUpdateKind { Inserted(P), diff --git a/common/sys/src/state.rs b/common/sys/src/state.rs index a3d222ecdc..b4575f0065 100644 --- a/common/sys/src/state.rs +++ b/common/sys/src/state.rs @@ -15,7 +15,7 @@ use common::{ }; use common_base::span; use common_ecs::{PhysicsMetrics, SysMetrics}; -use common_net::sync::WorldSyncExt; +use common_net::sync::{interpolation, WorldSyncExt}; use hashbrown::{HashMap, HashSet}; use rayon::{ThreadPool, ThreadPoolBuilder}; use specs::{ @@ -164,6 +164,7 @@ impl State { // Register client-local components // TODO: only register on the client ecs.register::(); + ecs.register::(); // Register server-local components // TODO: only register on the server From dad0012973bbeb60bd3ae96229963e410851580b Mon Sep 17 00:00:00 2001 From: Avi Weinstock Date: Sat, 13 Mar 2021 17:15:19 -0500 Subject: [PATCH 31/41] Get linear interpolation working for {Pos,Vel,Ori} with client-side timestamps. --- client/src/lib.rs | 2 + common/net/src/msg/ecs_packet.rs | 12 +-- common/net/src/sync/interpolation.rs | 127 ++++++++++++++++++++++++--- common/net/src/sync/packet.rs | 16 +++- common/src/resources.rs | 6 ++ common/sys/src/interpolation.rs | 63 +++++++++++++ common/sys/src/lib.rs | 1 + common/sys/src/state.rs | 12 ++- 8 files changed, 211 insertions(+), 28 deletions(-) create mode 100644 common/sys/src/interpolation.rs diff --git a/client/src/lib.rs b/client/src/lib.rs index 632f7205c4..2e4101f924 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -33,6 +33,7 @@ use common::{ grid::Grid, outcome::Outcome, recipe::RecipeBook, + resources::PlayerEntity, terrain::{block::Block, neighbors, BiomeKind, SitesKind, TerrainChunk, TerrainChunkSize}, trade::{PendingTrade, TradeAction, TradeId, TradeResult}, uid::{Uid, UidAllocator}, @@ -281,6 +282,7 @@ impl Client { let entity = state.ecs_mut().apply_entity_package(entity_package); *state.ecs_mut().write_resource() = time_of_day; + *state.ecs_mut().write_resource() = PlayerEntity(Some(entity)); state.ecs_mut().insert(material_stats); state.ecs_mut().insert(ability_map); diff --git a/common/net/src/msg/ecs_packet.rs b/common/net/src/msg/ecs_packet.rs index 804d1ed1f1..86049f7103 100644 --- a/common/net/src/msg/ecs_packet.rs +++ b/common/net/src/msg/ecs_packet.rs @@ -101,8 +101,8 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Sticky(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Pos(comp) => sync::handle_interp_insert(comp, entity, world), - EcsCompPacket::Vel(comp) => sync::handle_insert(comp, entity, world), - EcsCompPacket::Ori(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::Vel(comp) => sync::handle_interp_insert(comp, entity, world), + EcsCompPacket::Ori(comp) => sync::handle_interp_insert(comp, entity, world), EcsCompPacket::Shockwave(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::BeamSegment(comp) => sync::handle_insert(comp, entity, world), } @@ -133,8 +133,8 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Sticky(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CharacterState(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Pos(comp) => sync::handle_interp_modify(comp, entity, world), - EcsCompPacket::Vel(comp) => sync::handle_modify(comp, entity, world), - EcsCompPacket::Ori(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::Vel(comp) => sync::handle_interp_modify(comp, entity, world), + EcsCompPacket::Ori(comp) => sync::handle_interp_modify(comp, entity, world), EcsCompPacket::Shockwave(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::BeamSegment(comp) => sync::handle_modify(comp, entity, world), } @@ -169,8 +169,8 @@ impl sync::CompPacket for EcsCompPacket { sync::handle_remove::(entity, world) }, EcsCompPhantom::Pos(_) => sync::handle_interp_remove::(entity, world), - EcsCompPhantom::Vel(_) => sync::handle_remove::(entity, world), - EcsCompPhantom::Ori(_) => sync::handle_remove::(entity, world), + EcsCompPhantom::Vel(_) => sync::handle_interp_remove::(entity, world), + EcsCompPhantom::Ori(_) => sync::handle_interp_remove::(entity, world), EcsCompPhantom::Shockwave(_) => sync::handle_remove::(entity, world), EcsCompPhantom::BeamSegment(_) => sync::handle_remove::(entity, world), } diff --git a/common/net/src/sync/interpolation.rs b/common/net/src/sync/interpolation.rs index 6c321eb0a7..17b9d4ef1c 100644 --- a/common/net/src/sync/interpolation.rs +++ b/common/net/src/sync/interpolation.rs @@ -1,26 +1,125 @@ -// impls of `InterpolatableComponent` on things defined in `common`, since `common_net` is -// downstream of `common` -use common::comp::{Pos, Vel}; +// impls of `InterpolatableComponent` on things defined in `common`, since +// `common_net` is downstream of `common`, and an `InterpolationSystem` that +// applies them use super::InterpolatableComponent; -use specs::{Component, Entity, World}; +use common::comp::{Ori, Pos, Vel}; +use specs::Component; use specs_idvs::IdvStorage; -use vek::Vec3; +use tracing::warn; +use vek::ops::{Lerp, Slerp}; -#[derive(Default)] -pub struct PosBuffer(pub [Vec3; 4]); +#[derive(Debug, Default)] +pub struct InterpBuffer { + pub buf: [(f64, T); 4], + pub i: usize, +} -impl Component for PosBuffer { +impl Component for InterpBuffer { type Storage = IdvStorage; } impl InterpolatableComponent for Pos { - type InterpData = PosBuffer; + type InterpData = InterpBuffer; + type ReadData = Vel; - fn interpolate(self, data: &mut Self::InterpData, entity: Entity, world: &World) -> Self { - for i in 0..data.0.len()-1 { - data.0[i] = data.0[i+1]; + fn update_component(&self, interp_data: &mut Self::InterpData, time: f64) { + let InterpBuffer { + ref mut buf, + ref mut i, + } = interp_data; + *i += 1; + *i %= buf.len(); + buf[*i] = (time, *self); + } + + fn interpolate(self, interp_data: &Self::InterpData, t2: f64, _vel: &Vel) -> Self { + // lerp to test interface, do hermite spline later + let InterpBuffer { ref buf, ref i } = interp_data; + let (t0, p0) = buf[(i + buf.len() - 1) % buf.len()]; + let (t1, p1) = buf[i % buf.len()]; + if (t1 - t0).abs() < f64::EPSILON { + return self; } - data.0[data.0.len()-1] = self.0; - self + let lerp_factor = 1.0 + ((t2 - t1) / (t1 - t0)) as f32; + let mut out = Lerp::lerp_unclamped(p0.0, p1.0, lerp_factor); + if out.map(|x| x.is_nan()).reduce_or() { + warn!( + "interpolation output is nan: {}, {}, {:?}", + t2, lerp_factor, buf + ); + out = p1.0; + } + + Pos(out) + } +} + +impl InterpolatableComponent for Vel { + type InterpData = InterpBuffer; + type ReadData = (); + + fn update_component(&self, interp_data: &mut Self::InterpData, time: f64) { + let InterpBuffer { + ref mut buf, + ref mut i, + } = interp_data; + *i += 1; + *i %= buf.len(); + buf[*i] = (time, *self); + } + + fn interpolate(self, interp_data: &Self::InterpData, t2: f64, _: &()) -> Self { + let InterpBuffer { ref buf, ref i } = interp_data; + let (t0, p0) = buf[(i + buf.len() - 1) % buf.len()]; + let (t1, p1) = buf[i % buf.len()]; + if (t1 - t0).abs() < f64::EPSILON { + return self; + } + let lerp_factor = 1.0 + ((t2 - t1) / (t1 - t0)) as f32; + let mut out = Lerp::lerp_unclamped(p0.0, p1.0, lerp_factor); + if out.map(|x| x.is_nan()).reduce_or() { + warn!( + "interpolation output is nan: {}, {}, {:?}", + t2, lerp_factor, buf + ); + out = p1.0; + } + + Vel(out) + } +} + +impl InterpolatableComponent for Ori { + type InterpData = InterpBuffer; + type ReadData = (); + + fn update_component(&self, interp_data: &mut Self::InterpData, time: f64) { + let InterpBuffer { + ref mut buf, + ref mut i, + } = interp_data; + *i += 1; + *i %= buf.len(); + buf[*i] = (time, *self); + } + + fn interpolate(self, interp_data: &Self::InterpData, t2: f64, _: &()) -> Self { + let InterpBuffer { ref buf, ref i } = interp_data; + let (t0, p0) = buf[(i + buf.len() - 1) % buf.len()]; + let (t1, p1) = buf[i % buf.len()]; + if (t1 - t0).abs() < f64::EPSILON { + return self; + } + let lerp_factor = 1.0 + ((t2 - t1) / (t1 - t0)) as f32; + let mut out = Slerp::slerp_unclamped(p0.0, p1.0, lerp_factor); + if out.into_vec4().map(|x| x.is_nan()).reduce_or() { + warn!( + "interpolation output is nan: {}, {}, {:?}", + t2, lerp_factor, buf + ); + out = p1.0; + } + + Ori(out.normalized()) } } diff --git a/common/net/src/sync/packet.rs b/common/net/src/sync/packet.rs index d9d3e67d0d..152b7946d6 100644 --- a/common/net/src/sync/packet.rs +++ b/common/net/src/sync/packet.rs @@ -1,5 +1,5 @@ use super::track::UpdateTracker; -use common::uid::Uid; +use common::{resources::Time, uid::Uid}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use specs::{Component, Entity, Join, ReadStorage, World, WorldExt}; use std::{ @@ -9,6 +9,10 @@ use std::{ }; use tracing::error; +// TODO: apply_{insert,modify,remove} all take the entity and call +// `write_storage` once per entity per component, instead of once per update +// batch(e.g. in a system-like memory access pattern); if sync ends up being a +// bottleneck, try optimizing this /// Implemented by type that carries component data for insertion and /// modification The assocatied `Phantom` type only carries information about /// which component type is of interest and is used to transmit deletion events @@ -44,13 +48,16 @@ pub fn handle_remove(entity: Entity, world: &World) { pub trait InterpolatableComponent: Component { type InterpData: Component + Default; + type ReadData; - fn interpolate(self, data: &mut Self::InterpData, entity: Entity, world: &World) -> Self; + fn update_component(&self, data: &mut Self::InterpData, time: f64); + fn interpolate(self, data: &Self::InterpData, time: f64, read_data: &Self::ReadData) -> Self; } pub fn handle_interp_insert(comp: C, entity: Entity, world: &World) { let mut interp_data = C::InterpData::default(); - let comp = comp.interpolate(&mut interp_data, entity, world); + let time = world.read_resource::