diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 6324c05..d93729a 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -120,7 +120,7 @@ async fn main() { game.world().insert_resource(TpsAccumulator(0.0)); let mut sys = BatchedSystem::new(); - sys.with_criteria(FixedTimestep::new(30)); + sys.with_criteria(FixedTimestep::new(10)); sys.with_system(spin_system); sys.with_system(fps_system); diff --git a/src/math/transform.rs b/src/math/transform.rs index 5e213f2..fef492a 100755 --- a/src/math/transform.rs +++ b/src/math/transform.rs @@ -85,4 +85,18 @@ impl Transform { pub fn rotate_z(&mut self, angle: Angle) { self.rotate(Quat::from_rotation_z(angle.to_radians())) } + + /// Performs a linear interpolation between `self` and `rhs` based on the value `alpha`. + /// + /// When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, the result + /// will be equal to `rhs`. When `alpha` is outside of range `[0, 1]`, the result is linearly + /// extrapolated. + pub fn lerp(&self, rhs: Transform, alpha: f32) -> Self { + let mut res = self.clone(); + res.translation = self.translation.lerp(rhs.translation, alpha); + res.rotation = self.rotation.lerp(rhs.rotation, alpha); + res.scale = self.scale.lerp(rhs.scale, alpha); + + res + } } \ No newline at end of file diff --git a/src/render/render_job.rs b/src/render/render_job.rs index 319329f..1cdc102 100755 --- a/src/render/render_job.rs +++ b/src/render/render_job.rs @@ -2,17 +2,19 @@ use edict::EntityId; use crate::math::Transform; +use super::renderer::CachedTransform; + pub struct RenderJob { pub entity: EntityId, pub shader_id: u64, pub mesh_buffer_id: uuid::Uuid, pub transform: Transform, - pub last_transform: Option, // TODO: render interpolation + pub last_transform: Option, // TODO: render interpolation } impl RenderJob { - pub fn new(entity: EntityId, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform, last_transform: Option) -> Self { + pub fn new(entity: EntityId, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform, last_transform: Option) -> Self { Self { entity, shader_id, diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 5d67f97..aa8941c 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -7,6 +7,7 @@ use std::borrow::Cow; use edict::query::EpochOf; use edict::{EntityId, Entities}; use glam::Vec3; +use instant::Instant; use tracing::{debug, warn}; use wgpu::{BindGroup, BindGroupLayout, Limits}; use wgpu::util::DeviceExt; @@ -48,6 +49,13 @@ struct MeshBufferStorage { transform_index: TransformBufferIndices, } +#[derive(Clone, Debug)] +pub struct CachedTransform { + last_updated_at: Option, + cached_at: Instant, + transform: Transform, +} + pub struct BasicRenderer { pub surface: wgpu::Surface, pub device: wgpu::Device, @@ -63,6 +71,7 @@ pub struct BasicRenderer { mesh_buffers: HashMap, // TODO: clean up left over buffers from deleted entities/components entity_meshes: HashMap, + entity_last_transforms: HashMap, transform_buffers: TransformBuffers, transform_bind_group_layout: BindGroupLayout, @@ -312,6 +321,7 @@ impl BasicRenderer { texture_bind_group_layout, default_texture_bind_group: default_tex_bindgroup, depth_buffer_texture: depth_texture, + entity_last_transforms: HashMap::new(), }; // create the default pipelines @@ -510,18 +520,77 @@ impl Renderer for BasicRenderer { let mut alive_entities = HashSet::new(); - for (entity, model, model_epoch, transform) in main_world.query::<(Entities, &ModelComponent, EpochOf, &TransformComponent)>().iter() { + let now_inst = Instant::now(); + + for (entity, model, model_epoch, transform, transform_epoch) in main_world.query::<(Entities, &ModelComponent, EpochOf, &TransformComponent, EpochOf)>().iter() { alive_entities.insert(entity); let model = model.data.as_ref().unwrap().as_ref(); for mesh in model.meshes.iter() { + let last_transform = self.entity_last_transforms.get(&entity).cloned(); + + + if last_transform.is_none() { + let cached = CachedTransform { + last_updated_at: None, + cached_at: now_inst, + transform: transform.transform, + }; + self.entity_last_transforms.insert(entity, cached); + } else if transform_epoch == last_epoch { + let last = self.entity_last_transforms.get_mut(&entity).unwrap(); + last.transform = transform.transform; + last.last_updated_at = Some(last.cached_at); + last.cached_at = now_inst; + debug!("Updated transform"); + // to get the fixed delta time, you'd just subtract last_updated_at and cached_at. + // to get the tps_accumulator, you'd get the elapsed ms of cached_at. + } + + let transform_val = match last_transform { + Some(last) => { + debug!("now: {:?}, cached_at: {:?}, last_updated_at: {:?}", now_inst, last.cached_at, last.last_updated_at); + let fixed_time = match last.last_updated_at { + Some(last_updated_at) => last.cached_at - last_updated_at, + None => now_inst - last.cached_at + }.as_secs_f32(); + let accumulator = last.cached_at.elapsed().as_secs_f32(); + + let alpha = accumulator / fixed_time; + + debug!("fixed time: {fixed_time}, acc: {accumulator}, alpha: {alpha}"); + + last.transform.lerp(transform.transform, alpha) + } , + None => { + transform.transform + } + }; + + /*{ + match self.entity_last_transforms.get_mut(&entity) { + Some(last) => { + last.transform = transform.transform.clone(); + last.last_updated_at = Some(last.cached_at); + last.cached_at = Instant::now(); + }, + None => { + self.entity_last_transforms.insert(entity, CachedTransform { + last_updated_at: None, + cached_at: Instant::now(), + transform: transform.transform.clone(), + }); + } + } + }*/ + if !self.process_mesh(entity, transform.transform, mesh) && model_epoch == last_epoch { self.update_mesh_buffers(entity, mesh); } - + let shader = mesh.material().shader_uuid.unwrap_or(0); - let job = RenderJob::new(entity, shader, mesh.uuid, transform.transform, None); + let job = RenderJob::new(entity, shader, mesh.uuid, transform_val, None); self.render_jobs.push_back(job); } }