mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Get linear interpolation working for {Pos,Vel,Ori} with client-side timestamps.
This commit is contained in:
parent
a71bacdce0
commit
dad0012973
@ -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);
|
||||
|
||||
|
@ -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::<comp::CharacterState>(entity, world)
|
||||
},
|
||||
EcsCompPhantom::Pos(_) => sync::handle_interp_remove::<comp::Pos>(entity, world),
|
||||
EcsCompPhantom::Vel(_) => sync::handle_remove::<comp::Vel>(entity, world),
|
||||
EcsCompPhantom::Ori(_) => sync::handle_remove::<comp::Ori>(entity, world),
|
||||
EcsCompPhantom::Vel(_) => sync::handle_interp_remove::<comp::Vel>(entity, world),
|
||||
EcsCompPhantom::Ori(_) => sync::handle_interp_remove::<comp::Ori>(entity, world),
|
||||
EcsCompPhantom::Shockwave(_) => sync::handle_remove::<comp::Shockwave>(entity, world),
|
||||
EcsCompPhantom::BeamSegment(_) => sync::handle_remove::<comp::Ori>(entity, world),
|
||||
}
|
||||
|
@ -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<f32>; 4]);
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InterpBuffer<T> {
|
||||
pub buf: [(f64, T); 4],
|
||||
pub i: usize,
|
||||
}
|
||||
|
||||
impl Component for PosBuffer {
|
||||
impl<T: 'static + Send + Sync> Component for InterpBuffer<T> {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
||||
impl InterpolatableComponent for Pos {
|
||||
type InterpData = PosBuffer;
|
||||
type InterpData = InterpBuffer<Pos>;
|
||||
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<Vel>;
|
||||
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<Ori>;
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
@ -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<C: Component>(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<C: InterpolatableComponent>(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::<Time>().0;
|
||||
comp.update_component(&mut interp_data, time);
|
||||
handle_insert(comp, entity, world);
|
||||
handle_insert(interp_data, entity, world);
|
||||
}
|
||||
@ -61,7 +68,8 @@ pub fn handle_interp_modify<C: InterpolatableComponent + Debug>(
|
||||
world: &World,
|
||||
) {
|
||||
if let Some(mut interp_data) = world.write_storage::<C::InterpData>().get_mut(entity) {
|
||||
let comp = comp.interpolate(&mut interp_data, entity, world);
|
||||
let time = world.read_resource::<Time>().0;
|
||||
comp.update_component(&mut interp_data, time);
|
||||
handle_modify(comp, entity, world);
|
||||
} else {
|
||||
error!(
|
||||
|
@ -1,4 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::Entity;
|
||||
|
||||
/// A resource that stores the time of day.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Default)]
|
||||
@ -26,3 +27,8 @@ pub enum GameMode {
|
||||
// To be used later when we no longer start up an entirely new server for singleplayer
|
||||
Singleplayer,
|
||||
}
|
||||
|
||||
/// A resource that stores the player's entity (on the client), and None on the
|
||||
/// server
|
||||
#[derive(Copy, Clone, Default, Debug)]
|
||||
pub struct PlayerEntity(pub Option<Entity>);
|
||||
|
63
common/sys/src/interpolation.rs
Normal file
63
common/sys/src/interpolation.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use common::{
|
||||
comp::{Ori, Pos, Vel},
|
||||
resources::{PlayerEntity, Time},
|
||||
};
|
||||
use common_ecs::{Job, Origin, Phase, System};
|
||||
use common_net::sync::InterpolatableComponent;
|
||||
use specs::{
|
||||
prelude::ParallelIterator, shred::ResourceId, Entities, ParJoin, Read, ReadStorage, SystemData,
|
||||
World, WriteStorage,
|
||||
};
|
||||
|
||||
#[derive(SystemData)]
|
||||
pub struct InterpolationSystemData<'a> {
|
||||
time: Read<'a, Time>,
|
||||
player: Read<'a, PlayerEntity>,
|
||||
entities: Entities<'a>,
|
||||
pos: WriteStorage<'a, Pos>,
|
||||
pos_interpdata: ReadStorage<'a, <Pos as InterpolatableComponent>::InterpData>,
|
||||
vel: WriteStorage<'a, Vel>,
|
||||
vel_interpdata: ReadStorage<'a, <Vel as InterpolatableComponent>::InterpData>,
|
||||
ori: WriteStorage<'a, Ori>,
|
||||
ori_interpdata: ReadStorage<'a, <Ori as InterpolatableComponent>::InterpData>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InterpolationSystem;
|
||||
|
||||
impl<'a> System<'a> for InterpolationSystem {
|
||||
type SystemData = InterpolationSystemData<'a>;
|
||||
|
||||
const NAME: &'static str = "interpolation";
|
||||
const ORIGIN: Origin = Origin::Common;
|
||||
const PHASE: Phase = Phase::Apply;
|
||||
|
||||
fn run(_job: &mut Job<Self>, mut data: InterpolationSystemData<'a>) {
|
||||
let time = data.time.0;
|
||||
let player = data.player.0;
|
||||
|
||||
(
|
||||
&data.entities,
|
||||
&mut data.pos,
|
||||
&data.pos_interpdata,
|
||||
&data.vel,
|
||||
)
|
||||
.par_join()
|
||||
.filter(|(e, _, _, _)| Some(e) != player.as_ref())
|
||||
.for_each(|(_, pos, interp, vel)| {
|
||||
*pos = pos.interpolate(interp, time, vel);
|
||||
});
|
||||
(&data.entities, &mut data.vel, &data.vel_interpdata)
|
||||
.par_join()
|
||||
.filter(|(e, _, _)| Some(e) != player.as_ref())
|
||||
.for_each(|(_, vel, interp)| {
|
||||
*vel = vel.interpolate(interp, time, &());
|
||||
});
|
||||
(&data.entities, &mut data.ori, &data.ori_interpdata)
|
||||
.par_join()
|
||||
.filter(|(e, _, _)| Some(e) != player.as_ref())
|
||||
.for_each(|(_, ori, interp)| {
|
||||
*ori = ori.interpolate(interp, time, &());
|
||||
});
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ mod beam;
|
||||
mod buff;
|
||||
pub mod character_behavior;
|
||||
pub mod controller;
|
||||
mod interpolation;
|
||||
pub mod melee;
|
||||
mod mount;
|
||||
pub mod phys;
|
||||
|
@ -6,7 +6,7 @@ use common::{
|
||||
comp,
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
region::RegionMap,
|
||||
resources::{DeltaTime, GameMode, Time, TimeOfDay},
|
||||
resources::{DeltaTime, GameMode, PlayerEntity, Time, TimeOfDay},
|
||||
terrain::{Block, TerrainChunk, TerrainGrid},
|
||||
time::DayPeriod,
|
||||
trade::Trades,
|
||||
@ -14,8 +14,8 @@ use common::{
|
||||
vol::{ReadVol, WriteVol},
|
||||
};
|
||||
use common_base::span;
|
||||
use common_ecs::{PhysicsMetrics, SysMetrics};
|
||||
use common_net::sync::{interpolation, WorldSyncExt};
|
||||
use common_ecs::{run_now, PhysicsMetrics, SysMetrics};
|
||||
use common_net::sync::{interpolation as sync_interp, WorldSyncExt};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||
use specs::{
|
||||
@ -164,7 +164,9 @@ impl State {
|
||||
// Register client-local components
|
||||
// TODO: only register on the client
|
||||
ecs.register::<comp::LightAnimation>();
|
||||
ecs.register::<interpolation::PosBuffer>();
|
||||
ecs.register::<sync_interp::InterpBuffer<comp::Pos>>();
|
||||
ecs.register::<sync_interp::InterpBuffer<comp::Vel>>();
|
||||
ecs.register::<sync_interp::InterpBuffer<comp::Ori>>();
|
||||
|
||||
// Register server-local components
|
||||
// TODO: only register on the server
|
||||
@ -194,6 +196,7 @@ impl State {
|
||||
// Register unsynced resources used by the ECS.
|
||||
ecs.insert(Time(0.0));
|
||||
ecs.insert(DeltaTime(0.0));
|
||||
ecs.insert(PlayerEntity(None));
|
||||
ecs.insert(TerrainGrid::new().unwrap());
|
||||
ecs.insert(BlockChange::default());
|
||||
ecs.insert(TerrainChanges::default());
|
||||
@ -438,6 +441,7 @@ impl State {
|
||||
let mut dispatcher = dispatch_builder.build();
|
||||
drop(guard);
|
||||
span!(guard, "run systems");
|
||||
run_now::<crate::interpolation::InterpolationSystem>(&self.ecs);
|
||||
dispatcher.dispatch(&self.ecs);
|
||||
drop(guard);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user